diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 288724c7..8f4df2c6 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -68,4 +68,4 @@ jobs: command: wasm args: --locked env: - RUSTFLAGS: "-C link-arg=-s" \ No newline at end of file + RUSTFLAGS: "-C link-arg=-s" diff --git a/.github/workflows/interchaintest.yml b/.github/workflows/interchaintest.yml new file mode 100644 index 00000000..2c5a6f1b --- /dev/null +++ b/.github/workflows/interchaintest.yml @@ -0,0 +1,136 @@ +name: interchaintest + +permissions: + contents: write + +on: + pull_request: + push: + +env: + GO_VERSION: 1.21 + +jobs: + compile-contracts: + name: compile wasm contract with workspace-optimizer + runs-on: ubuntu-latest + steps: + - name: checkout sources + uses: actions/checkout@v3 + + - name: install latest cargo nightly toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.71.0 + target: wasm32-unknown-unknown + override: true + + - uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: install tar for cache + run: | + sudo apt-get update + sudo apt-get install tar + + - name: set up cargo cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: build artifacts + timeout-minutes: 40 + run: | + docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/workspace-optimizer:0.14.0 + + - name: upload contracts + uses: actions/upload-artifact@v3 + with: + name: contracts + path: artifacts/ + + swap-covenant: + needs: compile-contracts + runs-on: ubuntu-latest + steps: + - name: checkout repository + uses: actions/checkout@v3 + + - uses: actions/download-artifact@v3 + with: + name: contracts + path: artifacts/ + + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + + - name: setup just + uses: extractions/setup-just@v1 + + - name: swap covenant + run: mkdir interchaintest/swap/wasms && cp -R artifacts/*.wasm interchaintest/swap/wasms && just local-e2e swap TestTokenSwap + + # todo: figure out how to run ci with custom stride image + # single-party-pol-covenant: + # needs: compile-contracts + # runs-on: ubuntu-latest + # steps: + # - name: checkout repository + # uses: actions/checkout@v3 + + # - uses: actions/download-artifact@v3 + # with: + # name: contracts + # path: artifacts/ + + # - name: Set up Go ${{ env.GO_VERSION }} + # uses: actions/setup-go@v4 + # with: + # go-version: ${{ env.GO_VERSION }} + + # - name: setup just + # uses: extractions/setup-just@v1 + + # - name: single party pol covenant + # run: mkdir interchaintest/single-party-pol/wasms && cp -R artifacts/*.wasm interchaintest/single-party-pol/wasms && cp -R interchaintest/wasms/astroport/*.wasm interchaintest/single-party-pol/wasms && just local-e2e single-party-pol + + + two-party-pol-covenant: + needs: compile-contracts + runs-on: ubuntu-latest + steps: + - name: checkout repository + uses: actions/checkout@v3 + + - uses: actions/download-artifact@v3 + with: + name: contracts + path: artifacts/ + + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + + - name: setup just + uses: extractions/setup-just@v1 + + - name: two party POL native & interchain parties + run: mkdir -p interchaintest/two-party-pol/wasms && cp -R artifacts/*.wasm interchaintest/two-party-pol/wasms && cp -R interchaintest/wasms/astroport/*.wasm interchaintest/two-party-pol/wasms && just local-e2e two-party-pol TestTwoPartyNativePartyPol + + - name: two party POL two interchain parties + run: mkdir -p interchaintest/two-party-pol/wasms && cp -R artifacts/*.wasm interchaintest/two-party-pol/wasms && cp -R interchaintest/wasms/astroport/*.wasm interchaintest/two-party-pol/wasms && just local-e2e two-party-pol TestTwoPartyPol + diff --git a/.gitignore b/.gitignore index a4e8210c..009e165f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,10 +17,10 @@ target/ .cargo-ok # Build artifacts -*.wasm hash.txt contracts.txt artifacts/ +interchaintest/**/wasms/*.wasm # code coverage tarpaulin-report.* diff --git a/Cargo.lock b/Cargo.lock index e73bd3e7..3ca94482 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -15,109 +15,112 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "astroport" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcea351626899d205aab091c891fc878fc9b3c930585fd3ef6222de028d8a7a" +version = "2.9.5" +source = "git+https://github.com/astroport-fi/astroport-core.git?rev=700f66d#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", "cw-utils 0.15.1", - "cw20 0.15.1", - "itertools", + "cw20", + "itertools 0.10.5", "uint", ] [[package]] -name = "astroport" -version = "3.1.2" -source = "git+https://github.com/astroport-fi/astroport-core.git#d227ed3512f1094c11f54beaec27647387c0d7ba" +name = "astroport-factory" +version = "1.5.1" +source = "git+https://github.com/astroport-fi/astroport-core.git?rev=700f66d#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" dependencies = [ - "astroport-circular-buffer", + "astroport", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", - "cw-utils 1.0.1", - "cw20 0.15.1", - "cw3", - "itertools", - "uint", + "cw2 0.15.1", + "itertools 0.10.5", + "protobuf 2.28.0", + "thiserror", ] [[package]] -name = "astroport-circular-buffer" -version = "0.1.0" -source = "git+https://github.com/astroport-fi/astroport-core.git#d227ed3512f1094c11f54beaec27647387c0d7ba" +name = "astroport-native-coin-registry" +version = "1.0.1" +source = "git+https://github.com/astroport-fi/astroport-core.git?rev=700f66d#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" dependencies = [ + "astroport", "cosmwasm-schema", "cosmwasm-std", + "cosmwasm-storage", "cw-storage-plus 0.15.1", + "cw2 0.15.1", "thiserror", ] [[package]] -name = "astroport-factory" -version = "1.5.1" -source = "git+https://github.com/astroport-fi/astroport-core.git#d227ed3512f1094c11f54beaec27647387c0d7ba" +name = "astroport-pair" +version = "1.3.3" +source = "git+https://github.com/astroport-fi/astroport-core.git?rev=700f66d#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" dependencies = [ - "astroport 3.1.2", + "astroport", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", - "cw-utils 1.0.1", "cw2 0.15.1", - "itertools", + "cw20", + "integer-sqrt", "protobuf 2.28.0", "thiserror", ] [[package]] -name = "astroport-native-coin-registry" -version = "1.0.1" -source = "git+https://github.com/astroport-fi/astroport-core.git#d227ed3512f1094c11f54beaec27647387c0d7ba" +name = "astroport-pair-concentrated" +version = "1.2.13" +source = "git+https://github.com/astroport-fi/astroport-core.git?rev=700f66d#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" dependencies = [ - "astroport 3.1.2", + "astroport", + "astroport-factory", "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", "cw2 0.15.1", + "cw20", + "itertools 0.10.5", "thiserror", ] [[package]] name = "astroport-pair-stable" -version = "3.0.0" -source = "git+https://github.com/astroport-fi/astroport-core.git#d227ed3512f1094c11f54beaec27647387c0d7ba" +version = "2.1.4" +source = "git+https://github.com/astroport-fi/astroport-core.git?rev=700f66d#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" dependencies = [ - "astroport 3.1.2", - "astroport-circular-buffer", + "astroport", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", - "cw-utils 1.0.1", + "cw-utils 1.0.3", "cw2 0.15.1", - "cw20 0.15.1", - "itertools", + "cw20", + "itertools 0.10.5", "thiserror", ] [[package]] name = "astroport-token" version = "1.1.1" -source = "git+https://github.com/astroport-fi/astroport-core.git#d227ed3512f1094c11f54beaec27647387c0d7ba" +source = "git+https://github.com/astroport-fi/astroport-core.git?rev=700f66d#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" dependencies = [ - "astroport 3.1.2", + "astroport", "cosmwasm-schema", "cosmwasm-std", "cw2 0.15.1", - "cw20 0.15.1", + "cw20", "cw20-base", "snafu", ] @@ -125,12 +128,12 @@ dependencies = [ [[package]] name = "astroport-whitelist" version = "1.0.1" -source = "git+https://github.com/astroport-fi/astroport-core.git#d227ed3512f1094c11f54beaec27647387c0d7ba" +source = "git+https://github.com/astroport-fi/astroport-core.git?rev=700f66d#700f66d677a173686cb15cb9cb8c7a4d20c84ad8" dependencies = [ - "astroport 3.1.2", + "astroport", "cosmwasm-schema", "cosmwasm-std", - "cw1-whitelist 0.15.1", + "cw1-whitelist", "cw2 0.15.1", "thiserror", ] @@ -143,21 +146,15 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - -[[package]] -name = "base64" -version = "0.13.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.20.0" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -189,17 +186,23 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bnum" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" + [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" dependencies = [ "serde", ] @@ -210,11 +213,51 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "num-traits", +] + +[[package]] +name = "colored" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f741c91823341bebf717d4c71bda820630ce065443b58bd1b7451af008355" +dependencies = [ + "is-terminal", + "lazy_static", + "winapi", +] + [[package]] name = "const-oid" -version = "0.9.3" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6340df57935414636969091153f35f68d9f00bbc8fb4a9c6054706c213e6c6bc" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] [[package]] name = "cosmos-sdk-proto" @@ -223,28 +266,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20b42021d8488665b1a0d9748f1f81df7235362d194f44481e2e61bf376b77b4" dependencies = [ "prost 0.11.9", - "prost-types", + "prost-types 0.11.9", "tendermint-proto 0.23.9", ] [[package]] name = "cosmos-sdk-proto" -version = "0.16.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4776e787b24d9568dd61d3237eeb4eb321d622fb881b858c7b82806420e87d4" +checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" dependencies = [ - "prost 0.11.9", - "prost-types", - "tendermint-proto 0.27.0", + "prost 0.12.3", + "prost-types 0.12.3", + "tendermint-proto 0.34.1", ] [[package]] name = "cosmwasm-crypto" -version = "1.2.7" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb64554a91d6a9231127f4355d351130a0b94e663d5d9dc8b3a54ca17d83de49" +checksum = "e6b4c3f9c4616d6413d4b5fc4c270a4cc32a374b9be08671e80e1a019f805d8f" dependencies = [ "digest 0.10.7", + "ecdsa", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -253,18 +297,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.2.7" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0fb2ce09f41a3dae1a234d56a9988f9aff4c76441cd50ef1ee9a4f20415b028" +checksum = "c586ced10c3b00e809ee664a895025a024f60d65d34fe4c09daed4a4db68a3f3" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.7" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230e5d1cefae5331db8934763c81b9c871db6a2cd899056a5694fa71d292c815" +checksum = "ac3e3a2136e2a60e8b6582f5dffca5d1a683ed77bf38537d330bc1dfccd69010" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -275,9 +319,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.7" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43dadf7c23406cb28079d69e6cb922c9c29b9157b0fe887e3b79c783b7d4bcb8" +checksum = "f5d803bea6bd9ed61bd1ee0b4a2eb09ee20dbb539cc6e0b8795614d20952ebb1" dependencies = [ "proc-macro2", "quote", @@ -286,11 +330,13 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.7" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4337eef8dfaf8572fe6b6b415d6ec25f9308c7bb09f2da63789209fb131363be" +checksum = "712fe58f39d55c812f7b2c84e097cdede3a39d520f89b6dc3153837e31741927" dependencies = [ - "base64 0.13.1", + "base64", + "bech32", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -298,43 +344,25 @@ dependencies = [ "hex", "schemars", "serde", - "serde-json-wasm 0.5.1", - "sha2 0.10.7", + "serde-json-wasm 0.5.2", + "sha2 0.10.8", + "static_assertions", "thiserror", - "uint", ] [[package]] name = "cosmwasm-storage" -version = "1.2.7" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8601d284db8776e39fe99b3416516c5636ca73cef14666b7bb9648ca32c4b89" +checksum = "66de2ab9db04757bcedef2b5984fbe536903ada4a8a9766717a4a71197ef34f6" dependencies = [ "cosmwasm-std", "serde", ] [[package]] -name = "covenant-clock" +name = "covenant-macros" version = "1.0.0" -dependencies = [ - "anyhow", - "cosmwasm-schema", - "cosmwasm-std", - "covenant-clock-derive", - "covenant-clock-tester", - "cw-fifo", - "cw-multi-test", - "cw-storage-plus 1.1.0", - "cw2 1.1.0", - "neutron-sdk", - "serde", - "thiserror", -] - -[[package]] -name = "covenant-clock-derive" -version = "0.0.1" dependencies = [ "proc-macro2", "quote", @@ -342,155 +370,30 @@ dependencies = [ ] [[package]] -name = "covenant-clock-tester" -version = "1.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.1.0", - "cw2 1.1.0", - "thiserror", -] - -[[package]] -name = "covenant-covenant" -version = "1.0.0" -dependencies = [ - "anyhow", - "astroport 2.8.0", - "base64 0.13.1", - "bech32", - "cosmos-sdk-proto 0.14.0", - "cosmwasm-schema", - "cosmwasm-std", - "covenant-clock", - "covenant-depositor", - "covenant-holder", - "covenant-lp", - "covenant-ls", - "cw-multi-test", - "cw-storage-plus 1.1.0", - "cw-utils 1.0.1", - "cw2 1.1.0", - "neutron-sdk", - "prost 0.11.9", - "prost-types", - "protobuf 3.2.0", - "schemars", - "serde", - "serde-json-wasm 0.4.1", - "sha2 0.10.7", - "thiserror", -] - -[[package]] -name = "covenant-depositor" -version = "1.0.0" -dependencies = [ - "anyhow", - "base64 0.13.1", - "bech32", - "cosmos-sdk-proto 0.14.0", - "cosmwasm-schema", - "cosmwasm-std", - "covenant-clock", - "covenant-clock-derive", - "covenant-ls", - "cw-multi-test", - "cw-storage-plus 1.1.0", - "cw-utils 1.0.1", - "cw2 1.1.0", - "neutron-sdk", - "prost 0.11.9", - "prost-types", - "protobuf 3.2.0", - "schemars", - "serde", - "serde-json-wasm 0.4.1", - "sha2 0.10.7", - "thiserror", -] - -[[package]] -name = "covenant-holder" -version = "1.0.0" -dependencies = [ - "anyhow", - "astroport 2.8.0", - "astroport-factory", - "astroport-native-coin-registry", - "astroport-pair-stable", - "astroport-token", - "astroport-whitelist", - "cosmwasm-schema", - "cosmwasm-std", - "cw-multi-test", - "cw-storage-plus 1.1.0", - "cw2 1.1.0", - "cw20 0.15.1", - "serde", - "thiserror", -] - -[[package]] -name = "covenant-lp" +name = "covenant-utils" version = "1.0.0" dependencies = [ - "astroport 2.8.0", - "astroport-factory", - "astroport-native-coin-registry", - "astroport-pair-stable", - "astroport-token", - "astroport-whitelist", - "base64 0.13.1", + "astroport", "bech32", "cosmos-sdk-proto 0.14.0", "cosmwasm-schema", "cosmwasm-std", - "covenant-clock", - "covenant-clock-derive", - "covenant-holder", - "cw-multi-test", - "cw-storage-plus 1.1.0", - "cw-utils 1.0.1", - "cw1-whitelist 1.1.0", - "cw2 1.1.0", - "cw20 0.15.1", + "covenant-macros", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw20", "neutron-sdk", + "polytone", "prost 0.11.9", - "prost-types", - "protobuf 3.2.0", - "schemars", - "serde", - "serde-json-wasm 0.4.1", - "sha2 0.10.7", - "thiserror", -] - -[[package]] -name = "covenant-ls" -version = "1.0.0" -dependencies = [ - "cosmos-sdk-proto 0.14.0", - "cosmwasm-schema", - "cosmwasm-std", - "covenant-clock", - "covenant-clock-derive", - "cw-storage-plus 1.1.0", - "cw2 1.1.0", - "neutron-sdk", - "protobuf 3.2.0", - "schemars", - "serde", "serde-json-wasm 0.4.1", - "thiserror", + "sha2 0.10.8", ] [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -503,9 +406,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.4.9" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -538,30 +441,29 @@ dependencies = [ [[package]] name = "cw-fifo" -version = "0.0.1" +version = "1.0.0" dependencies = [ - "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "serde", ] [[package]] name = "cw-multi-test" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "127c7bb95853b8e828bdab97065c81cb5ddc20f7339180b61b2300565aaa99d1" +version = "0.20.0" +source = "git+https://github.com/Art3miX/cw-multi-test?branch=main#a09a3c0c3672ee33565a0f5281a71786c491afe1" dependencies = [ "anyhow", + "bech32", "cosmwasm-std", - "cw-storage-plus 1.1.0", - "cw-utils 1.0.1", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "derivative", - "itertools", - "k256", - "prost 0.9.0", + "itertools 0.12.1", + "prost 0.12.3", "schemars", "serde", + "sha2 0.10.8", "thiserror", ] @@ -578,9 +480,9 @@ dependencies = [ [[package]] name = "cw-storage-plus" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" +checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" dependencies = [ "cosmwasm-std", "schemars", @@ -604,13 +506,13 @@ dependencies = [ [[package]] name = "cw-utils" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw2 1.1.0", + "cw2 1.1.2", "schemars", "semver", "serde", @@ -629,18 +531,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw1" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f00d6dbf69d79809b86c144d7c322844a431568d4803ded466ed547dc393208" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "cw1-whitelist" version = "0.15.1" @@ -651,30 +541,13 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 0.15.1", "cw-utils 0.15.1", - "cw1 0.15.1", + "cw1", "cw2 0.15.1", "schemars", "serde", "thiserror", ] -[[package]] -name = "cw1-whitelist" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce24b204a0769ae41f5664d18be910a1745571dbc89f8643a62edbbab0fb127f" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.1.0", - "cw-utils 1.0.1", - "cw1 1.1.0", - "cw2 1.1.0", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw2" version = "0.15.1" @@ -690,14 +563,15 @@ dependencies = [ [[package]] name = "cw2" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" +checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0", + "cw-storage-plus 1.2.0", "schemars", + "semver", "serde", "thiserror", ] @@ -715,19 +589,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw20" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 1.0.1", - "schemars", - "serde", -] - [[package]] name = "cw20-base" version = "0.15.1" @@ -739,33 +600,18 @@ dependencies = [ "cw-storage-plus 0.15.1", "cw-utils 0.15.1", "cw2 0.15.1", - "cw20 0.15.1", + "cw20", "schemars", "semver", "serde", "thiserror", ] -[[package]] -name = "cw3" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171af3d9127de6805a7dd819fb070c7d2f6c3ea85f4193f42cef259f0a7f33d5" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 1.0.1", - "cw20 1.1.0", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "der" -version = "0.6.1" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "zeroize", @@ -798,6 +644,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -810,20 +657,22 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" -version = "0.14.8" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", + "spki", ] [[package]] @@ -843,19 +692,18 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "elliptic-curve" -version = "0.12.3" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "der", "digest 0.10.7", "ff", "generic-array", @@ -869,9 +717,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", "subtle", @@ -900,13 +748,14 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -915,9 +764,9 @@ dependencies = [ [[package]] name = "group" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", @@ -933,6 +782,18 @@ dependencies = [ "ahash", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[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" @@ -948,6 +809,26 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + [[package]] name = "itertools" version = "0.10.5" @@ -957,47 +838,73 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "k256" -version = "0.11.6" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sha2 0.10.7", + "once_cell", + "sha2 0.10.8", + "signature", ] [[package]] -name = "libc" -version = "0.2.147" +name = "lazy_static" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "neutron-sdk" -version = "0.5.0" -source = "git+https://github.com/neutron-org/neutron-sdk#141d4b7c45c45b655a60d0d7cfee8a8e4e49f455" +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "neutron-sdk" +version = "0.8.0" +source = "git+https://github.com/neutron-org/neutron-sdk?tag=v0.8.0#40f6592f1eec9e2e1bf0e0531a011294d70d1711" dependencies = [ - "base64 0.20.0", "bech32", - "cosmos-sdk-proto 0.16.0", + "cosmos-sdk-proto 0.20.0", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 1.1.0", - "prost 0.11.9", - "protobuf 3.2.0", + "prost 0.12.3", + "prost-types 0.12.3", + "protobuf 3.4.0", "schemars", "serde", - "serde-json-wasm 0.4.1", - "serde_json", + "serde-json-wasm 1.0.1", + "speedate", + "tendermint-proto 0.34.1", "thiserror", ] @@ -1014,87 +921,156 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] [[package]] name = "num_threads" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "osmosis-std" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "10d6fe6ac7fcba45ed61d738091d33c838c4cabbcf4892dc7aa56d19d39cc976" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive 0.13.2", + "prost 0.11.9", + "prost-types 0.11.9", + "schemars", + "serde", + "serde-cw-value", +] + +[[package]] +name = "osmosis-std" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7aa053bc3fad557ac90a0377688b400c395e2537f0f1de3293a15cad2e970" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive 0.20.1", + "prost 0.11.9", + "prost-types 0.11.9", + "schemars", + "serde", + "serde-cw-value", +] + +[[package]] +name = "osmosis-std-derive" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a455e262a6fdfd3914f3a4e11e6bc0ce491901cb9d507d7856d7ef6e129e90c6" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "osmosis-std-derive" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ebdfd1bc8ed04db596e110c6baa9b174b04f6ed1ec22c666ddc5cb3fa91bd7" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "prost-types 0.11.9", + "quote", + "syn 1.0.109", +] [[package]] name = "paste" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "pkcs8" -version = "0.9.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +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 1.2.0", + "thiserror", +] + [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", - "prost-derive 0.9.0", + "prost-derive 0.11.9", ] [[package]] name = "prost" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", - "prost-derive 0.11.9", + "prost-derive 0.12.3", ] [[package]] name = "prost-derive" -version = "0.9.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 1.0.109", @@ -1102,15 +1078,15 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", - "itertools", + "itertools 0.11.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.53", ] [[package]] @@ -1122,6 +1098,15 @@ dependencies = [ "prost 0.11.9", ] +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost 0.12.3", +] + [[package]] name = "protobuf" version = "2.28.0" @@ -1133,11 +1118,10 @@ dependencies = [ [[package]] name = "protobuf" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +checksum = "58678a64de2fced2bdec6bca052a6716a0efe692d6e3f53d1bda6a1def64cfc0" dependencies = [ - "bytes", "once_cell", "protobuf-support", "thiserror", @@ -1145,18 +1129,18 @@ dependencies = [ [[package]] name = "protobuf-support" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +checksum = "e1ed294a835b0f30810e13616b1cd34943c6d1e84a8f3b0dcfe466d256c3e7e7" dependencies = [ "thiserror", ] [[package]] name = "quote" -version = "1.0.29" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1178,26 +1162,31 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "crypto-bigint", "hmac", - "zeroize", + "subtle", ] [[package]] -name = "ryu" +name = "rustversion" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "schemars" -version = "0.8.12" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "dyn-clone", "schemars_derive", @@ -1207,9 +1196,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.12" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ "proc-macro2", "quote", @@ -1219,9 +1208,9 @@ dependencies = [ [[package]] name = "sec1" -version = "0.3.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", @@ -1233,19 +1222,28 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.166" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-cw-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75d32da6b8ed758b7d850b6c3c08f1d7df51a4df3cb201296e63e34a78e99d4" +dependencies = [ + "serde", +] + [[package]] name = "serde-json-wasm" version = "0.4.1" @@ -1257,31 +1255,40 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-json-wasm" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05da0d153dd4595bdffd5099dc0e9ce425b205ee648eb93437ff7302af8c9a5" dependencies = [ "serde", ] [[package]] name = "serde_bytes" -version = "0.11.11" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a16be4fe5320ade08736447e3198294a5ea9a6d44dde6f35f0a5e06859c427a" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.166" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.53", ] [[package]] @@ -1297,9 +1304,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -1321,9 +1328,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -1332,9 +1339,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", "rand_core 0.6.4", @@ -1361,11 +1368,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "speedate" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "242f76c50fd18cbf098607090ade73a08d39cfd84ea835f3796a2c855223b19b" +dependencies = [ + "strum", + "strum_macros", +] + [[package]] name = "spki" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -1377,6 +1394,28 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.53", +] + [[package]] name = "subtle" version = "2.5.0" @@ -1405,9 +1444,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ "proc-macro2", "quote", @@ -1425,7 +1464,7 @@ dependencies = [ "num-derive", "num-traits", "prost 0.11.9", - "prost-types", + "prost-types 0.11.9", "serde", "serde_bytes", "subtle-encoding", @@ -1434,16 +1473,16 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.27.0" +version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5895470f28c530f8ae8c4071bf8190304ce00bd131d25e81730453124a3375c" +checksum = "b797dd3d2beaaee91d2f065e7bdf239dc8d80bba4a183a288bc1279dd5a69a1e" dependencies = [ "bytes", "flex-error", "num-derive", "num-traits", - "prost 0.11.9", - "prost-types", + "prost 0.12.3", + "prost-types 0.12.3", "serde", "serde_bytes", "subtle-encoding", @@ -1452,22 +1491,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.41" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c16a64ba9387ef3fdae4f9c1a7f07a0997fce91985c0336f1ddc1822b3b37802" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.41" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d14928354b01c4d6a4f0e549069adef399a284e7995c7ccca94e8a07a5346c59" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.53", ] [[package]] @@ -1489,9 +1528,9 @@ checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uint" @@ -1507,9 +1546,421 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "unit-tests" +version = "0.1.0" +dependencies = [ + "astroport", + "astroport-factory", + "astroport-native-coin-registry", + "astroport-pair", + "astroport-pair-concentrated", + "astroport-pair-stable", + "astroport-token", + "astroport-whitelist", + "colored", + "const_format", + "cosmos-sdk-proto 0.14.0", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-utils", + "cw-multi-test", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw1-whitelist", + "cw20", + "cw20-base", + "neutron-sdk", + "osmosis-std 0.13.2", + "prost 0.11.9", + "prost-types 0.11.9", + "sha2 0.10.8", + "valence-astroport-liquid-pooler", + "valence-clock", + "valence-covenant-single-party-pol", + "valence-covenant-swap", + "valence-covenant-two-party-pol", + "valence-ibc-forwarder", + "valence-interchain-router", + "valence-native-router", + "valence-native-splitter", + "valence-osmo-liquid-pooler", + "valence-outpost-osmo-liquid-pooler", + "valence-remote-chain-splitter", + "valence-single-party-pol-holder", + "valence-stride-liquid-staker", + "valence-swap-holder", + "valence-two-party-pol-holder", +] + +[[package]] +name = "valence-astroport-liquid-pooler" +version = "1.0.0" +dependencies = [ + "astroport", + "bech32", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20", + "neutron-sdk", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", + "valence-clock", +] + +[[package]] +name = "valence-clock" +version = "1.0.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "cw-fifo", + "cw-multi-test", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "neutron-sdk", + "serde", + "thiserror", + "valence-clock-tester", +] + +[[package]] +name = "valence-clock-tester" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "thiserror", +] + +[[package]] +name = "valence-covenant-single-party-pol" +version = "1.0.0" +dependencies = [ + "astroport", + "bech32", + "cosmos-sdk-proto 0.14.0", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "neutron-sdk", + "prost 0.11.9", + "prost-types 0.11.9", + "schemars", + "serde", + "serde-json-wasm 0.4.1", + "sha2 0.10.8", + "thiserror", + "valence-astroport-liquid-pooler", + "valence-clock", + "valence-ibc-forwarder", + "valence-interchain-router", + "valence-osmo-liquid-pooler", + "valence-remote-chain-splitter", + "valence-single-party-pol-holder", + "valence-stride-liquid-staker", +] + +[[package]] +name = "valence-covenant-swap" +version = "1.0.0" +dependencies = [ + "bech32", + "cosmos-sdk-proto 0.14.0", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "neutron-sdk", + "prost 0.11.9", + "prost-types 0.11.9", + "schemars", + "serde", + "serde-json-wasm 0.4.1", + "sha2 0.10.8", + "thiserror", + "valence-clock", + "valence-ibc-forwarder", + "valence-interchain-router", + "valence-native-router", + "valence-native-splitter", + "valence-swap-holder", +] + +[[package]] +name = "valence-covenant-two-party-pol" +version = "1.0.0" +dependencies = [ + "astroport", + "bech32", + "cosmos-sdk-proto 0.14.0", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "neutron-sdk", + "prost 0.11.9", + "prost-types 0.11.9", + "schemars", + "serde", + "serde-json-wasm 0.4.1", + "sha2 0.10.8", + "thiserror", + "valence-astroport-liquid-pooler", + "valence-clock", + "valence-ibc-forwarder", + "valence-interchain-router", + "valence-native-router", + "valence-osmo-liquid-pooler", + "valence-two-party-pol-holder", +] + +[[package]] +name = "valence-ibc-forwarder" +version = "1.0.0" +dependencies = [ + "bech32", + "cosmos-sdk-proto 0.14.0", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "neutron-sdk", + "prost 0.11.9", + "prost-types 0.11.9", + "schemars", + "serde", + "serde-json-wasm 0.4.1", + "sha2 0.10.8", + "thiserror", + "valence-clock", +] + +[[package]] +name = "valence-interchain-router" +version = "1.0.0" +dependencies = [ + "anyhow", + "bech32", + "cosmos-sdk-proto 0.14.0", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-multi-test", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "neutron-sdk", + "prost 0.11.9", + "prost-types 0.11.9", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", + "valence-clock", +] + +[[package]] +name = "valence-native-router" +version = "1.0.0" +dependencies = [ + "bech32", + "cosmos-sdk-proto 0.14.0", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "neutron-sdk", + "prost 0.11.9", + "prost-types 0.11.9", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", + "valence-clock", +] + +[[package]] +name = "valence-native-splitter" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "schemars", + "serde", + "thiserror", + "valence-clock", +] + +[[package]] +name = "valence-osmo-liquid-pooler" +version = "1.0.0" +dependencies = [ + "bech32", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20", + "neutron-sdk", + "osmosis-std 0.20.1", + "polytone", + "prost 0.11.9", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", + "valence-clock", + "valence-outpost-osmo-liquid-pooler", +] + +[[package]] +name = "valence-outpost-osmo-liquid-pooler" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "osmosis-std 0.13.2", + "prost 0.11.9", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "valence-remote-chain-splitter" +version = "1.0.0" +dependencies = [ + "cosmos-sdk-proto 0.14.0", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "neutron-sdk", + "schemars", + "serde", + "serde-json-wasm 0.4.1", + "thiserror", + "valence-clock", +] + +[[package]] +name = "valence-single-party-pol-holder" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "serde", + "thiserror", +] + +[[package]] +name = "valence-stride-liquid-staker" +version = "1.0.0" +dependencies = [ + "cosmos-sdk-proto 0.14.0", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "neutron-sdk", + "schemars", + "serde", + "serde-json-wasm 0.4.1", + "thiserror", + "valence-clock", +] + +[[package]] +name = "valence-swap-holder" +version = "1.0.0" +dependencies = [ + "cosmos-sdk-proto 0.14.0", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "neutron-sdk", + "serde", + "thiserror", + "valence-clock", +] + +[[package]] +name = "valence-two-party-pol-holder" +version = "1.0.0" +dependencies = [ + "astroport", + "cosmwasm-schema", + "cosmwasm-std", + "covenant-macros", + "covenant-utils", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20", + "schemars", + "serde", + "thiserror", + "valence-clock", +] [[package]] name = "version_check" @@ -1523,8 +1974,96 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index 8eb74d29..23f2a9a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,15 +2,16 @@ members = [ "packages/*", "contracts/*", + "unit-tests/", ] +resolver = "2" [workspace.package] -edition = "2021" -license = "Apache-2.0" -version = "1.0.0" -repository = "https://github.com/timewave-computer/covenants/stride-covenant" - -rust-version = "1.66" +edition = "2021" +license = "BSD-3" +version = "1.0.0" +repository = "https://github.com/timewave-computer/covenants" +# rust-version = "1.71.0" [workspace.metadata.scripts] optimize = """docker run --rm -v "$(pwd)":/code \ @@ -31,42 +32,75 @@ panic = 'abort' rpath = false [workspace.dependencies] -covenant-depositor = { path = "contracts/depositor" } -covenant-lp = { path = "contracts/lper" } -covenant-clock = { path = "contracts/clock" } -covenant-clock-tester = { path = "contracts/clock-tester" } -covenant-ls = { path = "contracts/ls" } -covenant-covenant = { path = "contracts/covenant" } -covenant-holder = { path = "contracts/holder" } -# packages -clock-derive = { path = "packages/clock-derive" } -cw-fifo = { path = "packages/cw-fifo" } -covenant-clock-derive = { path = "packages/clock-derive" } +valence-clock = { path = "contracts/clock" } +valence-clock-tester = { path = "contracts/clock-tester" } +valence-ibc-forwarder = { path = "contracts/ibc-forwarder" } +valence-remote-chain-splitter = { path = "contracts/remote-chain-splitter" } +valence-native-splitter = { path = "contracts/native-splitter" } +valence-swap-holder = { path = "contracts/swap-holder" } +valence-covenant-swap = { path = "contracts/swap-covenant" } +valence-interchain-router = { path = "contracts/interchain-router" } +valence-two-party-pol-holder = { path = "contracts/two-party-pol-holder" } +valence-covenant-two-party-pol = { path = "contracts/two-party-pol-covenant" } +valence-astroport-liquid-pooler = { path = "contracts/astroport-liquid-pooler" } +valence-native-router = { path = "contracts/native-router" } +valence-osmo-liquid-pooler = { path = "contracts/osmo-liquid-pooler" } +valence-outpost-osmo-liquid-pooler = { path = "contracts/outpost-osmo-liquid-pooler" } +valence-single-party-pol-holder = { path = "contracts/single-party-pol-holder" } +valence-covenant-single-party-pol = { path = "contracts/single-party-pol-covenant" } +valence-stride-liquid-staker = { path = "contracts/stride-liquid-staker" } +# packages +polytone = "1.0.0" +clock-derive = { path = "packages/clock-derive" } +cw-fifo = { path = "packages/cw-fifo" } +covenant-macros = { path = "packages/covenant-macros" } +covenant-utils = { path = "packages/covenant-utils" } # the sha2 version here is the same as the one used by # cosmwasm-std. when bumping cosmwasm-std, this should also be # updated. to find cosmwasm_std's sha function: # ```cargo tree --package cosmwasm-std``` -sha2 = "0.10.6" -neutron-sdk = { git = "https://github.com/neutron-org/neutron-sdk", default-features = false } +sha2 = "0.10.8" +neutron-sdk = { git = "https://github.com/neutron-org/neutron-sdk", tag = "v0.8.0" } cosmos-sdk-proto = { version = "0.14.0", default-features = false } -protobuf = { version = "3.2.0", features = ["with-bytes"] } -serde-json-wasm = { version = "0.4.1" } -base64 = "0.13.0" -prost = "0.11" -astroport = "2.8.0" -prost-types = "0.11" -bech32 = "0.9.0" -cosmwasm-schema = "1.2.1" -cosmwasm-std = { version = "1.2.4", features = ["ibc3"] } -cw-storage-plus = "1.0.1" -cw-utils = "1.0.1" +protobuf = { version = "3.2.0", features = ["with-bytes"] } +serde-json-wasm = { version = "0.4.1" } +base64 = "0.13.0" +prost = "0.11" +prost-types = "0.11" +bech32 = "0.9.0" +cosmwasm-schema = "1.5.0" + +cosmwasm-std = { version = "1.5.4", features = [ + "ibc3", + "cosmwasm_1_1", + "cosmwasm_1_2", +] } + +cw-storage-plus = "1.2.0" +cw-utils = "1.0.3" +getrandom = { version = "0.2", features = ["js"] } cw2 = "1.0.1" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } -thiserror = "1.0.31" -schemars = "0.8.10" +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +thiserror = "1.0.31" +schemars = "0.8.10" +cw20 = { version = "0.15.1" } +cw20-base = { version = "0.15.1" } +proc-macro2 = "1" +quote = "1" +syn = "1" -# dev-dependencies -cw-multi-test = "0.16.2" -anyhow = { version = "1.0.51" } +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", rev = "700f66d" } +# dev-dependencies +cw-multi-test = { git = "https://github.com/Art3miX/cw-multi-test", branch = "main", features = ["cosmwasm_1_2"] } +anyhow = { version = "1.0.51" } +cw1-whitelist = "0.15" +astroport-token = { git = "https://github.com/astroport-fi/astroport-core.git", rev = "700f66d" } +astroport-whitelist = { git = "https://github.com/astroport-fi/astroport-core.git", rev = "700f66d" } +astroport-factory = { git = "https://github.com/astroport-fi/astroport-core.git", rev = "700f66d" } +astroport-native-coin-registry = { git = "https://github.com/astroport-fi/astroport-core.git", rev = "700f66d" } +astroport-pair-stable = { git = "https://github.com/astroport-fi/astroport-core.git", rev = "700f66d" } +astroport-pair-concentrated = { git = "https://github.com/astroport-fi/astroport-core.git", rev = "700f66d" } +astroport-pair = { git = "https://github.com/astroport-fi/astroport-core.git", rev = "700f66d" } +unit-tests = { path = "unit-tests" } diff --git a/LICENSE b/LICENSE index 261eeb9e..d6456956 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..0358cdb5 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,2 @@ +allow-unwrap-in-tests = true +allow-expect-in-tests = true diff --git a/contracts/covenant/.cargo/config b/contracts/astroport-liquid-pooler/.cargo/config similarity index 69% rename from contracts/covenant/.cargo/config rename to contracts/astroport-liquid-pooler/.cargo/config index d54cc015..6a6f2852 100644 --- a/contracts/covenant/.cargo/config +++ b/contracts/astroport-liquid-pooler/.cargo/config @@ -1,3 +1,3 @@ [alias] wasm = "build --release --lib --target wasm32-unknown-unknown" -schema = "run --example schema" \ No newline at end of file +schema = "run --bin schema" \ No newline at end of file diff --git a/contracts/astroport-liquid-pooler/Cargo.toml b/contracts/astroport-liquid-pooler/Cargo.toml new file mode 100644 index 00000000..8325b8f3 --- /dev/null +++ b/contracts/astroport-liquid-pooler/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "valence-astroport-liquid-pooler" +authors = ["benskey bekauz@protonmail.com"] +description = "Astroport liquid pooler contract for covenants" +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } +edition = { workspace = true } +# rust-version = { workspace = true } + +exclude = ["contract.wasm", "hash.txt"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +covenant-macros = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } + +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +# the sha2 version here is the same as the one used by +# cosmwasm-std. when bumping cosmwasm-std, this should also be +# updated. to find cosmwasm_std's sha function: +# ```cargo tree --package cosmwasm-std``` +sha2 = { workspace = true } +neutron-sdk = { workspace = true } +schemars = { workspace = true } +bech32 = { workspace = true } +astroport = { workspace = true } +cw20 = { workspace = true } +covenant-utils = { workspace = true } diff --git a/contracts/astroport-liquid-pooler/README.md b/contracts/astroport-liquid-pooler/README.md new file mode 100644 index 00000000..f87d7176 --- /dev/null +++ b/contracts/astroport-liquid-pooler/README.md @@ -0,0 +1,29 @@ +# astroport liquid pooler + +Contract responsible for providing liquidity to a specified pool. + +## Instantiation + +The following parameters are expected to instantiate the liquid pooler: + +`pool_address` - address of the liquidity pool we wish to interact with + +`clock_address` - address of the authorized clock contract to receive ticks from + +`slippage_tolerance` - optional parameter to specify the acceptable slippage tolerance for providing liquidity + +`assets` - TODO + +`single_side_lp_limits` - TODO + +`expected_pool_ratio` - the price at which we expect to provide liquidity at + +`acceptable_pool_ratio_delta` - the acceptable deviation from the expected price above + +`pair_type` - the expected pair type of the pool we wish to enter. used for validation of cases where pool migrates. + +## Flow + +After instantiation, liquid pooler continuously attempts to provide liquidity to the specified pool. +If possible, double sided liquidity is provided. If it is not, liquid pooler attempts to provide single-sided liquidity. +If neither are possible, nothing happens until the next tick is received, at which point it retries. diff --git a/contracts/lper/schema/covenant-lp.json b/contracts/astroport-liquid-pooler/schema/valence-astroport-liquid-pooler.json similarity index 64% rename from contracts/lper/schema/covenant-lp.json rename to contracts/astroport-liquid-pooler/schema/valence-astroport-liquid-pooler.json index bd6a54e3..e1859b3c 100644 --- a/contracts/lper/schema/covenant-lp.json +++ b/contracts/astroport-liquid-pooler/schema/valence-astroport-liquid-pooler.json @@ -1,5 +1,5 @@ { - "contract_name": "covenant-lp", + "contract_name": "valence-astroport-liquid-pooler", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { @@ -7,43 +7,33 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "allowed_return_delta", "assets", "clock_address", - "expected_ls_token_amount", - "expected_native_token_amount", "holder_address", + "pair_type", "pool_address", + "pool_price_config", "single_side_lp_limits" ], "properties": { - "allowed_return_delta": { - "$ref": "#/definitions/Uint128" - }, "assets": { "$ref": "#/definitions/AssetData" }, - "autostake": { - "type": [ - "boolean", - "null" - ] - }, "clock_address": { "type": "string" }, - "expected_ls_token_amount": { - "$ref": "#/definitions/Uint128" - }, - "expected_native_token_amount": { - "$ref": "#/definitions/Uint128" - }, "holder_address": { "type": "string" }, + "pair_type": { + "$ref": "#/definitions/PairType" + }, "pool_address": { "type": "string" }, + "pool_price_config": { + "$ref": "#/definitions/PoolPriceConfig" + }, "single_side_lp_limits": { "$ref": "#/definitions/SingleSideLpLimits" }, @@ -61,17 +51,17 @@ "additionalProperties": false, "definitions": { "AssetData": { - "description": "holds the native and ls asset denoms relevant for providing liquidity.", + "description": "holds the both asset denoms relevant for providing liquidity", "type": "object", "required": [ - "ls_asset_denom", - "native_asset_denom" + "asset_a_denom", + "asset_b_denom" ], "properties": { - "ls_asset_denom": { + "asset_a_denom": { "type": "string" }, - "native_asset_denom": { + "asset_b_denom": { "type": "string" } }, @@ -81,18 +71,81 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", + "type": "object", + "required": [ + "stable" + ], + "properties": { + "stable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "PoolPriceConfig": { + "description": "config for the pool price expectations upon covenant instantiation", + "type": "object", + "required": [ + "acceptable_price_spread", + "expected_spot_price" + ], + "properties": { + "acceptable_price_spread": { + "$ref": "#/definitions/Decimal" + }, + "expected_spot_price": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, "SingleSideLpLimits": { "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", "type": "object", "required": [ - "ls_asset_limit", - "native_asset_limit" + "asset_a_limit", + "asset_b_limit" ], "properties": { - "ls_asset_limit": { + "asset_a_limit": { "$ref": "#/definitions/Uint128" }, - "native_asset_limit": { + "asset_b_limit": { "$ref": "#/definitions/Uint128" } }, @@ -121,8 +174,40 @@ } }, "additionalProperties": false + }, + { + "description": "Tells the LPer to withdraw his position Should only be called by the holder of the covenant", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "properties": { + "percentage": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } - ] + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } }, "query": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -131,10 +216,10 @@ { "type": "object", "required": [ - "clock_address" + "contract_state" ], "properties": { - "clock_address": { + "contract_state": { "type": "object", "additionalProperties": false } @@ -144,10 +229,10 @@ { "type": "object", "required": [ - "contract_state" + "holder_address" ], "properties": { - "contract_state": { + "holder_address": { "type": "object", "additionalProperties": false } @@ -157,10 +242,10 @@ { "type": "object", "required": [ - "holder_address" + "lp_config" ], "properties": { - "holder_address": { + "lp_config": { "type": "object", "additionalProperties": false } @@ -170,10 +255,10 @@ { "type": "object", "required": [ - "assets" + "provided_liquidity_info" ], "properties": { - "assets": { + "provided_liquidity_info": { "type": "object", "additionalProperties": false } @@ -181,12 +266,27 @@ "additionalProperties": false }, { + "description": "Returns the associated clock address authorized to submit ticks", "type": "object", "required": [ - "lp_config" + "clock_address" ], "properties": { - "lp_config": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the address a contract expects to receive funds to", + "type": "object", + "required": [ + "deposit_address" + ], + "properties": { + "deposit_address": { "type": "object", "additionalProperties": false } @@ -208,16 +308,6 @@ "update_config": { "type": "object", "properties": { - "assets": { - "anyOf": [ - { - "$ref": "#/definitions/AssetData" - }, - { - "type": "null" - } - ] - }, "clock_addr": { "type": [ "string", @@ -278,17 +368,17 @@ "type": "string" }, "AssetData": { - "description": "holds the native and ls asset denoms relevant for providing liquidity.", + "description": "holds the both asset denoms relevant for providing liquidity", "type": "object", "required": [ - "ls_asset_denom", - "native_asset_denom" + "asset_a_denom", + "asset_b_denom" ], "properties": { - "ls_asset_denom": { + "asset_a_denom": { "type": "string" }, - "native_asset_denom": { + "asset_b_denom": { "type": "string" } }, @@ -302,44 +392,53 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "DecimalRange": { + "type": "object", + "required": [ + "max", + "min" + ], + "properties": { + "max": { + "$ref": "#/definitions/Decimal" + }, + "min": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, "LpConfig": { "type": "object", "required": [ - "allowed_return_delta", - "expected_ls_token_amount", - "expected_native_token_amount", + "asset_data", + "expected_pool_ratio_range", + "pair_type", "pool_address", "single_side_lp_limits" ], "properties": { - "allowed_return_delta": { - "description": "accepted return amount fluctuation that gets applied to EXPECTED_LS_TOKEN_AMOUNT", + "asset_data": { + "description": "denoms of both parties", "allOf": [ { - "$ref": "#/definitions/Uint128" + "$ref": "#/definitions/AssetData" } ] }, - "autostake": { - "description": "boolean flag for enabling autostaking of LP tokens upon liquidity provisioning", - "type": [ - "boolean", - "null" - ] - }, - "expected_ls_token_amount": { - "description": "stride redemption rate is variable so we set the expected ls token amount", + "expected_pool_ratio_range": { + "description": "expected price range", "allOf": [ { - "$ref": "#/definitions/Uint128" + "$ref": "#/definitions/DecimalRange" } ] }, - "expected_native_token_amount": { - "description": "the native token amount we expect to be funded with", + "pair_type": { + "description": "pair type specified in the covenant", "allOf": [ { - "$ref": "#/definitions/Uint128" + "$ref": "#/definitions/PairType" } ] }, @@ -352,7 +451,7 @@ ] }, "single_side_lp_limits": { - "description": "amounts of native and ls tokens we consider ok to single-side lp", + "description": "amounts of both tokens we consider ok to single-side lp", "allOf": [ { "$ref": "#/definitions/SingleSideLpLimits" @@ -373,393 +472,77 @@ }, "additionalProperties": false }, - "SingleSideLpLimits": { - "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", - "type": "object", - "required": [ - "ls_asset_limit", - "native_asset_limit" - ], - "properties": { - "ls_asset_limit": { - "$ref": "#/definitions/Uint128" - }, - "native_asset_limit": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "sudo": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "SudoMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "response" - ], - "properties": { - "response": { - "type": "object", - "required": [ - "data", - "request" - ], - "properties": { - "data": { - "$ref": "#/definitions/Binary" - }, - "request": { - "$ref": "#/definitions/RequestPacket" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "error" - ], - "properties": { - "error": { + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", "type": "object", "required": [ - "details", - "request" + "xyk" ], "properties": { - "details": { - "type": "string" - }, - "request": { - "$ref": "#/definitions/RequestPacket" + "xyk": { + "type": "object", + "additionalProperties": false } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "timeout" - ], - "properties": { - "timeout": { + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", "type": "object", "required": [ - "request" + "stable" ], "properties": { - "request": { - "$ref": "#/definitions/RequestPacket" + "stable": { + "type": "object", + "additionalProperties": false } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "open_ack" - ], - "properties": { - "open_ack": { + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", "type": "object", "required": [ - "channel_id", - "counterparty_channel_id", - "counterparty_version", - "port_id" + "custom" ], "properties": { - "channel_id": { - "type": "string" - }, - "counterparty_channel_id": { - "type": "string" - }, - "counterparty_version": { - "type": "string" - }, - "port_id": { + "custom": { "type": "string" } - } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { + "SingleSideLpLimits": { + "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", "type": "object", "required": [ - "tx_query_result" + "asset_a_limit", + "asset_b_limit" ], "properties": { - "tx_query_result": { - "type": "object", - "required": [ - "data", - "height", - "query_id" - ], - "properties": { - "data": { - "$ref": "#/definitions/Binary" - }, - "height": { - "$ref": "#/definitions/Height" - }, - "query_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } + "asset_a_limit": { + "$ref": "#/definitions/Uint128" + }, + "asset_b_limit": { + "$ref": "#/definitions/Uint128" } }, "additionalProperties": false }, - { - "type": "object", - "required": [ - "kv_query_result" - ], - "properties": { - "kv_query_result": { - "type": "object", - "required": [ - "query_id" - ], - "properties": { - "query_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" - }, - "Height": { - "type": "object", - "properties": { - "revision_height": { - "description": "*height** is a height of remote chain", - "default": 0, - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "revision_number": { - "description": "the revision that the client is currently on", - "default": 0, - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "RequestPacket": { - "type": "object", - "properties": { - "data": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "destination_channel": { - "type": [ - "string", - "null" - ] - }, - "destination_port": { - "type": [ - "string", - "null" - ] - }, - "sequence": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "source_channel": { - "type": [ - "string", - "null" - ] - }, - "source_port": { - "type": [ - "string", - "null" - ] - }, - "timeout_height": { - "anyOf": [ - { - "$ref": "#/definitions/RequestPacketTimeoutHeight" - }, - { - "type": "null" - } - ] - }, - "timeout_timestamp": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - }, - "RequestPacketTimeoutHeight": { - "type": "object", - "properties": { - "revision_height": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "revision_number": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } } } }, + "sudo": null, "responses": { - "assets": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_Asset", - "type": "array", - "items": { - "$ref": "#/definitions/Asset" - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Asset": { - "description": "This enum describes a Terra asset (native or CW20).", - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "description": "A token amount", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "info": { - "description": "Information about an asset stored in a [`AssetInfo`] struct", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", - "oneOf": [ - { - "description": "Non-native Token", - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Native token", - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, "clock_address": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Addr", @@ -775,6 +558,14 @@ "instantiated" ] }, + "deposit_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, "holder_address": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Addr", @@ -786,41 +577,34 @@ "title": "LpConfig", "type": "object", "required": [ - "allowed_return_delta", - "expected_ls_token_amount", - "expected_native_token_amount", + "asset_data", + "expected_pool_ratio_range", + "pair_type", "pool_address", "single_side_lp_limits" ], "properties": { - "allowed_return_delta": { - "description": "accepted return amount fluctuation that gets applied to EXPECTED_LS_TOKEN_AMOUNT", + "asset_data": { + "description": "denoms of both parties", "allOf": [ { - "$ref": "#/definitions/Uint128" + "$ref": "#/definitions/AssetData" } ] }, - "autostake": { - "description": "boolean flag for enabling autostaking of LP tokens upon liquidity provisioning", - "type": [ - "boolean", - "null" - ] - }, - "expected_ls_token_amount": { - "description": "stride redemption rate is variable so we set the expected ls token amount", + "expected_pool_ratio_range": { + "description": "expected price range", "allOf": [ { - "$ref": "#/definitions/Uint128" + "$ref": "#/definitions/DecimalRange" } ] }, - "expected_native_token_amount": { - "description": "the native token amount we expect to be funded with", + "pair_type": { + "description": "pair type specified in the covenant", "allOf": [ { - "$ref": "#/definitions/Uint128" + "$ref": "#/definitions/PairType" } ] }, @@ -833,7 +617,7 @@ ] }, "single_side_lp_limits": { - "description": "amounts of native and ls tokens we consider ok to single-side lp", + "description": "amounts of both tokens we consider ok to single-side lp", "allOf": [ { "$ref": "#/definitions/SingleSideLpLimits" @@ -858,22 +642,101 @@ "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" }, + "AssetData": { + "description": "holds the both asset denoms relevant for providing liquidity", + "type": "object", + "required": [ + "asset_a_denom", + "asset_b_denom" + ], + "properties": { + "asset_a_denom": { + "type": "string" + }, + "asset_b_denom": { + "type": "string" + } + }, + "additionalProperties": false + }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "DecimalRange": { + "type": "object", + "required": [ + "max", + "min" + ], + "properties": { + "max": { + "$ref": "#/definitions/Decimal" + }, + "min": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", + "type": "object", + "required": [ + "stable" + ], + "properties": { + "stable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, "SingleSideLpLimits": { "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", "type": "object", "required": [ - "ls_asset_limit", - "native_asset_limit" + "asset_a_limit", + "asset_b_limit" ], "properties": { - "ls_asset_limit": { + "asset_a_limit": { "$ref": "#/definitions/Uint128" }, - "native_asset_limit": { + "asset_b_limit": { "$ref": "#/definitions/Uint128" } }, @@ -884,6 +747,46 @@ "type": "string" } } + }, + "provided_liquidity_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProvidedLiquidityInfo", + "description": "keeps track of provided asset liquidities in `Uint128`.", + "type": "object", + "required": [ + "provided_coin_a", + "provided_coin_b" + ], + "properties": { + "provided_coin_a": { + "$ref": "#/definitions/Coin" + }, + "provided_coin_b": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } } } } diff --git a/contracts/lper/examples/schema.rs b/contracts/astroport-liquid-pooler/src/bin/schema.rs similarity index 58% rename from contracts/lper/examples/schema.rs rename to contracts/astroport-liquid-pooler/src/bin/schema.rs index a67239d4..7ca81516 100644 --- a/contracts/lper/examples/schema.rs +++ b/contracts/astroport-liquid-pooler/src/bin/schema.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::write_api; -use covenant_lp::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use neutron_sdk::sudo::msg::SudoMsg; +use valence_astroport_liquid_pooler::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { @@ -8,6 +7,5 @@ fn main() { execute: ExecuteMsg, query: QueryMsg, migrate: MigrateMsg, - sudo: SudoMsg, } } diff --git a/contracts/astroport-liquid-pooler/src/contract.rs b/contracts/astroport-liquid-pooler/src/contract.rs new file mode 100644 index 00000000..8d07294f --- /dev/null +++ b/contracts/astroport-liquid-pooler/src/contract.rs @@ -0,0 +1,624 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + coin, ensure, to_json_binary, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, + MessageInfo, Reply, Response, StdError, StdResult, SubMsg, SubMsgResult, Uint128, WasmMsg, +}; +use covenant_utils::{astroport::query_astro_pool_token, withdraw_lp_helper::WithdrawLPMsgs}; +use cw2::set_contract_version; +use valence_clock::helpers::{enqueue_msg, verify_clock}; + +use astroport::{ + asset::{Asset, PairInfo}, + factory::PairType, + pair::{Cw20HookMsg, ExecuteMsg::ProvideLiquidity, PoolResponse, SimulationResponse}, + DecimalCheckedOps, +}; +use cw20::Cw20ExecuteMsg; + +use crate::{ + error::ContractError, + msg::{ + ContractState, DecimalRange, ExecuteMsg, InstantiateMsg, LpConfig, MigrateMsg, + ProvidedLiquidityInfo, QueryMsg, + }, + state::{HOLDER_ADDRESS, LP_CONFIG, PROVIDED_LIQUIDITY_INFO}, +}; + +use neutron_sdk::NeutronResult; + +use crate::state::{CLOCK_ADDRESS, CONTRACT_STATE}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +const DOUBLE_SIDED_REPLY_ID: u64 = 321u64; +const SINGLE_SIDED_REPLY_ID: u64 = 322u64; +const SWAP_REPLY_ID: u64 = 323u64; + +#[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)?; + + // validate the contract addresses + let clock_addr = deps.api.addr_validate(&msg.clock_address)?; + let pool_addr = deps.api.addr_validate(&msg.pool_address)?; + let holder_addr = deps.api.addr_validate(&msg.holder_address)?; + + // validate that the pool did not migrate to a new pair type + let pool_response: PairInfo = deps + .querier + .query_wasm_smart(pool_addr.to_string(), &astroport::pair::QueryMsg::Pair {})?; + + ensure!( + pool_response.pair_type.eq(&msg.pair_type), + ContractError::PairTypeMismatch {} + ); + + // contract starts at Instantiated state + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + + // store the relevant module addresses + CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; + + HOLDER_ADDRESS.save(deps.storage, &holder_addr)?; + + let decimal_range = DecimalRange::try_from( + msg.pool_price_config.expected_spot_price, + msg.pool_price_config.acceptable_price_spread, + )?; + + let lp_config = LpConfig { + pool_address: pool_addr, + single_side_lp_limits: msg.single_side_lp_limits, + slippage_tolerance: msg.slippage_tolerance, + expected_pool_ratio_range: decimal_range, + pair_type: msg.pair_type, + asset_data: msg.assets, + }; + LP_CONFIG.save(deps.storage, &lp_config)?; + + // we begin with no liquidity provided + PROVIDED_LIQUIDITY_INFO.save( + deps.storage, + &ProvidedLiquidityInfo { + provided_coin_a: coin(0, lp_config.asset_data.asset_a_denom.as_str()), + provided_coin_b: coin(0, lp_config.asset_data.asset_b_denom.as_str()), + }, + )?; + + Ok(Response::default() + .add_message(enqueue_msg(clock_addr.as_str())?) + .add_attribute("method", "lp_instantiate") + .add_attribute("clock_addr", clock_addr) + .add_attributes(lp_config.to_response_attributes())) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Tick {} => try_tick(deps, env, info), + ExecuteMsg::Withdraw { percentage } => try_withdraw(deps, env, info, percentage), + } +} + +fn try_withdraw( + deps: DepsMut, + env: Env, + info: MessageInfo, + percent: Option, +) -> Result { + let percent = percent.unwrap_or(Decimal::one()); + ensure!( + percent > Decimal::zero() && percent <= Decimal::one(), + ContractError::WithdrawPercentageRangeError {} + ); + + let holder_addr = HOLDER_ADDRESS.load(deps.storage)?; + ensure!(info.sender == holder_addr, ContractError::NotHolder {}); + + // Query LP position of the LPer + let lp_config = LP_CONFIG.load(deps.storage)?; + let lp_token_info = query_astro_pool_token( + deps.querier, + lp_config.pool_address.to_string(), + env.contract.address.to_string(), + )?; + + // if no lp tokens are available, we attempt to withdraw any available denoms + if lp_token_info.balance_response.balance.is_zero() { + let asset_a_bal = deps.querier.query_balance( + env.contract.address.to_string(), + lp_config.asset_data.asset_a_denom.as_str(), + )?; + let asset_b_bal = deps.querier.query_balance( + env.contract.address.to_string(), + lp_config.asset_data.asset_b_denom.as_str(), + )?; + + let mut funds = vec![]; + + if !asset_a_bal.amount.is_zero() { + funds.push(asset_a_bal); + } + + if !asset_b_bal.amount.is_zero() { + funds.push(asset_b_bal); + } + + ensure!(!funds.is_empty(), ContractError::NothingToWithdraw {}); + + return Ok(Response::default().add_message(WasmMsg::Execute { + contract_addr: holder_addr.to_string(), + msg: to_json_binary(&WithdrawLPMsgs::Distribute {})?, + funds, + })); + } + + // If percentage is 100%, use the whole balance + // If percentage is less than 100%, calculate the percentage of share we want to withdraw + let withdraw_shares_amount = if percent == Decimal::one() { + lp_token_info.balance_response.balance + } else { + Decimal::from_atomics(lp_token_info.balance_response.balance, 0)? + .checked_mul(percent)? + .to_uint_floor() + }; + + // Clculate the withdrawn amount of A and B tokens from the shares we have + let withdrawn_coins = deps + .querier + .query_wasm_smart::>( + lp_config.pool_address.to_string(), + &astroport::pair::QueryMsg::Share { + amount: withdraw_shares_amount, + }, + )? + .iter() + .map(|asset| asset.to_coin()) + .collect::, _>>()?; + + // exit pool and withdraw funds with the shares calculated + let withdraw_liquidity_hook = &Cw20HookMsg::WithdrawLiquidity { assets: vec![] }; + let withdraw_msg = WasmMsg::Execute { + contract_addr: lp_token_info.pair_info.liquidity_token.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Send { + contract: lp_config.pool_address.to_string(), + amount: withdraw_shares_amount, + msg: to_json_binary(withdraw_liquidity_hook)?, + })?, + funds: vec![], + }; + + // send message to holder that we finished with the withdrawal + // with the funds we withdrew from the pool + let to_holder_msg = WasmMsg::Execute { + contract_addr: holder_addr.to_string(), + msg: to_json_binary(&WithdrawLPMsgs::Distribute {})?, + funds: withdrawn_coins, + }; + + Ok(Response::default() + .add_message(withdraw_msg) + .add_message(to_holder_msg)) +} + +/// attempts to advance the state machine. performs `info.sender` validation. +fn try_tick(deps: DepsMut, env: Env, info: MessageInfo) -> Result { + // Verify caller is the clock + verify_clock(&info.sender, &CLOCK_ADDRESS.load(deps.storage)?)?; + + let current_state = CONTRACT_STATE.load(deps.storage)?; + match current_state { + ContractState::Instantiated => try_lp(deps, env), + } +} + +/// method which attempts to provision liquidity to the pool. +/// if both desired asset balances are non-zero, double sided liquidity +/// is provided. +/// otherwise, single-sided liquidity provision is attempted. +fn try_lp(mut deps: DepsMut, env: Env) -> Result { + let lp_config = LP_CONFIG.load(deps.storage)?; + + let pool_response: PoolResponse = deps + .querier + .query_wasm_smart(&lp_config.pool_address, &astroport::pair::QueryMsg::Pool {})?; + + let (pool_token_a_bal, pool_token_b_bal) = get_pool_asset_amounts( + pool_response.assets, + lp_config.asset_data.asset_a_denom.as_str(), + lp_config.asset_data.asset_b_denom.as_str(), + )?; + + // `get_pool_asset_amounts` ensures that both a and b balances are non-zero so this is safe + let a_to_b_ratio = Decimal::from_ratio(pool_token_a_bal, pool_token_b_bal); + + // validate the current pool ratio against our expectations + lp_config + .expected_pool_ratio_range + .is_within_range(a_to_b_ratio)?; + + // first we query our own balances + let coin_a = deps.querier.query_balance( + env.contract.address.to_string(), + lp_config.asset_data.asset_a_denom.as_str(), + )?; + let coin_b = deps.querier.query_balance( + env.contract.address.to_string(), + lp_config.asset_data.asset_b_denom.as_str(), + )?; + let assets = lp_config + .asset_data + .to_asset_vec(coin_a.amount, coin_b.amount); + + // depending on available balances we attempt a different action: + match (coin_a.amount.is_zero(), coin_b.amount.is_zero()) { + // asset_b balance is non-zero, we attempt single-side + (true, false) => { + ensure!( + coin_b.amount <= lp_config.single_side_lp_limits.asset_b_limit, + ContractError::SingleSideLpLimitError {} + ); + + let single_sided_submsgs = + try_get_single_side_lp_submsg(deps.branch(), env, coin_b, assets, lp_config)?; + if !single_sided_submsgs.is_empty() { + return Ok(Response::default() + .add_submessages(single_sided_submsgs) + .add_attribute("method", "single_side_lp")); + } + } + // asset_a balance is non-zero, we attempt single-side + (false, true) => { + ensure!( + coin_a.amount <= lp_config.single_side_lp_limits.asset_a_limit, + ContractError::SingleSideLpLimitError {} + ); + let single_sided_submsgs = + try_get_single_side_lp_submsg(deps.branch(), env, coin_a, assets, lp_config)?; + if !single_sided_submsgs.is_empty() { + return Ok(Response::default() + .add_submessages(single_sided_submsgs) + .add_attribute("method", "single_side_lp")); + } + } + // both balances are non-zero, we attempt double-side + (false, false) => { + let double_sided_submsg = try_get_double_side_lp_submsg( + deps.branch(), + env, + (coin_a, coin_b), + a_to_b_ratio, + (pool_token_a_bal, pool_token_b_bal), + lp_config, + )?; + if let Some(msg) = double_sided_submsg { + return Ok(Response::default() + .add_submessage(msg) + .add_attribute("method", "double_side_lp")); + } + } + // both balances zero, no liquidity can be provisioned + _ => (), + } + + // if no message could be constructed, we keep waiting for funds + Ok(Response::default() + .add_attribute("method", "try_lp") + .add_attribute("status", "not enough funds")) +} + +/// attempts to get a double sided ProvideLiquidity submessage. +/// amounts here do not matter. as long as we have non-zero balances of both +/// a and b tokens, the maximum amount of liquidity is provided to maintain +/// the existing pool ratio. +fn try_get_double_side_lp_submsg( + deps: DepsMut, + env: Env, + (token_a, token_b): (Coin, Coin), + pool_token_ratio: Decimal, + (pool_token_a_bal, pool_token_b_bal): (Uint128, Uint128), + lp_config: LpConfig, +) -> Result, ContractError> { + // we thus find the required token amount to enter into the position using all available b tokens: + let required_token_a_amount = pool_token_ratio.checked_mul_uint128(token_b.amount)?; + + // depending on available balances we determine the highest amount + // of liquidity we can provide: + let (asset_a_double_sided, asset_b_double_sided) = if token_a.amount >= required_token_a_amount + { + // if we are able to satisfy the required amount, we do that: + // provide all b tokens along with required amount of a tokens + lp_config + .asset_data + .to_tuple(required_token_a_amount, token_b.amount) + } else { + // otherwise, our token a amount is insufficient to provide double + // sided liquidity using all of our b tokens. + // this means that we should provide all of our available a tokens, + // and as many b tokens as needed to satisfy the existing ratio + let ratio = Decimal::from_ratio(pool_token_b_bal, pool_token_a_bal); + lp_config + .asset_data + .to_tuple(token_a.amount, ratio.checked_mul_uint128(token_a.amount)?) + }; + + let a_coin = asset_a_double_sided.to_coin()?; + let b_coin = asset_b_double_sided.to_coin()?; + + // craft a ProvideLiquidity message with the determined assets + let double_sided_liq_msg = ProvideLiquidity { + assets: vec![asset_a_double_sided, asset_b_double_sided], + slippage_tolerance: lp_config.slippage_tolerance, + auto_stake: Some(false), + receiver: Some(env.contract.address.to_string()), + }; + + // update the provided amounts and leftover assets + PROVIDED_LIQUIDITY_INFO.update( + deps.storage, + |mut info: ProvidedLiquidityInfo| -> StdResult<_> { + info.provided_coin_b.amount = info.provided_coin_b.amount.checked_add(b_coin.amount)?; + info.provided_coin_a.amount = info.provided_coin_a.amount.checked_add(a_coin.amount)?; + Ok(info) + }, + )?; + + Ok(Some(SubMsg::reply_on_success( + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: lp_config.pool_address.to_string(), + msg: to_json_binary(&double_sided_liq_msg)?, + funds: vec![a_coin, b_coin], + }), + DOUBLE_SIDED_REPLY_ID, + ))) +} + +/// attempts to build a single sided `ProvideLiquidity` message. +/// pool ratio and single-side limit validations are performed by +/// the calling method. +fn try_get_single_side_lp_submsg( + deps: DepsMut, + env: Env, + coin: Coin, + mut assets: Vec, + lp_config: LpConfig, +) -> Result, ContractError> { + match lp_config.pair_type { + // xyk pools do not allow for automatic single-sided liquidity provision. + // we therefore perform a manual swap with 1/2 of the available denom, and execute + // two-sided lp provision with the resulting assets. + PairType::Xyk {} => { + // we halve the non-zero coin we have in order to swap it for the other denom. + // the halved coin amount here is the floor of the division result, + // so it is safe to assume that after the swap we will have at least + // the same amount of the offer asset left. + let halved_coin = Coin { + denom: coin.denom.clone(), + amount: coin.amount / Uint128::from(2u128), + }; + + let (offer_asset, offer_coin, mut ask_asset) = { + if assets[0].to_coin()?.denom == halved_coin.denom { + assets[0].amount = halved_coin.amount; + (assets[0].clone(), halved_coin, assets[1].clone()) + } else { + assets[1].amount = halved_coin.amount; + (assets[1].clone(), halved_coin, assets[0].clone()) + } + }; + + // we simulate a swap with 1/2 of the offer asset + let simulation: SimulationResponse = deps.querier.query_wasm_smart( + &lp_config.pool_address, + &astroport::pair::QueryMsg::Simulation { + offer_asset: offer_asset.clone(), + ask_asset_info: None, + }, + )?; + ask_asset.amount = simulation.return_amount; + let ask_coin = ask_asset.to_coin()?; + + let swap_wasm_msg: CosmosMsg = WasmMsg::Execute { + contract_addr: lp_config.pool_address.to_string(), + msg: to_json_binary(&astroport::pair::ExecuteMsg::Swap { + offer_asset: offer_asset.clone(), + max_spread: lp_config.slippage_tolerance, + belief_price: None, + to: None, + ask_asset_info: None, + })?, + funds: vec![offer_coin.clone()], + } + .into(); + + PROVIDED_LIQUIDITY_INFO.update(deps.storage, |mut info| -> StdResult<_> { + if offer_coin.denom == info.provided_coin_a.denom { + info.provided_coin_a.amount = + info.provided_coin_a.amount.checked_add(offer_coin.amount)?; + info.provided_coin_b.amount = + info.provided_coin_b.amount.checked_add(ask_coin.amount)?; + } else { + info.provided_coin_b.amount = + info.provided_coin_b.amount.checked_add(offer_coin.amount)?; + info.provided_coin_a.amount = + info.provided_coin_a.amount.checked_add(ask_coin.amount)?; + } + Ok(info) + })?; + + let provide_liquidity_msg: CosmosMsg = WasmMsg::Execute { + contract_addr: lp_config.pool_address.to_string(), + msg: to_json_binary(&ProvideLiquidity { + assets: vec![offer_asset, ask_asset], + slippage_tolerance: lp_config.slippage_tolerance, + auto_stake: Some(false), + receiver: Some(env.contract.address.to_string()), + })?, + funds: vec![offer_coin, ask_coin], + } + .into(); + let swap_submsg = SubMsg::reply_on_success(swap_wasm_msg, SWAP_REPLY_ID); + let provide_liquidity_submsg = + SubMsg::reply_on_success(provide_liquidity_msg, DOUBLE_SIDED_REPLY_ID); + + Ok(vec![swap_submsg, provide_liquidity_submsg]) + } + PairType::Stable {} | PairType::Custom(_) => { + // given one non-zero asset, we build the ProvideLiquidity message + let single_sided_liq_msg = ProvideLiquidity { + assets, + slippage_tolerance: lp_config.slippage_tolerance, + auto_stake: Some(false), + receiver: Some(env.contract.address.to_string()), + }; + + // update the provided liquidity info + PROVIDED_LIQUIDITY_INFO.update(deps.storage, |mut info| -> StdResult<_> { + if coin.denom == info.provided_coin_a.denom { + info.provided_coin_a.amount = + info.provided_coin_a.amount.checked_add(coin.amount)?; + } else { + info.provided_coin_b.amount = + info.provided_coin_b.amount.checked_add(coin.amount)?; + } + Ok(info) + })?; + + let submsg = SubMsg::reply_on_success( + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: lp_config.pool_address.to_string(), + msg: to_json_binary(&single_sided_liq_msg)?, + funds: vec![coin], + }), + SINGLE_SIDED_REPLY_ID, + ); + + Ok(vec![submsg]) + } + } +} + +/// filters out irrelevant balances and returns a and b token amounts +fn get_pool_asset_amounts( + assets: Vec, + a_denom: &str, + b_denom: &str, +) -> Result<(Uint128, Uint128), StdError> { + let (mut a_bal, mut b_bal) = (Uint128::zero(), Uint128::zero()); + + for asset in assets { + let coin = asset.to_coin()?; + if coin.denom == b_denom { + // found b balance + b_bal = coin.amount; + } else if coin.denom == a_denom { + // found a token balance + a_bal = coin.amount; + } + } + + if a_bal.is_zero() || b_bal.is_zero() { + return Err(StdError::generic_err("all pool assets must be non-zero")); + } + + Ok((a_bal, b_bal)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ClockAddress {} => Ok(to_json_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::ContractState {} => Ok(to_json_binary(&CONTRACT_STATE.may_load(deps.storage)?)?), + QueryMsg::HolderAddress {} => Ok(to_json_binary(&HOLDER_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::LpConfig {} => Ok(to_json_binary(&LP_CONFIG.may_load(deps.storage)?)?), + // the deposit address for LP module is the contract itself + QueryMsg::DepositAddress {} => { + Ok(to_json_binary(&Some(&env.contract.address.to_string()))?) + } + QueryMsg::ProvidedLiquidityInfo {} => Ok(to_json_binary( + &PROVIDED_LIQUIDITY_INFO.load(deps.storage)?, + )?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> NeutronResult { + match msg { + MigrateMsg::UpdateConfig { + clock_addr, + holder_address, + lp_config, + } => { + let mut response = Response::default().add_attribute("method", "update_config"); + + if let Some(clock_addr) = clock_addr { + CLOCK_ADDRESS.save(deps.storage, &deps.api.addr_validate(&clock_addr)?)?; + response = response.add_attribute("clock_addr", clock_addr); + } + + if let Some(holder_address) = holder_address { + HOLDER_ADDRESS.save(deps.storage, &deps.api.addr_validate(&holder_address)?)?; + response = response.add_attribute("holder_address", holder_address); + } + + if let Some(config) = lp_config { + // validate the address before storing it + deps.api.addr_validate(config.pool_address.as_str())?; + LP_CONFIG.save(deps.storage, &config)?; + response = response.add_attributes(config.to_response_attributes()); + } + + Ok(response) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result { + match msg.result { + SubMsgResult::Ok(_) => { + let response = Response::default().add_attribute("reply_id", msg.id.to_string()); + + match msg.id { + DOUBLE_SIDED_REPLY_ID => handle_double_sided_reply_id(response), + SINGLE_SIDED_REPLY_ID => handle_single_sided_reply_id(response), + SWAP_REPLY_ID => handle_swap_reply_id(response), + _ => Err(ContractError::from(StdError::generic_err(format!( + "unknown reply id: {}", + msg.id + )))), + } + } + SubMsgResult::Err(e) => Err(ContractError::from(StdError::generic_err(e))), + } +} + +fn handle_swap_reply_id(response: Response) -> Result { + Ok(response.add_attribute("method", "handle_swap_reply_id")) +} + +fn handle_double_sided_reply_id(response: Response) -> Result { + Ok(response.add_attribute("method", "handle_double_sided_reply_id")) +} + +fn handle_single_sided_reply_id(response: Response) -> Result { + Ok(response.add_attribute("method", "handle_single_sided_reply_id")) +} diff --git a/contracts/astroport-liquid-pooler/src/error.rs b/contracts/astroport-liquid-pooler/src/error.rs new file mode 100644 index 00000000..d002ea46 --- /dev/null +++ b/contracts/astroport-liquid-pooler/src/error.rs @@ -0,0 +1,54 @@ +use cosmwasm_std::{DecimalRangeExceeded, OverflowError, StdError}; +use neutron_sdk::NeutronError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error(transparent)] + NeutronError(#[from] NeutronError), + + #[error(transparent)] + OverflowError(#[from] OverflowError), + + #[error(transparent)] + DecimalRangeExceeded(#[from] DecimalRangeExceeded), + + #[error("Not clock")] + ClockVerificationError {}, + + #[error("Single side LP limit exceeded")] + SingleSideLpLimitError {}, + + #[error("Non zero balances for single side liquidity")] + SingleSideLpNonZeroBalanceError {}, + + #[error("Zero balance for double side liquidity")] + DoubleSideLpZeroBalanceError {}, + + #[error("Insufficient funds for double sided LP")] + DoubleSideLpLimitError {}, + + #[error("Incomplete pool assets")] + IncompletePoolAssets {}, + + #[error("Pool validation error")] + PoolValidationError {}, + + #[error("Price range error")] + PriceRangeError {}, + + #[error("Pair type mismatch")] + PairTypeMismatch {}, + + #[error("Only holder can withdraw the position")] + NotHolder {}, + + #[error("no covenant denom or lp tokens available")] + NothingToWithdraw {}, + + #[error("Withdraw percentage range must belong to range (0.0, 1.0]")] + WithdrawPercentageRangeError {}, +} diff --git a/contracts/astroport-liquid-pooler/src/lib.rs b/contracts/astroport-liquid-pooler/src/lib.rs new file mode 100644 index 00000000..0faea8f4 --- /dev/null +++ b/contracts/astroport-liquid-pooler/src/lib.rs @@ -0,0 +1,8 @@ +#![warn(clippy::unwrap_used, clippy::expect_used)] + +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/astroport-liquid-pooler/src/msg.rs b/contracts/astroport-liquid-pooler/src/msg.rs new file mode 100644 index 00000000..5af6c8a7 --- /dev/null +++ b/contracts/astroport-liquid-pooler/src/msg.rs @@ -0,0 +1,229 @@ +use astroport::{ + asset::{Asset, AssetInfo}, + factory::PairType, +}; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{ + ensure, to_json_binary, Addr, Attribute, Binary, Coin, Decimal, StdResult, Uint128, WasmMsg, +}; +use covenant_macros::{ + clocked, covenant_clock_address, covenant_deposit_address, covenant_lper_withdraw, +}; +use covenant_utils::{ + instantiate2_helper::Instantiate2HelperConfig, PoolPriceConfig, SingleSideLpLimits, +}; + +use crate::error::ContractError; + +#[cw_serde] +pub struct InstantiateMsg { + pub pool_address: String, + pub clock_address: String, + pub slippage_tolerance: Option, + pub assets: AssetData, + pub single_side_lp_limits: SingleSideLpLimits, + pub pool_price_config: PoolPriceConfig, + pub pair_type: PairType, + pub holder_address: String, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +#[cw_serde] +pub struct AstroportLiquidPoolerConfig { + pub pool_pair_type: PairType, + pub pool_address: String, + pub asset_a_denom: String, + pub asset_b_denom: String, + pub single_side_lp_limits: SingleSideLpLimits, +} + +impl AstroportLiquidPoolerConfig { + pub fn to_instantiate_msg( + &self, + clock_address: String, + holder_address: String, + pool_price_config: PoolPriceConfig, + ) -> InstantiateMsg { + InstantiateMsg { + pool_address: self.pool_address.to_string(), + clock_address, + single_side_lp_limits: self.single_side_lp_limits.clone(), + pool_price_config, + pair_type: self.pool_pair_type.clone(), + holder_address, + slippage_tolerance: None, + assets: AssetData { + asset_a_denom: self.asset_a_denom.to_string(), + asset_b_denom: self.asset_b_denom.to_string(), + }, + } + } +} + +#[cw_serde] +pub struct DecimalRange { + min: Decimal, + max: Decimal, +} + +impl DecimalRange { + pub fn try_from(mid: Decimal, delta: Decimal) -> Result { + Ok(DecimalRange { + min: mid.checked_sub(delta)?, + max: mid.checked_add(delta)?, + }) + } + + pub fn is_within_range(&self, value: Decimal) -> Result<(), ContractError> { + ensure!( + value >= self.min && value <= self.max, + ContractError::PriceRangeError {} + ); + Ok(()) + } +} + +#[cw_serde] +pub struct LpConfig { + /// address of the liquidity pool we plan to enter + pub pool_address: Addr, + /// denoms of both parties + pub asset_data: AssetData, + /// amounts of both tokens we consider ok to single-side lp + pub single_side_lp_limits: SingleSideLpLimits, + /// slippage tolerance parameter for liquidity provisioning + pub slippage_tolerance: Option, + /// expected price range + pub expected_pool_ratio_range: DecimalRange, + /// pair type specified in the covenant + pub pair_type: PairType, +} + +impl LpConfig { + pub fn to_response_attributes(self) -> Vec { + let slippage_tolerance = match self.slippage_tolerance { + Some(val) => val.to_string(), + None => "None".to_string(), + }; + vec![ + Attribute::new("pool_address", self.pool_address.to_string()), + Attribute::new( + "single_side_asset_a_limit", + self.single_side_lp_limits.asset_a_limit.to_string(), + ), + Attribute::new( + "single_side_asset_b_limit", + self.single_side_lp_limits.asset_b_limit.to_string(), + ), + Attribute::new("slippage_tolerance", slippage_tolerance), + Attribute::new("party_a_denom", self.asset_data.asset_a_denom), + Attribute::new("party_b_denom", self.asset_data.asset_b_denom), + ] + } +} + +/// holds the both asset denoms relevant for providing liquidity +#[cw_serde] +pub struct AssetData { + pub asset_a_denom: String, + pub asset_b_denom: String, +} + +impl AssetData { + pub fn to_asset_vec(&self, a_bal: Uint128, b_bal: Uint128) -> Vec { + vec![ + Asset { + info: AssetInfo::NativeToken { + denom: self.asset_a_denom.to_string(), + }, + amount: a_bal, + }, + Asset { + info: AssetInfo::NativeToken { + denom: self.asset_b_denom.to_string(), + }, + amount: b_bal, + }, + ] + } + + /// returns tuple of (asset_A, asset_B) + pub fn to_tuple(&self, a_bal: Uint128, b_bal: Uint128) -> (Asset, Asset) { + ( + Asset { + info: AssetInfo::NativeToken { + denom: self.asset_a_denom.to_string(), + }, + amount: a_bal, + }, + Asset { + info: AssetInfo::NativeToken { + denom: self.asset_b_denom.to_string(), + }, + amount: b_bal, + }, + ) + } +} + +#[clocked] +#[covenant_lper_withdraw] +#[cw_serde] +pub enum ExecuteMsg {} + +#[covenant_clock_address] +#[covenant_deposit_address] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(ContractState)] + ContractState {}, + #[returns(Addr)] + HolderAddress {}, + #[returns(LpConfig)] + LpConfig {}, + #[returns(ProvidedLiquidityInfo)] + ProvidedLiquidityInfo {}, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + holder_address: Option, + lp_config: Option>, + }, + UpdateCodeId { + data: Option, + }, +} + +/// keeps track of provided asset liquidities in `Uint128`. +#[cw_serde] +pub struct ProvidedLiquidityInfo { + pub provided_coin_a: Coin, + pub provided_coin_b: Coin, +} + +/// state of the LP state machine +#[cw_serde] +pub enum ContractState { + Instantiated, +} diff --git a/contracts/astroport-liquid-pooler/src/state.rs b/contracts/astroport-liquid-pooler/src/state.rs new file mode 100644 index 00000000..0952fde5 --- /dev/null +++ b/contracts/astroport-liquid-pooler/src/state.rs @@ -0,0 +1,19 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +use crate::msg::{ContractState, LpConfig, ProvidedLiquidityInfo}; + +/// contract state tracks the state machine progress +pub const CONTRACT_STATE: Item = Item::new("contract_state"); + +/// clock module address to verify the incoming ticks sender +pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); +/// holder module address to verify withdrawal requests +pub const HOLDER_ADDRESS: Item = Item::new("holder_address"); + +/// keeps track of both token amounts we provided to the pool +pub const PROVIDED_LIQUIDITY_INFO: Item = + Item::new("provided_liquidity_info"); + +/// configuration relevant to entering into an LP position +pub const LP_CONFIG: Item = Item::new("lp_config"); diff --git a/contracts/clock-tester/.cargo/config b/contracts/clock-tester/.cargo/config index d54cc015..5f6aa466 100644 --- a/contracts/clock-tester/.cargo/config +++ b/contracts/clock-tester/.cargo/config @@ -1,3 +1,3 @@ [alias] wasm = "build --release --lib --target wasm32-unknown-unknown" -schema = "run --example schema" \ No newline at end of file +schema = "run --bin schema" diff --git a/contracts/clock-tester/Cargo.toml b/contracts/clock-tester/Cargo.toml index 91cfb431..12ad33be 100644 --- a/contracts/clock-tester/Cargo.toml +++ b/contracts/clock-tester/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "covenant-clock-tester" +name = "valence-clock-tester" authors = ["ekez "] edition = { workspace = true } license = { workspace = true } -rust-version = { workspace = true } +# rust-version = { workspace = true } version = { workspace = true } [lib] @@ -17,7 +17,7 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } +cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } -cw2 = { workspace = true } -thiserror = { workspace = true } +cw2 = { workspace = true } +thiserror = { workspace = true } diff --git a/contracts/clock-tester/schema/covenant-clock-tester.json b/contracts/clock-tester/schema/covenant-clock-tester.json deleted file mode 100644 index 8d46e0c5..00000000 --- a/contracts/clock-tester/schema/covenant-clock-tester.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "contract_name": "covenant-clock-tester", - "contract_version": "1.0.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "mode" - ], - "properties": { - "mode": { - "$ref": "#/definitions/Mode" - } - }, - "additionalProperties": false, - "definitions": { - "Mode": { - "type": "string", - "enum": [ - "accept", - "error" - ] - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Receives a tick and processes it according to the current mode.", - "type": "object", - "required": [ - "tick" - ], - "properties": { - "tick": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "Gets the number of times the clock has received a tick and not errored in response.", - "type": "object", - "required": [ - "tick_count" - ], - "properties": { - "tick_count": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": null, - "sudo": null, - "responses": { - "tick_count": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Uint64", - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/clock-tester/src/contract.rs b/contracts/clock-tester/src/contract.rs index b165b7eb..cf907542 100644 --- a/contracts/clock-tester/src/contract.rs +++ b/contracts/clock-tester/src/contract.rs @@ -1,7 +1,7 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint64, + to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint64, }; use cw2::set_contract_version; @@ -9,7 +9,7 @@ use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, Mode, QueryMsg}; use crate::state::{MODE, TICK_COUNT}; -const CONTRACT_NAME: &str = "crates.io:covenant-clock-tester"; +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] @@ -48,6 +48,6 @@ pub fn execute( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::TickCount {} => to_binary(&Uint64::new(TICK_COUNT.load(deps.storage)?)), + QueryMsg::TickCount {} => to_json_binary(&Uint64::new(TICK_COUNT.load(deps.storage)?)), } } diff --git a/contracts/clock-tester/src/lib.rs b/contracts/clock-tester/src/lib.rs index f0e2fd34..a5abdbb0 100644 --- a/contracts/clock-tester/src/lib.rs +++ b/contracts/clock-tester/src/lib.rs @@ -2,6 +2,3 @@ pub mod contract; pub mod error; pub mod msg; pub mod state; - -#[cfg(test)] -mod tests; diff --git a/contracts/clock/.cargo/config b/contracts/clock/.cargo/config index d54cc015..5f6aa466 100644 --- a/contracts/clock/.cargo/config +++ b/contracts/clock/.cargo/config @@ -1,3 +1,3 @@ [alias] wasm = "build --release --lib --target wasm32-unknown-unknown" -schema = "run --example schema" \ No newline at end of file +schema = "run --bin schema" diff --git a/contracts/clock/Cargo.toml b/contracts/clock/Cargo.toml index 38a69104..5a061d3c 100644 --- a/contracts/clock/Cargo.toml +++ b/contracts/clock/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "covenant-clock" -authors = ["ekez "] +name = "valence-clock" +authors = ["ekez "] description = "A clock that advances the covenant state machine." -edition = { workspace = true } -license = { workspace = true } -rust-version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +# rust-version = { workspace = true } version = { workspace = true } [lib] @@ -18,16 +18,16 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -covenant-clock-derive = { workspace = true } -covenant-clock-tester = { workspace = true, features=["library"] } -cw-fifo = { workspace = true } +cosmwasm-std = { workspace = true } +covenant-macros = { workspace = true } +cw-fifo = { workspace = true } cw-storage-plus = { workspace = true } -cw2 = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } -neutron-sdk = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +neutron-sdk = { workspace = true } [dev-dependencies] -cw-multi-test = { workspace = true } -anyhow = { workspace = true } +cw-multi-test = { workspace = true } +anyhow = { workspace = true } +valence-clock-tester = { workspace = true, features = ["library"] } diff --git a/contracts/clock/schema/covenant-clock.json b/contracts/clock/schema/valence-clock.json similarity index 70% rename from contracts/clock/schema/covenant-clock.json rename to contracts/clock/schema/valence-clock.json index d99de29a..4683c9ed 100644 --- a/contracts/clock/schema/covenant-clock.json +++ b/contracts/clock/schema/valence-clock.json @@ -1,5 +1,5 @@ { - "contract_name": "covenant-clock", + "contract_name": "valence-clock", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { @@ -185,7 +185,130 @@ } ] }, - "migrate": null, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "description": "Pauses the clock. No `ExecuteMsg` messages will be executable until the clock is unpaused. Callable only if the clock is unpaused.", + "type": "object", + "required": [ + "pause" + ], + "properties": { + "pause": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Unpauses the clock. Callable only if the clock is paused.", + "type": "object", + "required": [ + "unpause" + ], + "properties": { + "unpause": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Updates the max gas allowed to be consumed by a tick. This should be no larger than 100_000 less the block max gas so as to save enough gas to process the tick's error.", + "type": "object", + "required": [ + "update_tick_max_gas" + ], + "properties": { + "update_tick_max_gas": { + "type": "object", + "required": [ + "new_value" + ], + "properties": { + "new_value": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "manage_whitelist" + ], + "properties": { + "manage_whitelist": { + "type": "object", + "properties": { + "add": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "remove": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, "sudo": null, "responses": { "is_queued": { diff --git a/contracts/holder/examples/schema.rs b/contracts/clock/src/bin/schema.rs similarity index 70% rename from contracts/holder/examples/schema.rs rename to contracts/clock/src/bin/schema.rs index de12d293..fc3a2aff 100644 --- a/contracts/holder/examples/schema.rs +++ b/contracts/clock/src/bin/schema.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::write_api; -use covenant_holder::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use valence_clock::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/clock/src/contract.rs b/contracts/clock/src/contract.rs index b19dc1df..0fb86603 100644 --- a/contracts/clock/src/contract.rs +++ b/contracts/clock/src/contract.rs @@ -1,21 +1,21 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, SubMsg, - Uint64, WasmMsg, + to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, + SubMsg, Uint64, WasmMsg, }; use cw2::set_contract_version; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use crate::state::{PAUSED, QUEUE, TICK_MAX_GAS_LIMIT, WHITELIST}; +use crate::state::{PAUSED, QUEUE, TICK_MAX_GAS, WHITELIST}; -const CONTRACT_NAME: &str = "crates.io:covenant-clock"; +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const MIN_TICK_GAS_LIMIT: Uint64 = Uint64::new(200_000); +pub const MIN_TICK_MAX_GAS: Uint64 = Uint64::new(200_000); pub const DEFAULT_TICK_MAX_GAS: Uint64 = Uint64::new(2_900_000); -pub const MAX_TICK_GAS_LIMIT: Uint64 = Uint64::new(3_000_000); +pub const MAX_TICK_MAX_GAS: Uint64 = Uint64::new(3_000_000); #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -24,27 +24,24 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> Result { - deps.api.debug("WASMDEBUG: clock instantiate"); - let tick_max_gas = if let Some(tick_max_gas) = msg.tick_max_gas { // at least MIN_MAX_GAS, at most the relayer limit - tick_max_gas.clamp(MIN_TICK_GAS_LIMIT, MAX_TICK_GAS_LIMIT) + tick_max_gas.max(MIN_TICK_MAX_GAS).min(MAX_TICK_MAX_GAS) } else { // todo: find some reasonable default value DEFAULT_TICK_MAX_GAS }; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - TICK_MAX_GAS_LIMIT.save(deps.storage, &tick_max_gas)?; + TICK_MAX_GAS.save(deps.storage, &tick_max_gas)?; PAUSED.save(deps.storage, &false)?; - // Verify vector are addresses - // We don't verify its a contract because it might not be instantiated yet let whitelist: Vec = msg .whitelist .iter() .map(|addr| deps.api.addr_validate(addr)) .collect::>>()?; + WHITELIST.save(deps.storage, &whitelist)?; Ok(Response::default() @@ -75,12 +72,12 @@ pub fn execute( SubMsg::reply_on_error( WasmMsg::Execute { contract_addr: receiver.to_string(), - msg: to_binary(&ExecuteMsg::Tick {})?, + msg: to_json_binary(&ExecuteMsg::Tick {})?, funds: vec![], }, 0, ) - .with_gas_limit(TICK_MAX_GAS_LIMIT.load(deps.storage)?.u64()), + .with_gas_limit(TICK_MAX_GAS.load(deps.storage)?.u64()), )) } else { Ok(Response::default() @@ -123,9 +120,9 @@ pub fn execute( pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::IsQueued { address } => { - to_binary(&QUEUE.has(deps.storage, Addr::unchecked(address))) + to_json_binary(&QUEUE.has(deps.storage, Addr::unchecked(address))) } - QueryMsg::Queue { start_after, limit } => to_binary( + QueryMsg::Queue { start_after, limit } => to_json_binary( &QUEUE.query_queue( deps.storage, start_after @@ -134,9 +131,9 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { limit, )?, ), - QueryMsg::TickMaxGas {} => to_binary(&TICK_MAX_GAS_LIMIT.load(deps.storage)?), - QueryMsg::Paused {} => to_binary(&PAUSED.load(deps.storage)?), - QueryMsg::Whitelist {} => to_binary(&WHITELIST.load(deps.storage)?), + QueryMsg::TickMaxGas {} => to_json_binary(&TICK_MAX_GAS.load(deps.storage)?), + QueryMsg::Paused {} => to_json_binary(&PAUSED.load(deps.storage)?), + QueryMsg::Whitelist {} => to_json_binary(&WHITELIST.load(deps.storage)?), } } @@ -180,10 +177,10 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result StdResult { Ok(WasmMsg::Execute { contract_addr: addr.to_string(), - msg: to_binary(&Enqueue {})?, + msg: to_json_binary(&Enqueue {})?, funds: vec![], }) } @@ -16,7 +16,7 @@ pub fn enqueue_msg(addr: &str) -> StdResult { pub fn dequeue_msg(addr: &str) -> StdResult { Ok(WasmMsg::Execute { contract_addr: addr.to_string(), - msg: to_binary(&Dequeue {})?, + msg: to_json_binary(&Dequeue {})?, funds: vec![], }) } diff --git a/contracts/clock/src/lib.rs b/contracts/clock/src/lib.rs index 94756908..21a8b737 100644 --- a/contracts/clock/src/lib.rs +++ b/contracts/clock/src/lib.rs @@ -3,6 +3,7 @@ pub mod error; pub mod helpers; pub mod msg; pub mod state; +pub mod test_helpers; #[cfg(test)] -mod suite_tests; +pub mod suite_tests; diff --git a/contracts/clock/src/msg.rs b/contracts/clock/src/msg.rs index 3855bd87..dce74dfb 100644 --- a/contracts/clock/src/msg.rs +++ b/contracts/clock/src/msg.rs @@ -1,9 +1,12 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Addr; +use cosmwasm_std::to_json_binary; use cosmwasm_std::Binary; +use cosmwasm_std::StdResult; use cosmwasm_std::Uint64; -use covenant_clock_derive::clocked; +use cosmwasm_std::WasmMsg; +use covenant_macros::clocked; #[cw_serde] pub struct InstantiateMsg { @@ -22,28 +25,22 @@ pub struct InstantiateMsg { pub whitelist: Vec, } -#[cw_serde] -pub struct PresetClockFields { - pub tick_max_gas: Option, - pub whitelist: Vec, - pub clock_code: u64, - pub label: String, -} - -impl PresetClockFields { - pub fn to_instantiate_msg(self) -> InstantiateMsg { - let tick_max_gas = if let Some(tmg) = self.tick_max_gas { - // double the 100k minimum seems fair - tmg.min(Uint64::new(200000)) - } else { - // todo: find some reasonable default value - Uint64::new(2900000) - }; - - InstantiateMsg { - tick_max_gas: Some(tick_max_gas), - whitelist: self.whitelist, - } +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + code_id: u64, + salt: Binary, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt, + }) } } diff --git a/contracts/clock/src/state.rs b/contracts/clock/src/state.rs index 1a666db8..208d1a72 100644 --- a/contracts/clock/src/state.rs +++ b/contracts/clock/src/state.rs @@ -4,5 +4,5 @@ use cw_storage_plus::Item; pub(crate) const QUEUE: FIFOQueue = FIFOQueue::new("front", "back", "count"); pub(crate) const PAUSED: Item = Item::new("paused"); -pub(crate) const TICK_MAX_GAS_LIMIT: Item = Item::new("tmg"); +pub(crate) const TICK_MAX_GAS: Item = Item::new("tmg"); pub(crate) const WHITELIST: Item> = Item::new("whitelist"); diff --git a/contracts/clock/src/suite_tests/mod.rs b/contracts/clock/src/suite_tests/mod.rs index 83328d90..6b42309c 100644 --- a/contracts/clock/src/suite_tests/mod.rs +++ b/contracts/clock/src/suite_tests/mod.rs @@ -27,9 +27,9 @@ pub fn clock_contract() -> Box> { pub fn clock_tester_contract() -> Box> { let contract = ContractWrapper::new( - covenant_clock_tester::contract::execute, - covenant_clock_tester::contract::instantiate, - covenant_clock_tester::contract::query, + valence_clock_tester::contract::execute, + valence_clock_tester::contract::instantiate, + valence_clock_tester::contract::query, ); Box::new(contract) } diff --git a/contracts/clock/src/suite_tests/suite.rs b/contracts/clock/src/suite_tests/suite.rs index 5603cc0b..1c3036af 100644 --- a/contracts/clock/src/suite_tests/suite.rs +++ b/contracts/clock/src/suite_tests/suite.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Addr, Uint64}; -use covenant_clock_tester::msg::Mode; use cw_multi_test::{App, AppResponse, Executor}; +use valence_clock_tester::msg::Mode; use crate::{ contract::DEFAULT_TICK_MAX_GAS, @@ -79,7 +79,7 @@ impl SuiteBuilder { .instantiate_contract( code_id, Addr::unchecked(ADMIN), - &covenant_clock_tester::msg::InstantiateMsg { mode }, + &valence_clock_tester::msg::InstantiateMsg { mode }, &[], "clock-tester", Some(ADMIN.to_string()), @@ -213,7 +213,7 @@ impl Suite { .wrap() .query_wasm_smart( tester.to_string(), - &covenant_clock_tester::msg::QueryMsg::TickCount {}, + &valence_clock_tester::msg::QueryMsg::TickCount {}, ) .unwrap(); res.u64() diff --git a/contracts/clock/src/suite_tests/tests.rs b/contracts/clock/src/suite_tests/tests.rs index 81563c13..a5c884a5 100644 --- a/contracts/clock/src/suite_tests/tests.rs +++ b/contracts/clock/src/suite_tests/tests.rs @@ -1,7 +1,8 @@ -use cosmwasm_std::{Addr, Uint64}; -use covenant_clock_tester::msg::Mode; +use cosmwasm_std::{Addr, StdError, Uint64}; +use valence_clock_tester::msg::Mode; use crate::contract::DEFAULT_TICK_MAX_GAS; +use crate::error::ContractError; use super::is_error; use super::suite::SuiteBuilder; @@ -147,10 +148,13 @@ fn test_update_tick_max_gas() { // tests that dequeueing an address that is not in the queue results // in an error. #[test] -#[should_panic(expected = "u64 not found")] fn test_dequeue_nonexistant() { let mut suite = SuiteBuilder::default().build(); - suite.dequeue("nobody").unwrap(); + let err: ContractError = suite.dequeue("nobody").unwrap_err().downcast().unwrap(); + assert!(matches!( + err, + ContractError::Std(StdError::NotFound { kind: _ }) + )); } // the same tick receiver can not be in the queue more than once. @@ -188,7 +192,7 @@ fn test_whitelist() { assert_eq!(whitelist, vec![receiver]); } -// only whitelisted contracts can be enqueued. +// only contract addresses can be enqueued. #[test] fn test_enqueue_non_whitelisted() { let mut suite_builder = SuiteBuilder::default(); diff --git a/contracts/clock/src/test_helpers/helpers.rs b/contracts/clock/src/test_helpers/helpers.rs new file mode 100644 index 00000000..9dd60e1f --- /dev/null +++ b/contracts/clock/src/test_helpers/helpers.rs @@ -0,0 +1,45 @@ +use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use neutron_sdk::{ + bindings::{msg::NeutronMsg, query::NeutronQuery}, + NeutronResult, +}; + +use crate::msg::{InstantiateMsg, QueryMsg}; + +type ExecuteDeps<'a> = DepsMut<'a, NeutronQuery>; +type QueryDeps<'a> = Deps<'a, NeutronQuery>; + +pub fn mock_neutron_clock_instantiate( + _deps: ExecuteDeps, + _env: Env, + _info: MessageInfo, + _msg: InstantiateMsg, +) -> NeutronResult> { + Ok(Response::default()) +} + +pub fn mock_neutron_clock_execute( + _deps: ExecuteDeps, + _env: Env, + _info: MessageInfo, + msg: crate::msg::ExecuteMsg, +) -> NeutronResult> { + match msg { + crate::msg::ExecuteMsg::Enqueue {} => Ok(Response::default()), + crate::msg::ExecuteMsg::Dequeue {} => Ok(Response::default()), + crate::msg::ExecuteMsg::Tick {} => Ok(Response::default()), + } +} + +pub fn mock_neutron_clock_query(_deps: QueryDeps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::IsQueued { address: _ } => Ok(Binary::default()), + QueryMsg::Queue { + start_after: _, + limit: _, + } => Ok(Binary::default()), + QueryMsg::TickMaxGas {} => Ok(Binary::default()), + QueryMsg::Paused {} => Ok(Binary::default()), + QueryMsg::Whitelist {} => Ok(Binary::default()), + } +} diff --git a/contracts/clock/src/test_helpers/mod.rs b/contracts/clock/src/test_helpers/mod.rs new file mode 100644 index 00000000..1630fabc --- /dev/null +++ b/contracts/clock/src/test_helpers/mod.rs @@ -0,0 +1 @@ +pub mod helpers; diff --git a/contracts/covenant/schema/covenant-covenant.json b/contracts/covenant/schema/covenant-covenant.json deleted file mode 100644 index 3cf5513e..00000000 --- a/contracts/covenant/schema/covenant-covenant.json +++ /dev/null @@ -1,734 +0,0 @@ -{ - "contract_name": "covenant-covenant", - "contract_version": "1.0.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "label", - "pool_address", - "preset_clock_fields", - "preset_depositor_fields", - "preset_holder_fields", - "preset_ibc_fee", - "preset_lp_fields", - "preset_ls_fields", - "timeouts" - ], - "properties": { - "label": { - "description": "contract label for this specific covenant", - "type": "string" - }, - "pool_address": { - "description": "address of the liquidity pool we wish to interact with", - "type": "string" - }, - "preset_clock_fields": { - "description": "instantiation fields relevant to clock module known in advance", - "allOf": [ - { - "$ref": "#/definitions/PresetClockFields" - } - ] - }, - "preset_depositor_fields": { - "description": "instantiation fields relevant to depositor module known in advance", - "allOf": [ - { - "$ref": "#/definitions/PresetDepositorFields" - } - ] - }, - "preset_holder_fields": { - "description": "instantiation fields relevant to holder module known in advance", - "allOf": [ - { - "$ref": "#/definitions/PresetHolderFields" - } - ] - }, - "preset_ibc_fee": { - "description": "neutron relayer fee structure", - "allOf": [ - { - "$ref": "#/definitions/PresetIbcFee" - } - ] - }, - "preset_lp_fields": { - "description": "instantiation fields relevant to lp module known in advance", - "allOf": [ - { - "$ref": "#/definitions/PresetLpFields" - } - ] - }, - "preset_ls_fields": { - "description": "instantiation fields relevant to ls module known in advance", - "allOf": [ - { - "$ref": "#/definitions/PresetLsFields" - } - ] - }, - "timeouts": { - "description": "ibc transfer and ica timeouts passed down to relevant modules", - "allOf": [ - { - "$ref": "#/definitions/Timeouts" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "AssetData": { - "description": "holds the native and ls asset denoms relevant for providing liquidity.", - "type": "object", - "required": [ - "ls_asset_denom", - "native_asset_denom" - ], - "properties": { - "ls_asset_denom": { - "type": "string" - }, - "native_asset_denom": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "PresetClockFields": { - "type": "object", - "required": [ - "clock_code", - "label", - "whitelist" - ], - "properties": { - "clock_code": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "label": { - "type": "string" - }, - "tick_max_gas": { - "anyOf": [ - { - "$ref": "#/definitions/Uint64" - }, - { - "type": "null" - } - ] - }, - "whitelist": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "PresetDepositorFields": { - "type": "object", - "required": [ - "atom_receiver_amount", - "autopilot_format", - "depositor_code", - "gaia_neutron_ibc_transfer_channel_id", - "gaia_stride_ibc_transfer_channel_id", - "label", - "neutron_atom_ibc_denom", - "neutron_gaia_connection_id", - "st_atom_receiver_amount" - ], - "properties": { - "atom_receiver_amount": { - "$ref": "#/definitions/WeightedReceiverAmount" - }, - "autopilot_format": { - "type": "string" - }, - "depositor_code": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "gaia_neutron_ibc_transfer_channel_id": { - "type": "string" - }, - "gaia_stride_ibc_transfer_channel_id": { - "type": "string" - }, - "label": { - "type": "string" - }, - "neutron_atom_ibc_denom": { - "type": "string" - }, - "neutron_gaia_connection_id": { - "type": "string" - }, - "st_atom_receiver_amount": { - "$ref": "#/definitions/WeightedReceiverAmount" - } - }, - "additionalProperties": false - }, - "PresetHolderFields": { - "description": "Preset fields are set by the user when instantiating the covenant. use `to_instantiate_msg` implementation method to get `InstantiateMsg`.", - "type": "object", - "required": [ - "holder_code", - "label" - ], - "properties": { - "holder_code": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "label": { - "type": "string" - }, - "withdrawer": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "PresetIbcFee": { - "type": "object", - "required": [ - "ack_fee", - "timeout_fee" - ], - "properties": { - "ack_fee": { - "$ref": "#/definitions/Uint128" - }, - "timeout_fee": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "PresetLpFields": { - "description": "Defines fields relevant to LP module that are known prior to covenant being instantiated. Use `to_instantiate_msg` implemented method to obtain the `InstantiateMsg` by providing the non-deterministic fields.", - "type": "object", - "required": [ - "allowed_return_delta", - "assets", - "expected_ls_token_amount", - "expected_native_token_amount", - "label", - "lp_code" - ], - "properties": { - "allowed_return_delta": { - "description": "difference (both ways) we tolerate with regards to the `expected_ls_token_amount`", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "assets": { - "description": "denominations of native and ls assets", - "allOf": [ - { - "$ref": "#/definitions/AssetData" - } - ] - }, - "autostake": { - "description": "determines whether provided liquidity is automatically staked", - "type": [ - "boolean", - "null" - ] - }, - "expected_ls_token_amount": { - "description": "workaround for the current lack of stride redemption rate query. we set the expected amount of ls tokens we expect to receive for the relevant half of the native tokens we have", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "expected_native_token_amount": { - "description": "amount of native tokens we expect to receive from depositor", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "label": { - "description": "label for contract to be instantiated with", - "type": "string" - }, - "lp_code": { - "description": "lp contract code", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "single_side_lp_limits": { - "description": "limits (in `Uint128`) for single side liquidity provision. Defaults to 100 if none are provided.", - "anyOf": [ - { - "$ref": "#/definitions/SingleSideLpLimits" - }, - { - "type": "null" - } - ] - }, - "slippage_tolerance": { - "description": "slippage tolerance for providing liquidity", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - "PresetLsFields": { - "type": "object", - "required": [ - "label", - "ls_code", - "ls_denom", - "neutron_stride_ibc_connection_id", - "stride_neutron_ibc_transfer_channel_id" - ], - "properties": { - "label": { - "type": "string" - }, - "ls_code": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "ls_denom": { - "type": "string" - }, - "neutron_stride_ibc_connection_id": { - "type": "string" - }, - "stride_neutron_ibc_transfer_channel_id": { - "type": "string" - } - }, - "additionalProperties": false - }, - "SingleSideLpLimits": { - "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", - "type": "object", - "required": [ - "ls_asset_limit", - "native_asset_limit" - ], - "properties": { - "ls_asset_limit": { - "$ref": "#/definitions/Uint128" - }, - "native_asset_limit": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - }, - "Timeouts": { - "type": "object", - "required": [ - "ibc_transfer_timeout", - "ica_timeout" - ], - "properties": { - "ibc_transfer_timeout": { - "description": "ibc transfer timeout in seconds", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "ica_timeout": { - "description": "ica timeout in seconds", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - }, - "WeightedReceiverAmount": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "type": "string", - "enum": [] - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "depositor_address" - ], - "properties": { - "depositor_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "clock_address" - ], - "properties": { - "clock_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "lp_address" - ], - "properties": { - "lp_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "ls_address" - ], - "properties": { - "ls_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "holder_address" - ], - "properties": { - "holder_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "pool_address" - ], - "properties": { - "pool_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "migrate_contracts" - ], - "properties": { - "migrate_contracts": { - "type": "object", - "properties": { - "clock": { - "anyOf": [ - { - "$ref": "#/definitions/MigrateMsg" - }, - { - "type": "null" - } - ] - }, - "depositor": { - "anyOf": [ - { - "$ref": "#/definitions/MigrateMsg" - }, - { - "type": "null" - } - ] - }, - "holder": { - "anyOf": [ - { - "$ref": "#/definitions/MigrateMsg" - }, - { - "type": "null" - } - ] - }, - "lp": { - "anyOf": [ - { - "$ref": "#/definitions/MigrateMsg" - }, - { - "type": "null" - } - ] - }, - "ls": { - "anyOf": [ - { - "$ref": "#/definitions/MigrateMsg" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "MigrateMsg": { - "oneOf": [ - { - "description": "Pauses the clock. No `ExecuteMsg` messages will be executable until the clock is unpaused. Callable only if the clock is unpaused.", - "type": "object", - "required": [ - "pause" - ], - "properties": { - "pause": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Unpauses the clock. Callable only if the clock is paused.", - "type": "object", - "required": [ - "unpause" - ], - "properties": { - "unpause": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Updates the max gas allowed to be consumed by a tick. This should be no larger than 100_000 less the block max gas so as to save enough gas to process the tick's error.", - "type": "object", - "required": [ - "update_tick_max_gas" - ], - "properties": { - "update_tick_max_gas": { - "type": "object", - "required": [ - "new_value" - ], - "properties": { - "new_value": { - "$ref": "#/definitions/Uint64" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "update_code_id" - ], - "properties": { - "update_code_id": { - "type": "object", - "properties": { - "data": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "manage_whitelist" - ], - "properties": { - "manage_whitelist": { - "type": "object", - "properties": { - "add": { - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } - }, - "remove": { - "type": [ - "array", - "null" - ], - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "sudo": null, - "responses": { - "clock_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "depositor_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "holder_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "lp_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "ls_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "pool_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - } - } -} diff --git a/contracts/depositor/schema/covenant-depositor.json b/contracts/depositor/schema/covenant-depositor.json deleted file mode 100644 index 7633fa99..00000000 --- a/contracts/depositor/schema/covenant-depositor.json +++ /dev/null @@ -1,1071 +0,0 @@ -{ - "contract_name": "covenant-depositor", - "contract_version": "1.0.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "atom_receiver", - "autopilot_format", - "clock_address", - "gaia_neutron_ibc_transfer_channel_id", - "gaia_stride_ibc_transfer_channel_id", - "ibc_fee", - "ibc_transfer_timeout", - "ica_timeout", - "ls_address", - "neutron_atom_ibc_denom", - "neutron_gaia_connection_id", - "st_atom_receiver" - ], - "properties": { - "atom_receiver": { - "$ref": "#/definitions/WeightedReceiver" - }, - "autopilot_format": { - "description": "json formatted string meant to be used for one-click liquid staking on stride", - "type": "string" - }, - "clock_address": { - "description": "address for the clock. this contract verifies that only the clock can execute ticks", - "type": "string" - }, - "gaia_neutron_ibc_transfer_channel_id": { - "description": "ibc transfer channel on gaia for neutron this is used to ibc transfer uatom on gaia to the LP contract", - "type": "string" - }, - "gaia_stride_ibc_transfer_channel_id": { - "description": "ibc transfer channel on gaia for stride This is used to ibc transfer uatom on gaia to the ica on stride", - "type": "string" - }, - "ibc_fee": { - "description": "neutron requires fees to be set to refund relayers for submission of ack and timeout messages. recv_fee and ack_fee paid in untrn from this contract", - "allOf": [ - { - "$ref": "#/definitions/IbcFee" - } - ] - }, - "ibc_transfer_timeout": { - "description": "timeout in seconds. this is used to craft a timeout timestamp that will be attached to the IBC transfer message from the ICA on the host chain (gaia) to its destination. typically this timeout should be greater than the ICA timeout, otherwise if the ICA times out, the destination chain receiving the funds will also receive the IBC packet with an expired timestamp.", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "ica_timeout": { - "description": "time in seconds for ICA SubmitTX messages from neutron note that ICA uses ordered channels, a timeout implies channel closed. We can reopen the channel by reregistering the ICA with the same port id and connection id", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "ls_address": { - "description": "address of the liquid staker module that will be used to query for the ICA address on stride", - "type": "string" - }, - "neutron_atom_ibc_denom": { - "description": "ibc denom of uatom on neutron", - "type": "string" - }, - "neutron_gaia_connection_id": { - "description": "IBC connection ID on neutron for gaia We make an Interchain Account over this connection", - "type": "string" - }, - "st_atom_receiver": { - "description": "weighted receiver information used to determine where and how many funds should be sent from depositor", - "allOf": [ - { - "$ref": "#/definitions/WeightedReceiver" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "IbcFee": { - "description": "IbcFee defines struct for fees that refund the relayer for `SudoMsg` messages submission. Unused fee kind will be returned back to message sender. Please refer to these links for more information: IBC transaction structure - General mechanics of fee payments - ", - "type": "object", - "required": [ - "ack_fee", - "recv_fee", - "timeout_fee" - ], - "properties": { - "ack_fee": { - "description": "*ack_fee** is an amount of coins to refund relayer for submitting ack message for a particular IBC packet.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "recv_fee": { - "description": "**recv_fee** currently is used for compatibility with ICS-29 interface only and must be set to zero (i.e. 0untrn), because Neutron's fee module can't refund relayer for submission of Recv IBC packets due to compatibility with target chains.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "timeout_fee": { - "description": "*timeout_fee** amount of coins to refund relayer for submitting timeout message for a particular IBC packet.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - }, - "WeightedReceiver": { - "type": "object", - "required": [ - "address", - "amount" - ], - "properties": { - "address": { - "type": "string" - }, - "amount": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Wakes the state machine up. The caller should check the sender of the tick is the clock if they'd like to pause when the clock does.", - "type": "object", - "required": [ - "tick" - ], - "properties": { - "tick": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "st_atom_receiver" - ], - "properties": { - "st_atom_receiver": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "atom_receiver" - ], - "properties": { - "atom_receiver": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "clock_address" - ], - "properties": { - "clock_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "contract_state" - ], - "properties": { - "contract_state": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "depositor_interchain_account_address" - ], - "properties": { - "depositor_interchain_account_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "this query goes to neutron and get stored ICA with a specific query", - "type": "object", - "required": [ - "interchain_account_address" - ], - "properties": { - "interchain_account_address": { - "type": "object", - "required": [ - "connection_id", - "interchain_account_id" - ], - "properties": { - "connection_id": { - "type": "string" - }, - "interchain_account_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "interchain_account_address_from_contract" - ], - "properties": { - "interchain_account_address_from_contract": { - "type": "object", - "required": [ - "interchain_account_id" - ], - "properties": { - "interchain_account_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "acknowledgement_result" - ], - "properties": { - "acknowledgement_result": { - "type": "object", - "required": [ - "interchain_account_id", - "sequence_id" - ], - "properties": { - "interchain_account_id": { - "type": "string" - }, - "sequence_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "errors_queue" - ], - "properties": { - "errors_queue": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "autopilot_format" - ], - "properties": { - "autopilot_format": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "properties": { - "atom_receiver": { - "anyOf": [ - { - "$ref": "#/definitions/WeightedReceiver" - }, - { - "type": "null" - } - ] - }, - "autopilot_format": { - "type": [ - "string", - "null" - ] - }, - "clock_addr": { - "type": [ - "string", - "null" - ] - }, - "gaia_neutron_ibc_transfer_channel_id": { - "type": [ - "string", - "null" - ] - }, - "gaia_stride_ibc_transfer_channel_id": { - "type": [ - "string", - "null" - ] - }, - "ibc_config": { - "anyOf": [ - { - "$ref": "#/definitions/IbcConfig" - }, - { - "type": "null" - } - ] - }, - "ls_address": { - "type": [ - "string", - "null" - ] - }, - "neutron_gaia_connection_id": { - "type": [ - "string", - "null" - ] - }, - "st_atom_receiver": { - "anyOf": [ - { - "$ref": "#/definitions/WeightedReceiver" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "update_code_id" - ], - "properties": { - "update_code_id": { - "type": "object", - "properties": { - "data": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "IbcConfig": { - "type": "object", - "required": [ - "ibc_fee", - "ibc_transfer_timeout", - "ica_timeout" - ], - "properties": { - "ibc_fee": { - "$ref": "#/definitions/IbcFee" - }, - "ibc_transfer_timeout": { - "$ref": "#/definitions/Uint64" - }, - "ica_timeout": { - "$ref": "#/definitions/Uint64" - } - }, - "additionalProperties": false - }, - "IbcFee": { - "description": "IbcFee defines struct for fees that refund the relayer for `SudoMsg` messages submission. Unused fee kind will be returned back to message sender. Please refer to these links for more information: IBC transaction structure - General mechanics of fee payments - ", - "type": "object", - "required": [ - "ack_fee", - "recv_fee", - "timeout_fee" - ], - "properties": { - "ack_fee": { - "description": "*ack_fee** is an amount of coins to refund relayer for submitting ack message for a particular IBC packet.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "recv_fee": { - "description": "**recv_fee** currently is used for compatibility with ICS-29 interface only and must be set to zero (i.e. 0untrn), because Neutron's fee module can't refund relayer for submission of Recv IBC packets due to compatibility with target chains.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "timeout_fee": { - "description": "*timeout_fee** amount of coins to refund relayer for submitting timeout message for a particular IBC packet.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - }, - "WeightedReceiver": { - "type": "object", - "required": [ - "address", - "amount" - ], - "properties": { - "address": { - "type": "string" - }, - "amount": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false - } - } - }, - "sudo": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "SudoMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "response" - ], - "properties": { - "response": { - "type": "object", - "required": [ - "data", - "request" - ], - "properties": { - "data": { - "$ref": "#/definitions/Binary" - }, - "request": { - "$ref": "#/definitions/RequestPacket" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "error" - ], - "properties": { - "error": { - "type": "object", - "required": [ - "details", - "request" - ], - "properties": { - "details": { - "type": "string" - }, - "request": { - "$ref": "#/definitions/RequestPacket" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "timeout" - ], - "properties": { - "timeout": { - "type": "object", - "required": [ - "request" - ], - "properties": { - "request": { - "$ref": "#/definitions/RequestPacket" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "open_ack" - ], - "properties": { - "open_ack": { - "type": "object", - "required": [ - "channel_id", - "counterparty_channel_id", - "counterparty_version", - "port_id" - ], - "properties": { - "channel_id": { - "type": "string" - }, - "counterparty_channel_id": { - "type": "string" - }, - "counterparty_version": { - "type": "string" - }, - "port_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "tx_query_result" - ], - "properties": { - "tx_query_result": { - "type": "object", - "required": [ - "data", - "height", - "query_id" - ], - "properties": { - "data": { - "$ref": "#/definitions/Binary" - }, - "height": { - "$ref": "#/definitions/Height" - }, - "query_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "kv_query_result" - ], - "properties": { - "kv_query_result": { - "type": "object", - "required": [ - "query_id" - ], - "properties": { - "query_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Height": { - "type": "object", - "properties": { - "revision_height": { - "description": "*height** is a height of remote chain", - "default": 0, - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "revision_number": { - "description": "the revision that the client is currently on", - "default": 0, - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "RequestPacket": { - "type": "object", - "properties": { - "data": { - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "destination_channel": { - "type": [ - "string", - "null" - ] - }, - "destination_port": { - "type": [ - "string", - "null" - ] - }, - "sequence": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "source_channel": { - "type": [ - "string", - "null" - ] - }, - "source_port": { - "type": [ - "string", - "null" - ] - }, - "timeout_height": { - "anyOf": [ - { - "$ref": "#/definitions/RequestPacketTimeoutHeight" - }, - { - "type": "null" - } - ] - }, - "timeout_timestamp": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - }, - "RequestPacketTimeoutHeight": { - "type": "object", - "properties": { - "revision_height": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - }, - "revision_number": { - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - "responses": { - "acknowledgement_result": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_AcknowledgementResult", - "anyOf": [ - { - "$ref": "#/definitions/AcknowledgementResult" - }, - { - "type": "null" - } - ], - "definitions": { - "AcknowledgementResult": { - "description": "Serves for storing acknowledgement calls for interchain transactions", - "oneOf": [ - { - "description": "Success - Got success acknowledgement in sudo with array of message item types in it", - "type": "object", - "required": [ - "success" - ], - "properties": { - "success": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - { - "description": "Error - Got error acknowledgement in sudo with payload message in it and error details", - "type": "object", - "required": [ - "error" - ], - "properties": { - "error": { - "type": "array", - "items": [ - { - "type": "string" - }, - { - "type": "string" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "Timeout - Got timeout acknowledgement in sudo with payload message in it", - "type": "object", - "required": [ - "timeout" - ], - "properties": { - "timeout": { - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, - "atom_receiver": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "WeightedReceiver", - "type": "object", - "required": [ - "address", - "amount" - ], - "properties": { - "address": { - "type": "string" - }, - "amount": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "autopilot_format": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "String", - "type": "string" - }, - "clock_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "contract_state": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ContractState", - "oneOf": [ - { - "description": "Contract was instantiated, create ica", - "type": "string", - "enum": [ - "instantiated" - ] - }, - { - "description": "ICA was created, send native token to lper", - "type": "string", - "enum": [ - "i_c_a_created" - ] - }, - { - "description": "Verify native token was sent to lper and send ls msg", - "type": "string", - "enum": [ - "verify_native_token" - ] - }, - { - "description": "Verify the lper entered a position, if not try to resend ls msg again", - "type": "string", - "enum": [ - "verify_lp" - ] - }, - { - "description": "Depositor completed his mission.", - "type": "string", - "enum": [ - "complete" - ] - } - ] - }, - "depositor_interchain_account_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryInterchainAccountAddressResponse", - "type": "object", - "required": [ - "interchain_account_address" - ], - "properties": { - "interchain_account_address": { - "description": "*interchain_account_address** is a interchain account address on the remote chain", - "type": "string" - } - } - }, - "errors_queue": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_Tuple_of_Array_of_uint8_and_String", - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - { - "type": "string" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "interchain_account_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryInterchainAccountAddressResponse", - "type": "object", - "required": [ - "interchain_account_address" - ], - "properties": { - "interchain_account_address": { - "description": "*interchain_account_address** is a interchain account address on the remote chain", - "type": "string" - } - } - }, - "interchain_account_address_from_contract": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Tuple_of_String_and_String", - "type": "array", - "items": [ - { - "type": "string" - }, - { - "type": "string" - } - ], - "maxItems": 2, - "minItems": 2 - }, - "st_atom_receiver": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "WeightedReceiver", - "type": "object", - "required": [ - "address", - "amount" - ], - "properties": { - "address": { - "type": "string" - }, - "amount": { - "$ref": "#/definitions/Uint128" - } - }, - "additionalProperties": false, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - } - } -} diff --git a/contracts/depositor/.cargo/config b/contracts/ibc-forwarder/.cargo/config similarity index 69% rename from contracts/depositor/.cargo/config rename to contracts/ibc-forwarder/.cargo/config index d54cc015..5f6aa466 100644 --- a/contracts/depositor/.cargo/config +++ b/contracts/ibc-forwarder/.cargo/config @@ -1,3 +1,3 @@ [alias] wasm = "build --release --lib --target wasm32-unknown-unknown" -schema = "run --example schema" \ No newline at end of file +schema = "run --bin schema" diff --git a/contracts/ibc-forwarder/Cargo.toml b/contracts/ibc-forwarder/Cargo.toml new file mode 100644 index 00000000..f5c7ee0e --- /dev/null +++ b/contracts/ibc-forwarder/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "valence-ibc-forwarder" +edition = { workspace = true } +authors = ["benskey bekauz@protonmail.com"] +description = "IBC Forwarder module for covenants" +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } +# rust-version = { workspace = true } + +exclude = ["contract.wasm", "hash.txt"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +covenant-macros = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +sha2 = { workspace = true } +neutron-sdk = { workspace = true } +cosmos-sdk-proto = { workspace = true } +schemars = { workspace = true } +serde-json-wasm = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +bech32 = { workspace = true } +covenant-utils = { workspace = true } +cw-utils = { workspace = true } \ No newline at end of file diff --git a/contracts/ibc-forwarder/LICENSE b/contracts/ibc-forwarder/LICENSE new file mode 100644 index 00000000..0bdef96b --- /dev/null +++ b/contracts/ibc-forwarder/LICENSE @@ -0,0 +1,29 @@ +Copyright 2023 Timewave + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/contracts/ibc-forwarder/README.md b/contracts/ibc-forwarder/README.md new file mode 100644 index 00000000..1371e16a --- /dev/null +++ b/contracts/ibc-forwarder/README.md @@ -0,0 +1,26 @@ +# IBC Forwarder + +IBC Forwarders are contracts instantiated on neutron with the sole responsibility of +receiving funds to an ICA on a remote chain and forwarding them to another module. + +In addition to being aware of all IBC related information such as ICA & IBC transfer +timeouts and channel/connection-ids, forwarder needs to have a destination contract +address. + +The destination contract is used to perform a `DepositAddress {}` query, which will return an +`Option`. This gives us two cases: + +1. `None`, in which case IBC Forwarder does nothing and keeps waiting +1. `Addr`, which is then used as a destination address to forward the funds to + +While IBC Forwarder should remain agnostic to any underlying details of what the +deposit address is, a few examples of it may be: + +- another ICA address or an autopilot receiver string in case of Liquid Staker +- contract address itself in case of Liquid Pooler + +IBC Forwarder needs to receive funds in order to be able to forward them. To enable +that, we expose a `DepositAddress {}` query method. After instantiating its ICA, +forwarder can return that ICA address as its deposit address. Prior to ICA +instantiation the query should be returning `None`, indicating that it is not yet +ready to receive funds. diff --git a/contracts/ibc-forwarder/schema/valence-ibc-forwarder.json b/contracts/ibc-forwarder/schema/valence-ibc-forwarder.json new file mode 100644 index 00000000..ab06e457 --- /dev/null +++ b/contracts/ibc-forwarder/schema/valence-ibc-forwarder.json @@ -0,0 +1,478 @@ +{ + "contract_name": "valence-ibc-forwarder", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "amount", + "clock_address", + "denom", + "ibc_transfer_timeout", + "ica_timeout", + "next_contract", + "remote_chain_channel_id", + "remote_chain_connection_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "clock_address": { + "description": "address for the clock. this contract verifies that only the clock can execute ticks", + "type": "string" + }, + "denom": { + "type": "string" + }, + "fallback_address": { + "type": [ + "string", + "null" + ] + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds. this is used to craft a timeout timestamp that will be attached to the IBC transfer message from the ICA on the host chain to its destination. typically this timeout should be greater than the ICA timeout, otherwise if the ICA times out, the destination chain receiving the funds will also receive the IBC packet with an expired timestamp.", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "ica_timeout": { + "description": "time in seconds for ICA SubmitTX messages from neutron note that ICA uses ordered channels, a timeout implies channel closed. We can reopen the channel by reregistering the ICA with the same port id and connection id", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "next_contract": { + "description": "contract responsible for providing the address to forward the funds to", + "type": "string" + }, + "remote_chain_channel_id": { + "type": "string" + }, + "remote_chain_connection_id": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "distribute_fallback" + ], + "properties": { + "distribute_fallback": { + "type": "object", + "required": [ + "coins" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Wakes the state machine up. The caller should check the sender of the tick is the clock if they'd like to pause when the clock does.", + "type": "object", + "required": [ + "tick" + ], + "properties": { + "tick": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "contract_state" + ], + "properties": { + "contract_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "fallback_address" + ], + "properties": { + "fallback_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the address a contract expects to receive funds to", + "type": "object", + "required": [ + "deposit_address" + ], + "properties": { + "deposit_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated remote chain information", + "type": "object", + "required": [ + "remote_chain_info" + ], + "properties": { + "remote_chain_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated clock address authorized to submit ticks", + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated remote chain information", + "type": "object", + "required": [ + "ica_address" + ], + "properties": { + "ica_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "fallback_address": { + "anyOf": [ + { + "$ref": "#/definitions/FallbackAddressUpdateConfig" + }, + { + "type": "null" + } + ] + }, + "next_contract": { + "type": [ + "string", + "null" + ] + }, + "remote_chain_info": { + "anyOf": [ + { + "$ref": "#/definitions/RemoteChainInfo" + }, + { + "type": "null" + } + ] + }, + "transfer_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "FallbackAddressUpdateConfig": { + "oneOf": [ + { + "type": "object", + "required": [ + "explicit_address" + ], + "properties": { + "explicit_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "disable" + ], + "properties": { + "disable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "RemoteChainInfo": { + "type": "object", + "required": [ + "channel_id", + "connection_id", + "denom", + "ibc_transfer_timeout", + "ica_timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "connection_id": { + "description": "connection id from neutron to the remote chain on which we wish to open an ICA", + "type": "string" + }, + "denom": { + "type": "string" + }, + "ibc_transfer_timeout": { + "$ref": "#/definitions/Uint64" + }, + "ica_timeout": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "sudo": null, + "responses": { + "clock_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "contract_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractState", + "oneOf": [ + { + "description": "Contract was instantiated, ready create ica", + "type": "string", + "enum": [ + "instantiated" + ] + }, + { + "description": "ICA was created, funds are ready to be forwarded", + "type": "string", + "enum": [ + "ica_created" + ] + } + ] + }, + "deposit_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "fallback_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "ica_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "remote_chain_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RemoteChainInfo", + "type": "object", + "required": [ + "channel_id", + "connection_id", + "denom", + "ibc_transfer_timeout", + "ica_timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "connection_id": { + "description": "connection id from neutron to the remote chain on which we wish to open an ICA", + "type": "string" + }, + "denom": { + "type": "string" + }, + "ibc_transfer_timeout": { + "$ref": "#/definitions/Uint64" + }, + "ica_timeout": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false, + "definitions": { + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/contracts/clock/examples/schema.rs b/contracts/ibc-forwarder/src/bin/schema.rs similarity index 58% rename from contracts/clock/examples/schema.rs rename to contracts/ibc-forwarder/src/bin/schema.rs index defe5be2..3a82c8b4 100644 --- a/contracts/clock/examples/schema.rs +++ b/contracts/ibc-forwarder/src/bin/schema.rs @@ -1,11 +1,11 @@ use cosmwasm_schema::write_api; - -use covenant_clock::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use valence_ibc_forwarder::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { instantiate: InstantiateMsg, execute: ExecuteMsg, query: QueryMsg, + migrate: MigrateMsg, } } diff --git a/contracts/ibc-forwarder/src/contract.rs b/contracts/ibc-forwarder/src/contract.rs new file mode 100644 index 00000000..2e94dee7 --- /dev/null +++ b/contracts/ibc-forwarder/src/contract.rs @@ -0,0 +1,447 @@ +use std::collections::BTreeSet; + +use cosmos_sdk_proto::cosmos::bank::v1beta1::{Input, MsgMultiSend, Output}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, + StdResult, Uint128, +}; +use covenant_utils::{ + ica::{ + get_ica, msg_with_sudo_callback, prepare_sudo_payload, query_ica_registration_fee, + sudo_error, sudo_open_ack, sudo_response, sudo_timeout, INTERCHAIN_ACCOUNT_ID, + }, + neutron::{ + assert_ibc_fee_coverage, get_proto_coin, query_ibc_fee, to_proto_msg_transfer, + RemoteChainInfo, SudoPayload, + }, +}; +use cw2::set_contract_version; +use neutron_sdk::{ + bindings::{msg::NeutronMsg, query::NeutronQuery, types::ProtobufAny}, + interchain_txs::helpers::get_port_id, + sudo::msg::SudoMsg, + NeutronError, NeutronResult, +}; +use prost::Message; +use valence_clock::helpers::{enqueue_msg, verify_clock}; + +use crate::state::{IbcForwarderIcaStateHelper, FALLBACK_ADDRESS}; +use crate::{error::ContractError, msg::FallbackAddressUpdateConfig}; +use crate::{ + helpers::{get_next_memo, MsgTransfer}, + msg::{ContractState, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + state::{ + CLOCK_ADDRESS, CONTRACT_STATE, INTERCHAIN_ACCOUNTS, NEXT_CONTRACT, REMOTE_CHAIN_INFO, + TRANSFER_AMOUNT, + }, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const SUDO_PAYLOAD_REPLY_ID: u64 = 1; + +type QueryDeps<'a> = Deps<'a, NeutronQuery>; +type ExecuteDeps<'a> = DepsMut<'a, NeutronQuery>; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: ExecuteDeps, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> NeutronResult> { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let next_contract = deps.api.addr_validate(&msg.next_contract)?; + let clock_addr = deps.api.addr_validate(&msg.clock_address)?; + CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; + NEXT_CONTRACT.save(deps.storage, &next_contract)?; + TRANSFER_AMOUNT.save(deps.storage, &msg.amount)?; + let remote_chain_info = RemoteChainInfo { + connection_id: msg.remote_chain_connection_id.to_string(), + channel_id: msg.remote_chain_channel_id.to_string(), + denom: msg.denom.to_string(), + ica_timeout: msg.ica_timeout, + ibc_transfer_timeout: msg.ibc_transfer_timeout, + }; + REMOTE_CHAIN_INFO.save(deps.storage, &remote_chain_info)?; + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + if let Some(addr) = &msg.fallback_address { + FALLBACK_ADDRESS.save(deps.storage, addr)?; + } + + Ok(Response::default() + .add_message(enqueue_msg(clock_addr.as_str())?) + .add_attribute("method", "ibc_forwarder_instantiate") + .add_attribute("next_contract", next_contract) + .add_attribute("contract_state", "instantiated") + .add_attributes(msg.get_response_attributes())) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: ExecuteDeps, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> NeutronResult> { + match msg { + ExecuteMsg::DistributeFallback { coins } => try_distribute_fallback(deps, env, info, coins), + ExecuteMsg::Tick {} => try_tick(deps, env, info), + } +} + +fn try_distribute_fallback( + mut deps: ExecuteDeps, + env: Env, + info: MessageInfo, + coins: Vec, +) -> NeutronResult> { + // load the fallback address or error out if its not set + let destination = match FALLBACK_ADDRESS.may_load(deps.storage)? { + Some(addr) => addr, + None => return Err(ContractError::MissingFallbackAddress {}.into()), + }; + let remote_chain_info = REMOTE_CHAIN_INFO.load(deps.storage)?; + + let min_ibc_fee_config = query_ibc_fee(deps.querier)?; + assert_ibc_fee_coverage(info, min_ibc_fee_config.total_ntrn_fee, Uint128::one())?; + + // we iterate over coins to be distributed, validate them, and generate the proto coins to be sent + let mut encountered_denoms: BTreeSet = BTreeSet::new(); + let mut proto_coins: Vec = vec![]; + + for coin in coins { + // validate that target denom is not passed for fallback distribution + ensure!( + coin.denom != remote_chain_info.denom, + Into::::into(ContractError::UnauthorizedDenomDistribution {}) + ); + + // error out if denom is duplicated + ensure!( + encountered_denoms.insert(coin.denom.to_string()), + Into::::into(ContractError::DuplicateDenomDistribution {}) + ); + + proto_coins.push(get_proto_coin(coin.denom, coin.amount)); + } + + let port_id = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + let interchain_account = INTERCHAIN_ACCOUNTS.may_load(deps.storage, port_id.clone())?; + if let Some(Some((address, controller_conn_id))) = interchain_account { + let multi_send_msg = MsgMultiSend { + inputs: vec![Input { + address, + coins: proto_coins.clone(), + }], + outputs: vec![Output { + address: destination, + coins: proto_coins, + }], + }; + + // Serialize the multi send message. + let mut buf = Vec::with_capacity(multi_send_msg.encoded_len()); + + if let Err(e) = multi_send_msg.encode(&mut buf) { + return Err(NeutronError::Std(StdError::generic_err(format!( + "Encode error: {e:}", + )))); + } + + let any_msg = ProtobufAny { + type_url: "/cosmos.bank.v1beta1.MsgMultiSend".to_string(), + value: Binary::from(buf), + }; + let submit_msg = NeutronMsg::submit_tx( + controller_conn_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + vec![any_msg], + "".to_string(), + remote_chain_info.ica_timeout.u64(), + min_ibc_fee_config.ibc_fee, + ); + let state_helper = IbcForwarderIcaStateHelper; + let sudo_msg = msg_with_sudo_callback( + &state_helper, + deps.branch(), + submit_msg, + SudoPayload { + port_id, + message: "distribute_fallback_multisend".to_string(), + }, + SUDO_PAYLOAD_REPLY_ID, + )?; + + Ok(Response::default() + .add_attribute("method", "try_forward_fallback") + .add_submessages(vec![sudo_msg])) + } else { + Err(NeutronError::Std(StdError::generic_err("no ica found"))) + } +} + +/// attempts to advance the state machine. validates the caller to be the clock. +fn try_tick(deps: ExecuteDeps, env: Env, info: MessageInfo) -> NeutronResult> { + // Verify caller is the clock + verify_clock(&info.sender, &CLOCK_ADDRESS.load(deps.storage)?)?; + + let current_state = CONTRACT_STATE.load(deps.storage)?; + match current_state { + ContractState::Instantiated => try_register_ica(deps, env), + ContractState::IcaCreated => try_forward_funds(env, deps), + } +} + +/// tries to register an ICA on the remote chain +fn try_register_ica(deps: ExecuteDeps, env: Env) -> NeutronResult> { + let remote_chain_info = REMOTE_CHAIN_INFO.load(deps.storage)?; + let ica_registration_fee = query_ica_registration_fee(deps.querier)?; + + let register_msg = NeutronMsg::register_interchain_account( + remote_chain_info.connection_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + Some(ica_registration_fee), + ); + + let key = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + + // we are saving empty data here because we handle response of registering ICA in sudo_open_ack method + INTERCHAIN_ACCOUNTS.save(deps.storage, key, &None)?; + + Ok(Response::new() + .add_attribute("method", "try_register_ica") + .add_message(register_msg)) +} + +fn try_forward_funds(env: Env, mut deps: ExecuteDeps) -> NeutronResult> { + // first we verify whether the next contract is ready for receiving the funds + let next_contract = NEXT_CONTRACT.load(deps.storage)?; + let deposit_address_query: Option = deps.querier.query_wasm_smart( + next_contract.to_string(), + &covenant_utils::neutron::QueryMsg::DepositAddress {}, + )?; + + // if query returns None, then we error and wait + let Some(deposit_address) = deposit_address_query else { + return Err(NeutronError::Std(StdError::not_found( + "Next contract is not ready for receiving the funds yet", + ))); + }; + + let min_fee_query_response = query_ibc_fee(deps.querier)?; + + let port_id = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + let interchain_account = INTERCHAIN_ACCOUNTS.load(deps.storage, port_id.clone())?; + + match interchain_account { + Some((address, controller_conn_id)) => { + let remote_chain_info = REMOTE_CHAIN_INFO.load(deps.storage)?; + let amount = TRANSFER_AMOUNT.load(deps.storage)?; + + let memo = get_next_memo(deps.querier, next_contract.as_str())?; + + let transfer_msg = MsgTransfer { + source_port: "transfer".to_string(), + source_channel: remote_chain_info.channel_id, + token: Some(get_proto_coin(remote_chain_info.denom, amount)), + sender: address, + receiver: deposit_address, + timeout_height: None, + timeout_timestamp: env + .block + .time + .plus_seconds(remote_chain_info.ica_timeout.u64()) + .plus_seconds(remote_chain_info.ibc_transfer_timeout.u64()) + .nanos(), + memo, + }; + + let protobuf_msg = to_proto_msg_transfer(transfer_msg)?; + + // tx to our ICA that wraps the transfer message defined above + let submit_msg = NeutronMsg::submit_tx( + controller_conn_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + vec![protobuf_msg], + "".to_string(), + remote_chain_info.ica_timeout.u64(), + min_fee_query_response.ibc_fee, + ); + + // sudo callback msg + // let state_helper = IbcForwarderIcaStateHelper; + let submsg = msg_with_sudo_callback( + &IbcForwarderIcaStateHelper, + deps.branch(), + submit_msg, + SudoPayload { + port_id, + message: "try_forward_funds".to_string(), + }, + SUDO_PAYLOAD_REPLY_ID, + )?; + + Ok(Response::default() + .add_attribute("method", "try_forward_funds") + .add_submessage(submsg)) + } + None => { + // I can't think of a case of how we could end up here as `sudo_open_ack` + // callback advances the state to `ICACreated` and stores the ICA. + // just in case, we revert the state to `Instantiated` to restart the flow. + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + Ok(Response::default() + .add_attribute("method", "try_forward_funds") + .add_attribute("error", "no_ica_found")) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: QueryDeps, env: Env, msg: QueryMsg) -> NeutronResult { + match msg { + QueryMsg::ClockAddress {} => Ok(to_json_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), + // we expect to receive funds into our ICA account on the remote chain. + // if the ICA had not been opened yet, we return `None` so that the + // contract querying this will be instructed to wait and retry. + QueryMsg::DepositAddress {} => { + let key = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + // here we want to return None instead of any errors in case no ICA + // is registered yet + let ica = match INTERCHAIN_ACCOUNTS.may_load(deps.storage, key)? { + Some(entry) => { + if let Some((addr, _)) = entry { + Some(addr) + } else { + None + } + } + None => None, + }; + + Ok(to_json_binary(&ica)?) + } + QueryMsg::IcaAddress {} => Ok(to_json_binary( + &get_ica( + &IbcForwarderIcaStateHelper, + deps.storage, + env.contract.address.as_str(), + INTERCHAIN_ACCOUNT_ID, + )? + .0, + )?), + QueryMsg::RemoteChainInfo {} => { + Ok(to_json_binary(&REMOTE_CHAIN_INFO.may_load(deps.storage)?)?) + } + QueryMsg::ContractState {} => Ok(to_json_binary(&CONTRACT_STATE.may_load(deps.storage)?)?), + QueryMsg::FallbackAddress {} => { + Ok(to_json_binary(&FALLBACK_ADDRESS.may_load(deps.storage)?)?) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: ExecuteDeps, env: Env, msg: SudoMsg) -> StdResult> { + match msg { + // For handling successful (non-error) acknowledgements. + SudoMsg::Response { request, data } => sudo_response(request, data), + + // For handling error acknowledgements. + SudoMsg::Error { request, details } => sudo_error(request, details), + + // For handling error timeouts. + SudoMsg::Timeout { request } => { + sudo_timeout(&IbcForwarderIcaStateHelper, deps, env, request) + } + + // For handling successful registering of ICA + SudoMsg::OpenAck { + port_id, + channel_id, + counterparty_channel_id, + counterparty_version, + } => sudo_open_ack( + &IbcForwarderIcaStateHelper, + deps, + env, + port_id, + channel_id, + counterparty_channel_id, + counterparty_version, + ), + _ => Ok(Response::default()), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: ExecuteDeps, env: Env, msg: Reply) -> StdResult> { + match msg.id { + SUDO_PAYLOAD_REPLY_ID => prepare_sudo_payload(&IbcForwarderIcaStateHelper, deps, env, msg), + _ => Err(StdError::generic_err(format!( + "unsupported reply message id {}", + msg.id + ))), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: ExecuteDeps, _env: Env, msg: MigrateMsg) -> StdResult> { + match msg { + MigrateMsg::UpdateConfig { + clock_addr, + next_contract, + remote_chain_info, + transfer_amount, + fallback_address, + } => { + let mut resp = Response::default().add_attribute("method", "update_config"); + + if let Some(addr) = clock_addr { + let clock_address = deps.api.addr_validate(&addr)?; + CLOCK_ADDRESS.save(deps.storage, &clock_address)?; + resp = resp.add_attribute("clock_addr", addr); + } + + if let Some(addr) = next_contract { + let next_contract_addr = deps.api.addr_validate(&addr)?; + NEXT_CONTRACT.save(deps.storage, &next_contract_addr)?; + resp = resp.add_attribute("next_contract", addr); + } + + if let Some(rci) = *remote_chain_info { + REMOTE_CHAIN_INFO.save(deps.storage, &rci)?; + resp = resp.add_attributes(rci.get_response_attributes()); + } + + if let Some(amount) = transfer_amount { + TRANSFER_AMOUNT.save(deps.storage, &amount)?; + resp = resp.add_attribute("transfer_amount", amount.to_string()); + } + + if let Some(config) = fallback_address { + match config { + FallbackAddressUpdateConfig::ExplicitAddress(addr) => { + FALLBACK_ADDRESS.save(deps.storage, &addr)?; + resp = resp.add_attribute("fallback_address", addr); + } + FallbackAddressUpdateConfig::Disable {} => { + FALLBACK_ADDRESS.remove(deps.storage); + resp = resp.add_attribute("fallback_address", "removed"); + } + } + } + + Ok(resp) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} diff --git a/contracts/ibc-forwarder/src/error.rs b/contracts/ibc-forwarder/src/error.rs new file mode 100644 index 00000000..05aa88e6 --- /dev/null +++ b/contracts/ibc-forwarder/src/error.rs @@ -0,0 +1,30 @@ +use cosmwasm_std::StdError; +use neutron_sdk::NeutronError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Next contract is not ready for receiving the funds yet")] + DepositAddressNotAvailable {}, + + #[error("Missing fallback address")] + MissingFallbackAddress {}, + + #[error("Cannot distribute target denom via fallback distribution")] + UnauthorizedDenomDistribution {}, + + #[error("Attempt to distribute duplicate denoms via fallback distribution")] + DuplicateDenomDistribution {}, +} + +impl From for NeutronError { + fn from(value: ContractError) -> Self { + NeutronError::Std(StdError::generic_err(value.to_string())) + } +} diff --git a/contracts/ibc-forwarder/src/helpers.rs b/contracts/ibc-forwarder/src/helpers.rs new file mode 100644 index 00000000..82c3c8f6 --- /dev/null +++ b/contracts/ibc-forwarder/src/helpers.rs @@ -0,0 +1,83 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{QuerierWrapper, StdResult}; +use neutron_sdk::bindings::query::NeutronQuery; + +/// Query next contract for the memo field +/// If query failed, we set memo to empty string, meaning no memo is expected +/// If query returns an empty string, we error out because we expect the memo not to be empty +/// +/// We do that because not all next contract will need a memo, and if the next contract +/// doesn't have the NextMemo query, we don't want to error out, rather return an empty memo. +/// Thats why if the NextMemo query doesn't fail, we expect it to return a non-empty string. +/// +/// This requires the next contract to implement the NextMemo query if it needs it, and +/// be careful not to return an error. +pub(crate) fn get_next_memo( + querier: QuerierWrapper, + addr: &str, +) -> StdResult { + #[cw_serde] + enum Query { + NextMemo {}, + } + + // We check that the query was successful, if not, we return empty string + let Ok(memo) = querier.query_wasm_smart::(addr.to_string(), &Query::NextMemo {}) else { + return Ok("".to_string()); + }; + + // If the query was successful, we expect the memo to be non-empty + // If memo is empty, something went wrong in the query, so we should error and retry later + if memo.is_empty() { + Err(cosmwasm_std::StdError::generic_err( + "NextMemo query returned empty string", + )) + } else { + Ok(memo) + } +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, +)] +pub struct IbcCounterpartyHeight { + #[prost(uint64, optional, tag = "1")] + revision_number: Option, + #[prost(uint64, optional, tag = "2")] + revision_height: Option, +} + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgTransfer { + /// the port on which the packet will be sent + #[prost(string, tag = "1")] + pub source_port: String, + /// the channel by which the packet will be sent + #[prost(string, tag = "2")] + pub source_channel: String, + /// the tokens to be transferred + #[prost(message, optional, tag = "3")] + pub token: Option, + /// the sender address + #[prost(string, tag = "4")] + pub sender: String, + /// the recipient address on the destination chain + #[prost(string, tag = "5")] + pub receiver: String, + /// Timeout height relative to the current block height. + /// The timeout is disabled when set to 0. + #[prost(message, optional, tag = "6")] + pub timeout_height: Option, + /// Timeout timestamp in absolute nanoseconds since unix epoch. + /// The timeout is disabled when set to 0. + #[prost(uint64, tag = "7")] + pub timeout_timestamp: u64, + #[prost(string, tag = "8")] + pub memo: String, +} diff --git a/contracts/ibc-forwarder/src/lib.rs b/contracts/ibc-forwarder/src/lib.rs new file mode 100644 index 00000000..3da36ff7 --- /dev/null +++ b/contracts/ibc-forwarder/src/lib.rs @@ -0,0 +1,7 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod helpers; +pub mod msg; +pub mod state; diff --git a/contracts/ibc-forwarder/src/msg.rs b/contracts/ibc-forwarder/src/msg.rs new file mode 100644 index 00000000..38c79801 --- /dev/null +++ b/contracts/ibc-forwarder/src/msg.rs @@ -0,0 +1,125 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{ + to_json_binary, Addr, Attribute, Binary, Coin, StdResult, Uint128, Uint64, WasmMsg, +}; +use covenant_macros::{ + clocked, covenant_clock_address, covenant_deposit_address, covenant_ica_address, + covenant_remote_chain, +}; +use covenant_utils::{instantiate2_helper::Instantiate2HelperConfig, neutron::RemoteChainInfo}; + +#[cw_serde] +pub struct InstantiateMsg { + /// address for the clock. this contract verifies + /// that only the clock can execute ticks + pub clock_address: String, + /// contract responsible for providing the address to forward the + /// funds to + pub next_contract: String, + + pub remote_chain_connection_id: String, + pub remote_chain_channel_id: String, + pub denom: String, + pub amount: Uint128, + + /// timeout in seconds. this is used to craft a timeout timestamp + /// that will be attached to the IBC transfer message from the ICA + /// on the host chain to its destination. typically this timeout + /// should be greater than the ICA timeout, otherwise if the ICA + /// times out, the destination chain receiving the funds will also + /// receive the IBC packet with an expired timestamp. + pub ibc_transfer_timeout: Uint64, + /// time in seconds for ICA SubmitTX messages from neutron + /// note that ICA uses ordered channels, a timeout implies + /// channel closed. We can reopen the channel by reregistering + /// the ICA with the same port id and connection id + pub ica_timeout: Uint64, + // fallback address on the remote chain + pub fallback_address: Option, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +impl InstantiateMsg { + pub fn get_response_attributes(&self) -> Vec { + vec![ + Attribute::new("clock_address", &self.clock_address), + Attribute::new( + "remote_chain_connection_id", + &self.remote_chain_connection_id, + ), + Attribute::new("remote_chain_channel_id", &self.remote_chain_channel_id), + Attribute::new("remote_chain_denom", &self.denom), + Attribute::new("remote_chain_amount", self.amount.to_string()), + Attribute::new( + "ibc_transfer_timeout", + self.ibc_transfer_timeout.to_string(), + ), + Attribute::new("ica_timeout", self.ica_timeout.to_string()), + Attribute::new("fallback_address", format!("{:?}", self.fallback_address)), + ] + } +} + +#[clocked] +#[cw_serde] +pub enum ExecuteMsg { + DistributeFallback { coins: Vec }, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + next_contract: Option, + remote_chain_info: Box>, + transfer_amount: Option, + fallback_address: Option, + }, + UpdateCodeId { + data: Option, + }, +} + +#[cw_serde] +pub enum FallbackAddressUpdateConfig { + ExplicitAddress(String), + Disable {}, +} + +#[covenant_deposit_address] +#[covenant_remote_chain] +#[covenant_clock_address] +#[covenant_ica_address] +#[derive(QueryResponses)] +#[cw_serde] +pub enum QueryMsg { + #[returns(ContractState)] + ContractState {}, + #[returns(Option)] + FallbackAddress {}, +} + +#[cw_serde] +pub enum ContractState { + /// Contract was instantiated, ready create ica + Instantiated, + /// ICA was created, funds are ready to be forwarded + IcaCreated, +} diff --git a/contracts/ibc-forwarder/src/state.rs b/contracts/ibc-forwarder/src/state.rs new file mode 100644 index 00000000..1b48469c --- /dev/null +++ b/contracts/ibc-forwarder/src/state.rs @@ -0,0 +1,84 @@ +use cosmwasm_std::{from_json, to_json_vec, Addr, Binary, StdError, StdResult, Storage, Uint128}; +use covenant_utils::{ + ica::IcaStateHelper, + neutron::{RemoteChainInfo, SudoPayload}, +}; +use cw_storage_plus::{Item, Map}; + +use crate::msg::ContractState; + +/// tracks the current state of state machine +pub const CONTRACT_STATE: Item = Item::new("contract_state"); + +/// clock module address to verify the sender of incoming ticks +pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); +pub const TRANSFER_AMOUNT: Item = Item::new("transfer_amount"); + +pub const NEXT_CONTRACT: Item = Item::new("next_contract"); + +/// information needed for an ibc transfer to the remote chain +pub const REMOTE_CHAIN_INFO: Item = Item::new("r_c_info"); + +/// interchain accounts storage in form of (port_id) -> (address, controller_connection_id) +pub const INTERCHAIN_ACCOUNTS: Map> = + Map::new("interchain_accounts"); + +pub const REPLY_ID_STORAGE: Item> = Item::new("reply_queue_id"); +pub const SUDO_PAYLOAD: Map<(String, u64), Vec> = Map::new("sudo_payload"); +pub const FALLBACK_ADDRESS: Item = Item::new("fallback_address"); + +pub(crate) struct IbcForwarderIcaStateHelper; + +impl IcaStateHelper for IbcForwarderIcaStateHelper { + fn reset_state(&self, storage: &mut dyn Storage) -> StdResult<()> { + CONTRACT_STATE.save(storage, &ContractState::Instantiated)?; + Ok(()) + } + + fn clear_ica(&self, storage: &mut dyn Storage) -> StdResult<()> { + INTERCHAIN_ACCOUNTS.clear(storage); + Ok(()) + } + + fn save_ica( + &self, + storage: &mut dyn Storage, + port_id: String, + address: String, + controller_connection_id: String, + ) -> StdResult<()> { + INTERCHAIN_ACCOUNTS.save(storage, port_id, &Some((address, controller_connection_id)))?; + Ok(()) + } + + fn save_state_ica_created(&self, storage: &mut dyn Storage) -> StdResult<()> { + CONTRACT_STATE.save(storage, &ContractState::IcaCreated)?; + Ok(()) + } + + fn save_reply_payload(&self, storage: &mut dyn Storage, payload: SudoPayload) -> StdResult<()> { + REPLY_ID_STORAGE.save(storage, &to_json_vec(&payload)?)?; + Ok(()) + } + + fn read_reply_payload(&self, storage: &mut dyn Storage) -> StdResult { + let data = REPLY_ID_STORAGE.load(storage)?; + from_json(Binary(data)) + } + + fn save_sudo_payload( + &self, + storage: &mut dyn Storage, + channel_id: String, + seq_id: u64, + payload: SudoPayload, + ) -> StdResult<()> { + SUDO_PAYLOAD.save(storage, (channel_id, seq_id), &to_json_vec(&payload)?) + } + + fn get_ica(&self, storage: &dyn Storage, key: String) -> StdResult<(String, String)> { + INTERCHAIN_ACCOUNTS + .load(storage, key)? + .ok_or_else(|| StdError::generic_err("Interchain account is not created yet")) + } +} diff --git a/contracts/holder/.cargo/config b/contracts/interchain-router/.cargo/config similarity index 69% rename from contracts/holder/.cargo/config rename to contracts/interchain-router/.cargo/config index d54cc015..5f6aa466 100644 --- a/contracts/holder/.cargo/config +++ b/contracts/interchain-router/.cargo/config @@ -1,3 +1,3 @@ [alias] wasm = "build --release --lib --target wasm32-unknown-unknown" -schema = "run --example schema" \ No newline at end of file +schema = "run --bin schema" diff --git a/contracts/interchain-router/Cargo.toml b/contracts/interchain-router/Cargo.toml new file mode 100644 index 00000000..417935b3 --- /dev/null +++ b/contracts/interchain-router/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "valence-interchain-router" +edition = { workspace = true } +authors = ["benskey bekauz@protonmail.com"] +description = "Interchain router contract for covenants" +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } +# rust-version = { workspace = true } + +exclude = ["contract.wasm", "hash.txt"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +covenant-macros = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +sha2 = { workspace = true } +neutron-sdk = { workspace = true } +cosmos-sdk-proto = { workspace = true } +schemars = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +bech32 = { workspace = true } +covenant-utils = { workspace = true } +cw-utils = { workspace = true } + +[dev-dependencies] +cw-multi-test = { workspace = true } +anyhow = { workspace = true } +valence-clock = { workspace = true } + diff --git a/contracts/interchain-router/README.md b/contracts/interchain-router/README.md new file mode 100644 index 00000000..cd348664 --- /dev/null +++ b/contracts/interchain-router/README.md @@ -0,0 +1,10 @@ +# Interchain Router + +Interchain Router is a contract that facilitates a predetermined routing of funds. +Each instance of the interchain router is associated with a single receiver. + +The router continuously attempts to perform IBC transfers to the receiver. +Upon receiving a `Tick`, the contract queries its own balances and uses them +to generate ibc transfer messages to the destination address. + +In case any of the IBC transfers fail, the funds will be refunded, and we can safely try again. diff --git a/contracts/interchain-router/schema/valence-interchain-router.json b/contracts/interchain-router/schema/valence-interchain-router.json new file mode 100644 index 00000000..99f057cd --- /dev/null +++ b/contracts/interchain-router/schema/valence-interchain-router.json @@ -0,0 +1,406 @@ +{ + "contract_name": "valence-interchain-router", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "clock_address", + "denoms", + "destination_config" + ], + "properties": { + "clock_address": { + "description": "address for the clock. this contract verifies that only the clock can execute ticks", + "type": "string" + }, + "denoms": { + "description": "specified denoms to route", + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "destination_config": { + "description": "config that determines how to facilitate the ibc routing", + "allOf": [ + { + "$ref": "#/definitions/DestinationConfig" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "DestinationConfig": { + "type": "object", + "required": [ + "denom_to_pfm_map", + "destination_receiver_addr", + "ibc_transfer_timeout", + "local_to_destination_chain_channel_id" + ], + "properties": { + "denom_to_pfm_map": { + "description": "pfm configurations for denoms", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "destination_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "local_to_destination_chain_channel_id": { + "description": "channel id of the destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "distribute_fallback" + ], + "properties": { + "distribute_fallback": { + "type": "object", + "required": [ + "denoms" + ], + "properties": { + "denoms": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Wakes the state machine up. The caller should check the sender of the tick is the clock if they'd like to pause when the clock does.", + "type": "object", + "required": [ + "tick" + ], + "properties": { + "tick": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "receiver_config" + ], + "properties": { + "receiver_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "target_denoms" + ], + "properties": { + "target_denoms": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated clock address authorized to submit ticks", + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "destination_config": { + "anyOf": [ + { + "$ref": "#/definitions/DestinationConfig" + }, + { + "type": "null" + } + ] + }, + "target_denoms": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "DestinationConfig": { + "type": "object", + "required": [ + "denom_to_pfm_map", + "destination_receiver_addr", + "ibc_transfer_timeout", + "local_to_destination_chain_channel_id" + ], + "properties": { + "denom_to_pfm_map": { + "description": "pfm configurations for denoms", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "destination_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "local_to_destination_chain_channel_id": { + "description": "channel id of the destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "sudo": null, + "responses": { + "clock_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "receiver_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DestinationConfig", + "type": "object", + "required": [ + "denom_to_pfm_map", + "destination_receiver_addr", + "ibc_transfer_timeout", + "local_to_destination_chain_channel_id" + ], + "properties": { + "denom_to_pfm_map": { + "description": "pfm configurations for denoms", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "destination_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "local_to_destination_chain_channel_id": { + "description": "channel id of the destination chain", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "target_denoms": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Set_of_String", + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + } +} diff --git a/contracts/interchain-router/src/bin/schema.rs b/contracts/interchain-router/src/bin/schema.rs new file mode 100644 index 00000000..ae821b0b --- /dev/null +++ b/contracts/interchain-router/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use valence_interchain_router::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/interchain-router/src/contract.rs b/contracts/interchain-router/src/contract.rs new file mode 100644 index 00000000..36e64b7a --- /dev/null +++ b/contracts/interchain-router/src/contract.rs @@ -0,0 +1,219 @@ +use std::collections::BTreeSet; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, Attribute, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, + StdResult, Uint128, +}; +use covenant_utils::{ + neutron::{assert_ibc_fee_coverage, query_ibc_fee}, + soft_validate_remote_chain_addr, +}; +use cw2::set_contract_version; +use neutron_sdk::{ + bindings::{msg::NeutronMsg, query::NeutronQuery}, + query::min_ibc_fee::MinIbcFeeResponse, + NeutronError, NeutronResult, +}; +use valence_clock::helpers::{enqueue_msg, verify_clock}; + +use crate::state::{DESTINATION_CONFIG, TARGET_DENOMS}; +use crate::{ + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + state::CLOCK_ADDRESS, +}; + +type ExecuteDeps<'a> = DepsMut<'a, NeutronQuery>; +type QueryDeps<'a> = Deps<'a, NeutronQuery>; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: ExecuteDeps, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> NeutronResult> { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let clock_address = deps.api.addr_validate(&msg.clock_address)?; + soft_validate_remote_chain_addr(deps.api, &msg.destination_config.destination_receiver_addr)?; + + CLOCK_ADDRESS.save(deps.storage, &clock_address)?; + DESTINATION_CONFIG.save(deps.storage, &msg.destination_config)?; + TARGET_DENOMS.save(deps.storage, &msg.denoms)?; + + Ok(Response::default() + .add_message(enqueue_msg(msg.clock_address.as_str())?) + .add_attribute("method", "interchain_router_instantiate") + .add_attribute("clock_address", clock_address.to_string()) + .add_attributes(msg.destination_config.get_response_attributes())) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: ExecuteDeps, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> NeutronResult> { + match msg { + ExecuteMsg::Tick {} => { + // Verify caller is the clock + verify_clock(&info.sender, &CLOCK_ADDRESS.load(deps.storage)?)?; + try_route_balances(deps, env) + } + ExecuteMsg::DistributeFallback { denoms } => { + try_distribute_fallback(deps, env, info, denoms) + } + } +} + +fn try_distribute_fallback( + deps: ExecuteDeps, + env: Env, + info: MessageInfo, + denoms: Vec, +) -> NeutronResult> { + let mut available_balances = Vec::with_capacity(denoms.len()); + let destination_config = DESTINATION_CONFIG.load(deps.storage)?; + let explicit_denoms = TARGET_DENOMS.load(deps.storage)?; + let min_ibc_fee_config = query_ibc_fee(deps.querier)?; + + assert_ibc_fee_coverage( + info, + min_ibc_fee_config.total_ntrn_fee, + Uint128::from(denoms.len() as u128), + )?; + + for denom in denoms { + // we do not distribute the main covenant denoms + // according to the fallback split + if explicit_denoms.contains(&denom) { + return Err(NeutronError::Std(StdError::generic_err( + "unauthorized denom distribution", + ))); + } + let queried_coin = deps + .querier + .query_balance(env.contract.address.to_string(), denom)?; + available_balances.push(queried_coin); + } + + let fallback_distribution_messages = destination_config.get_ibc_transfer_messages_for_coins( + available_balances, + env.block.time, + env.contract.address.to_string(), + min_ibc_fee_config.ibc_fee, + )?; + + Ok(Response::default() + .add_attribute("method", "try_distribute_fallback") + .add_messages(fallback_distribution_messages)) +} + +/// method that attempts to transfer out all available balances to the receiver +fn try_route_balances(deps: ExecuteDeps, env: Env) -> NeutronResult> { + let destination_config = DESTINATION_CONFIG.load(deps.storage)?; + let denoms_to_route = TARGET_DENOMS.load(deps.storage)?; + let mut denom_balances = Vec::with_capacity(denoms_to_route.len()); + + for denom in denoms_to_route { + let coin_to_route = deps + .querier + .query_balance(env.contract.address.to_string(), denom)?; + if !coin_to_route.amount.is_zero() { + denom_balances.push(coin_to_route); + } + } + + // if there are no balances, we return early; + // otherwise build up the response attributes + let balance_attributes: Vec = match denom_balances.len() { + 0 => { + return Ok(Response::default() + .add_attribute("method", "try_route_balances") + .add_attribute("balances", "[]")) + } + 1 => vec![Attribute::new( + denom_balances[0].denom.to_string(), + denom_balances[0].amount, + )], + _ => denom_balances + .iter() + .map(|c| Attribute::new(c.denom.to_string(), c.amount)) + .collect(), + }; + + let min_ibc_fee: MinIbcFeeResponse = deps.querier.query(&NeutronQuery::MinIbcFee {}.into())?; + + // get transfer messages for each denom + let messages = destination_config.get_ibc_transfer_messages_for_coins( + denom_balances, + env.block.time, + env.contract.address.to_string(), + min_ibc_fee.min_fee, + )?; + + Ok(Response::default() + .add_attribute("method", "try_route_balances") + .add_attributes(balance_attributes) + .add_messages(messages)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: QueryDeps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ReceiverConfig {} => { + Ok(to_json_binary(&DESTINATION_CONFIG.may_load(deps.storage)?)?) + } + QueryMsg::ClockAddress {} => Ok(to_json_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::TargetDenoms {} => Ok(to_json_binary(&TARGET_DENOMS.may_load(deps.storage)?)?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate( + deps: ExecuteDeps, + _env: Env, + msg: MigrateMsg, +) -> NeutronResult> { + match msg { + MigrateMsg::UpdateConfig { + clock_addr, + destination_config, + target_denoms, + } => { + let mut response = + Response::default().add_attribute("method", "update_interchain_router"); + + if let Some(addr) = clock_addr { + CLOCK_ADDRESS.save(deps.storage, &deps.api.addr_validate(&addr)?)?; + response = response.add_attribute("clock_addr", addr); + } + + if let Some(denoms) = target_denoms { + let denoms_str = denoms.join(",").to_string(); + let denom_set: BTreeSet = denoms.into_iter().collect(); + TARGET_DENOMS.save(deps.storage, &denom_set)?; + response = response.add_attribute("target_denoms", denoms_str); + } + + if let Some(config) = destination_config { + DESTINATION_CONFIG.save(deps.storage, &config)?; + response = response.add_attributes(config.get_response_attributes()); + } + + Ok(response) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default().add_attribute("method", "update_interchain_router")) + } + } +} diff --git a/contracts/interchain-router/src/error.rs b/contracts/interchain-router/src/error.rs new file mode 100644 index 00000000..a062ec4b --- /dev/null +++ b/contracts/interchain-router/src/error.rs @@ -0,0 +1,25 @@ +use cosmwasm_std::StdError; +use cw_utils::PaymentError; +use neutron_sdk::NeutronError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("unauthorized to distribute explicitly defined denom")] + UnauthorizedDenomDistribution {}, + + #[error("caller must cover ibc fees: {0}")] + IbcFeeError(PaymentError), +} + +impl ContractError { + pub fn to_neutron_std(&self) -> NeutronError { + NeutronError::Std(StdError::generic_err(self.to_string())) + } +} diff --git a/contracts/interchain-router/src/lib.rs b/contracts/interchain-router/src/lib.rs new file mode 100644 index 00000000..ff7db88e --- /dev/null +++ b/contracts/interchain-router/src/lib.rs @@ -0,0 +1,10 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; + +#[allow(clippy::unwrap_used)] +#[cfg(test)] +pub mod suite_tests; diff --git a/contracts/interchain-router/src/msg.rs b/contracts/interchain-router/src/msg.rs new file mode 100644 index 00000000..f090940b --- /dev/null +++ b/contracts/interchain-router/src/msg.rs @@ -0,0 +1,63 @@ +use std::collections::BTreeSet; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{to_json_binary, Addr, Binary, StdResult, WasmMsg}; +use covenant_macros::{clocked, covenant_clock_address}; +use covenant_utils::{instantiate2_helper::Instantiate2HelperConfig, DestinationConfig}; + +#[cw_serde] +pub struct InstantiateMsg { + /// address for the clock. this contract verifies + /// that only the clock can execute ticks + pub clock_address: String, + /// config that determines how to facilitate the ibc routing + pub destination_config: DestinationConfig, + /// specified denoms to route + pub denoms: BTreeSet, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +#[clocked] +#[cw_serde] +pub enum ExecuteMsg { + DistributeFallback { denoms: Vec }, +} + +#[covenant_clock_address] +#[derive(QueryResponses)] +#[cw_serde] +pub enum QueryMsg { + #[returns(DestinationConfig)] + ReceiverConfig {}, + #[returns(BTreeSet)] + TargetDenoms {}, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + destination_config: Option, + target_denoms: Option>, + }, + UpdateCodeId { + data: Option, + }, +} diff --git a/contracts/interchain-router/src/state.rs b/contracts/interchain-router/src/state.rs new file mode 100644 index 00000000..e25889bb --- /dev/null +++ b/contracts/interchain-router/src/state.rs @@ -0,0 +1,9 @@ +use std::collections::BTreeSet; + +use cosmwasm_std::Addr; +use covenant_utils::DestinationConfig; +use cw_storage_plus::Item; + +pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); +pub const DESTINATION_CONFIG: Item = Item::new("destination_config"); +pub const TARGET_DENOMS: Item> = Item::new("denoms"); diff --git a/contracts/interchain-router/src/suite_tests/mod.rs b/contracts/interchain-router/src/suite_tests/mod.rs new file mode 100644 index 00000000..ca2e314c --- /dev/null +++ b/contracts/interchain-router/src/suite_tests/mod.rs @@ -0,0 +1,18 @@ +pub mod suite; +pub mod tests; + +use cw_multi_test::{Contract, ContractWrapper}; +use neutron_sdk::bindings::{msg::NeutronMsg, query::NeutronQuery}; +use valence_clock::test_helpers::helpers::{ + mock_neutron_clock_execute, mock_neutron_clock_instantiate, mock_neutron_clock_query, +}; + +pub fn mock_clock_neutron_deps_contract() -> Box> { + let contract = ContractWrapper::new( + mock_neutron_clock_execute, + mock_neutron_clock_instantiate, + mock_neutron_clock_query, + ); + + Box::new(contract) +} diff --git a/contracts/interchain-router/src/suite_tests/suite.rs b/contracts/interchain-router/src/suite_tests/suite.rs new file mode 100644 index 00000000..c651ee3d --- /dev/null +++ b/contracts/interchain-router/src/suite_tests/suite.rs @@ -0,0 +1,202 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use cosmwasm_std::{ + testing::{MockApi, MockStorage}, + Addr, Coin, Empty, GovMsg, Uint64, +}; + +use covenant_utils::DestinationConfig; +use cw_multi_test::{ + App, AppResponse, BankKeeper, BasicAppBuilder, Contract, ContractWrapper, DistributionKeeper, + Executor, FailingModule, IbcAcceptingModule, StakeKeeper, WasmKeeper, +}; +use neutron_sdk::bindings::{msg::NeutronMsg, query::NeutronQuery}; + +use super::mock_clock_neutron_deps_contract; + +pub const ADMIN: &str = "admin"; +pub const CLOCK_ADDR: &str = "neutron19yz8hu6dand9lchzrcwezug763h770cv8sfen7kc7gw0jtdqha8qsl7tp9"; +pub const DEFAULT_CHANNEL: &str = "channel-1"; + +fn router_contract() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ) + .with_migrate(crate::contract::migrate); + + Box::new(contract) +} + +type CustomApp = App< + BankKeeper, + MockApi, + MockStorage, + FailingModule, + WasmKeeper, + StakeKeeper, + DistributionKeeper, + IbcAcceptingModule, + FailingModule, +>; + +pub struct Suite { + pub app: CustomApp, + pub router: Addr, + pub destination_addr: Addr, +} + +pub struct SuiteBuilder { + pub instantiate: InstantiateMsg, + pub app: App, +} + +impl Default for SuiteBuilder { + fn default() -> Self { + let hrp = "cosmos"; + + let canonical_address: Vec = vec![1; 90]; + let base32_address = bech32::ToBase32::to_base32(&canonical_address); + let destination_address = + bech32::encode(hrp, base32_address, bech32::Variant::Bech32).unwrap(); + + Self { + instantiate: InstantiateMsg { + clock_address: CLOCK_ADDR.to_string(), + destination_config: DestinationConfig { + local_to_destination_chain_channel_id: DEFAULT_CHANNEL.to_string(), + destination_receiver_addr: destination_address.to_string(), + ibc_transfer_timeout: Uint64::new(10), + denom_to_pfm_map: BTreeMap::new(), + }, + denoms: BTreeSet::new(), + }, + app: App::default(), + } + } +} + +impl SuiteBuilder { + pub fn with_denoms(mut self, denoms: Vec) -> Self { + let covenant_denoms: BTreeSet = denoms.into_iter().collect(); + + self.instantiate.denoms = covenant_denoms; + self + } + + pub fn build(mut self) -> Suite { + let mut app = BasicAppBuilder::::new_custom() + .with_ibc(IbcAcceptingModule::new()) + .with_api(MockApi::default().with_prefix("cosmos")) + .build(|_, _, _| ()); + + let router_code = app.store_code(router_contract()); + let clock_code = app.store_code(mock_clock_neutron_deps_contract()); + + self.instantiate.clock_address = app + .instantiate_contract( + clock_code, + Addr::unchecked(ADMIN), + &valence_clock::msg::InstantiateMsg { + tick_max_gas: None, + whitelist: vec![], + }, + &[], + "clock", + Some(ADMIN.to_string()), + ) + .unwrap() + .to_string(); + + let router = app + .instantiate_contract( + router_code, + Addr::unchecked(ADMIN), + &self.instantiate, + &[], + "router", + Some(ADMIN.to_string()), + ) + .unwrap(); + + Suite { + app, + router, + destination_addr: Addr::unchecked( + self.instantiate + .destination_config + .destination_receiver_addr, + ), + } + } +} + +// actions +impl Suite { + pub fn tick(&mut self, caller: &str) -> AppResponse { + self.app + .execute_contract( + Addr::unchecked(caller), + self.router.clone(), + &ExecuteMsg::Tick {}, + &[], + ) + .unwrap() + } + + pub fn migrate(&mut self, msg: MigrateMsg) -> Result { + self.app + .migrate_contract(Addr::unchecked(ADMIN), self.router.clone(), &msg, 1) + } +} + +// queries +impl Suite { + pub fn query_clock_addr(&self) -> Addr { + self.app + .wrap() + .query_wasm_smart(&self.router, &QueryMsg::ClockAddress {}) + .unwrap() + } + + pub fn query_destination_config(&self) -> DestinationConfig { + self.app + .wrap() + .query_wasm_smart(&self.router, &QueryMsg::ReceiverConfig {}) + .unwrap() + } + + pub fn query_target_denoms(&self) -> BTreeSet { + self.app + .wrap() + .query_wasm_smart(&self.router, &QueryMsg::TargetDenoms {}) + .unwrap() + } +} + +// helper +impl Suite { + pub fn _fund_router(&mut self, tokens: Vec) -> AppResponse { + self.app + .sudo(cw_multi_test::SudoMsg::Bank( + cw_multi_test::BankSudo::Mint { + to_address: self.router.to_string(), + amount: tokens, + }, + )) + .unwrap() + } + + pub fn _assert_router_balance(&mut self, tokens: Vec) { + for c in &tokens { + let queried_amount = self + .app + .wrap() + .query_balance(self.router.to_string(), c.denom.clone()) + .unwrap(); + assert_eq!(&queried_amount, c); + } + } +} diff --git a/contracts/interchain-router/src/suite_tests/tests.rs b/contracts/interchain-router/src/suite_tests/tests.rs new file mode 100644 index 00000000..6689ab7e --- /dev/null +++ b/contracts/interchain-router/src/suite_tests/tests.rs @@ -0,0 +1,362 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + marker::PhantomData, +}; + +use cosmwasm_std::{ + coin, + testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage}, + to_json_binary, Attribute, CosmosMsg, Empty, OwnedDeps, SubMsg, Uint128, Uint64, +}; +use covenant_utils::DestinationConfig; +use neutron_sdk::{ + bindings::msg::{IbcFee, NeutronMsg}, + query::min_ibc_fee::MinIbcFeeResponse, + sudo::msg::RequestPacketTimeoutHeight, + NeutronError, +}; + +use crate::{ + contract::{execute, instantiate}, + msg::MigrateMsg, + suite_tests::suite::DEFAULT_CHANNEL, +}; + +use super::suite::{SuiteBuilder, CLOCK_ADDR}; + +#[test] +fn test_instantiate_and_query_all() { + let suite = SuiteBuilder::default().build(); + + let clock = suite.query_clock_addr().to_string(); + let config = suite.query_destination_config(); + let denoms = suite.query_target_denoms(); + + assert_eq!("contract0", clock); + assert_eq!( + DestinationConfig { + local_to_destination_chain_channel_id: DEFAULT_CHANNEL.to_string(), + destination_receiver_addr: suite.destination_addr.to_string(), + ibc_transfer_timeout: Uint64::new(10), + denom_to_pfm_map: BTreeMap::new(), + }, + config + ); + assert_eq!(BTreeSet::new(), denoms); +} + +#[test] +fn test_migrate_config() { + let mut suite = SuiteBuilder::default().build(); + let target_denom_vec = vec!["new_denom_1".to_string(), "new_denom_2".to_string()]; + let target_denom_set: BTreeSet = target_denom_vec.clone().into_iter().collect(); + let migrate_msg = MigrateMsg::UpdateConfig { + clock_addr: Some("working_clock".to_string()), + destination_config: Some(DestinationConfig { + local_to_destination_chain_channel_id: "new_channel".to_string(), + destination_receiver_addr: "new_receiver".to_string(), + ibc_transfer_timeout: Uint64::new(100), + denom_to_pfm_map: BTreeMap::new(), + }), + target_denoms: Some(target_denom_vec), + }; + + suite.migrate(migrate_msg).unwrap(); + + let clock = suite.query_clock_addr(); + let config = suite.query_destination_config(); + let target_denoms = suite.query_target_denoms(); + + assert_eq!("working_clock", clock); + assert_eq!( + DestinationConfig { + local_to_destination_chain_channel_id: "new_channel".to_string(), + destination_receiver_addr: "new_receiver".to_string(), + ibc_transfer_timeout: Uint64::new(100), + denom_to_pfm_map: BTreeMap::new(), + }, + config + ); + assert_eq!(target_denom_set, target_denoms); +} + +#[test] +#[should_panic(expected = "Caller is not the clock, only clock can tick contracts")] +fn test_unauthorized_tick() { + let mut suite = SuiteBuilder::default().build(); + suite.tick("not_the_clock"); +} + +#[test] +#[should_panic(expected = "must cover ibc fees to distribute fallback denoms")] +fn test_tick_no_ibc_fee() { + let usdc_coin = coin(100, "usdc"); + let random_coin_1 = coin(100, "denom1"); + let random_coin_2 = coin(100, "denom2"); + let random_coin_3 = coin(100, "denom3"); + + let coins = vec![usdc_coin, random_coin_1, random_coin_2, random_coin_3]; + let querier: MockQuerier = MockQuerier::new(&[("cosmos2contract", &coins)]) + .with_custom_handler(|_| { + cosmwasm_std::SystemResult::Ok( + to_json_binary(&MinIbcFeeResponse { + min_fee: IbcFee { + recv_fee: vec![], + ack_fee: vec![coin(100000, "untrn")], + timeout_fee: vec![coin(100000, "untrn")], + }, + }) + .into(), + ) + }); + + let mut deps = OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: MockQuerier::new(&[]), + custom_query_type: PhantomData, + }; + // set the custom querier on our mock deps + deps.querier = querier; + + let no_ibc_fee_info = mock_info(CLOCK_ADDR, &[]); + + instantiate( + deps.as_mut(), + mock_env(), + no_ibc_fee_info.clone(), + SuiteBuilder::default() + .with_denoms(vec!["usdc".to_string()]) + .instantiate, + ) + .unwrap(); + + execute( + deps.as_mut(), + mock_env(), + no_ibc_fee_info.clone(), + crate::msg::ExecuteMsg::DistributeFallback { + denoms: vec!["denom1".to_string()], + }, + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "insufficient fees")] +fn test_tick_insufficient_ibc_fee() { + let usdc_coin = coin(100, "usdc"); + let random_coin_1 = coin(100, "denom1"); + let random_coin_2 = coin(100, "denom2"); + let random_coin_3 = coin(100, "denom3"); + + let coins = vec![usdc_coin, random_coin_1, random_coin_2, random_coin_3]; + let querier: MockQuerier = MockQuerier::new(&[("cosmos2contract", &coins)]) + .with_custom_handler(|_| { + cosmwasm_std::SystemResult::Ok( + to_json_binary(&MinIbcFeeResponse { + min_fee: IbcFee { + recv_fee: vec![], + ack_fee: vec![coin(100000, "untrn")], + timeout_fee: vec![coin(100000, "untrn")], + }, + }) + .into(), + ) + }); + + let mut deps = OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: MockQuerier::new(&[]), + custom_query_type: PhantomData, + }; + // set the custom querier on our mock deps + deps.querier = querier; + + let no_ibc_fee_info = mock_info(CLOCK_ADDR, &[coin(100000, "untrn")]); + + instantiate( + deps.as_mut(), + mock_env(), + no_ibc_fee_info.clone(), + SuiteBuilder::default() + .with_denoms(vec!["usdc".to_string()]) + .instantiate, + ) + .unwrap(); + + execute( + deps.as_mut(), + mock_env(), + no_ibc_fee_info.clone(), + crate::msg::ExecuteMsg::DistributeFallback { + denoms: vec!["denom1".to_string()], + }, + ) + .unwrap(); +} + +#[test] +fn test_tick() { + let usdc_coin = coin(100, "usdc"); + let random_coin_1 = coin(100, "denom1"); + let random_coin_2 = coin(100, "denom2"); + let random_coin_3 = coin(100, "denom3"); + + let coins = vec![usdc_coin, random_coin_1, random_coin_2, random_coin_3]; + let querier: MockQuerier = MockQuerier::new(&[("cosmos2contract", &coins)]) + .with_custom_handler(|_| { + cosmwasm_std::SystemResult::Ok( + to_json_binary(&MinIbcFeeResponse { + min_fee: IbcFee { + recv_fee: vec![], + ack_fee: vec![coin(100_000, "untrn")], + timeout_fee: vec![coin(100_000, "untrn")], + }, + }) + .into(), + ) + }); + + let mut deps = OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: MockQuerier::new(&[]), + custom_query_type: PhantomData, + }; + // set the custom querier on our mock deps + deps.querier = querier; + + let info = mock_info(CLOCK_ADDR, &[coin(10000000, "untrn")]); + + let sb = SuiteBuilder::default().with_denoms(vec!["usdc".to_string()]); + instantiate( + deps.as_mut(), + mock_env(), + info.clone(), + sb.instantiate.clone(), + ) + .unwrap(); + + let resp = execute( + deps.as_mut(), + mock_env(), + info.clone(), + crate::msg::ExecuteMsg::Tick {}, + ) + .unwrap(); + let mock_env = mock_env(); + let msg_exp = CosmosMsg::Custom(NeutronMsg::IbcTransfer { + source_port: "transfer".to_string(), + source_channel: "channel-1".to_string(), + token: coin(100, "usdc"), + sender: "cosmos2contract".to_string(), + receiver: "receiver".to_string(), + timeout_height: RequestPacketTimeoutHeight { + revision_number: None, + revision_height: None, + }, + timeout_timestamp: mock_env + .block + .time + .plus_seconds(Uint64::new(10).u64()) + .nanos(), + memo: format!("ibc_distribution: denom1:{:?}", Uint128::new(100)), + fee: IbcFee { + // must be empty + recv_fee: vec![], + ack_fee: vec![cosmwasm_std::Coin { + denom: "untrn".to_string(), + amount: Uint128::new(100000), + }], + timeout_fee: vec![cosmwasm_std::Coin { + denom: "untrn".to_string(), + amount: Uint128::new(100000), + }], + }, + }); + let _expected_messages = vec![SubMsg { + id: 0, + msg: msg_exp, + gas_limit: None, + reply_on: cosmwasm_std::ReplyOn::Never, + }]; + let expected_attributes = vec![ + Attribute { + key: "method".to_string(), + value: "try_route_balances".to_string(), + }, + Attribute { + key: "usdc".to_string(), + value: "100".to_string(), + }, + ]; + + // assert the expected response attributes and messages + assert_eq!(expected_attributes, resp.attributes); + + // try to use the fallback method to distribute + // explicitly defined denom + let err = execute( + deps.as_mut(), + mock_env.clone(), + info.clone(), + crate::msg::ExecuteMsg::DistributeFallback { + denoms: vec!["usdc".to_string()], + }, + ) + .unwrap_err(); + + assert_eq!( + err, + NeutronError::Std(cosmwasm_std::StdError::generic_err( + "unauthorized denom distribution".to_string() + )) + ); + + // now distribute a valid fallback denom + let resp = execute( + deps.as_mut(), + mock_env, + info, + crate::msg::ExecuteMsg::DistributeFallback { + denoms: vec!["denom1".to_string()], + }, + ) + .unwrap(); + + for msg in resp.messages { + assert_eq!( + msg, + SubMsg { + id: 0, + msg: CosmosMsg::Custom(NeutronMsg::IbcTransfer { + source_port: "transfer".to_string(), + source_channel: "channel-1".to_string(), + token: cosmwasm_std::Coin::new(100, "denom1".to_string()), + sender: "cosmos2contract".to_string(), + receiver: sb + .instantiate + .destination_config + .destination_receiver_addr + .to_string(), + timeout_height: RequestPacketTimeoutHeight { + revision_number: None, + revision_height: None + }, + timeout_timestamp: 1571797429879305533, + memo: format!("ibc_distribution: {:?}:{:?}", "denom1", Uint128::new(100),) + .to_string(), + fee: IbcFee { + recv_fee: vec![], + ack_fee: vec![cosmwasm_std::coin(100000, "untrn".to_string())], + timeout_fee: vec![cosmwasm_std::coin(100000, "untrn".to_string())], + }, + },), + gas_limit: None, + reply_on: cosmwasm_std::ReplyOn::Never, + } + ); + } +} diff --git a/contracts/ls/.cargo/config b/contracts/ls/.cargo/config deleted file mode 100644 index d54cc015..00000000 --- a/contracts/ls/.cargo/config +++ /dev/null @@ -1,3 +0,0 @@ -[alias] -wasm = "build --release --lib --target wasm32-unknown-unknown" -schema = "run --example schema" \ No newline at end of file diff --git a/contracts/ls/src/msg.rs b/contracts/ls/src/msg.rs deleted file mode 100644 index 49392032..00000000 --- a/contracts/ls/src/msg.rs +++ /dev/null @@ -1,211 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Attribute, Binary, Coin, StdError, Uint128, Uint64}; -use covenant_clock_derive::clocked; -use neutron_sdk::bindings::msg::IbcFee; - -#[cw_serde] -pub struct InstantiateMsg { - /// Address for the clock. This contract verifies - /// that only the clock can execute Ticks - pub clock_address: String, - /// IBC transfer channel on Stride for Neutron - /// This is used to IBC transfer stuatom on Stride - /// to the LP contract - pub stride_neutron_ibc_transfer_channel_id: String, - /// IBC connection ID on Neutron for Stride - /// We make an Interchain Account over this connection - pub neutron_stride_ibc_connection_id: String, - /// Address for the covenant's LP contract. - /// We send the liquid staked amount to this address - pub lp_address: String, - /// The liquid staked denom (e.g., stuatom). This is - /// required because we only allow transfers of this denom - /// out of the LSer - pub ls_denom: String, - /// Neutron requires fees to be set to refund relayers for - /// submission of ack and timeout messages. - /// recv_fee and ack_fee paid in untrn from this contract - pub ibc_fee: IbcFee, - /// Time in seconds for ICA SubmitTX messages from Neutron - /// Note that ICA uses ordered channels, a timeout implies - /// channel closed. We can reopen the channel by reregistering - /// the ICA with the same port id and connection id - pub ica_timeout: Uint64, - /// Timeout in seconds. This is used to craft a timeout timestamp - /// that will be attached to the IBC transfer message from the ICA - /// on the host chain (Stride) to its destination. Typically - /// this timeout should be greater than the ICA timeout, otherwise - /// if the ICA times out, the destination chain receiving the funds - /// will also receive the IBC packet with an expired timestamp. - pub ibc_transfer_timeout: Uint64, -} - -#[cw_serde] -pub struct PresetLsFields { - pub ls_code: u64, - pub label: String, - pub ls_denom: String, - pub stride_neutron_ibc_transfer_channel_id: String, - pub neutron_stride_ibc_connection_id: String, -} - -impl PresetLsFields { - pub fn to_instantiate_msg( - self, - clock_address: String, - lp_address: String, - ibc_fee: IbcFee, - ica_timeout: Uint64, - ibc_transfer_timeout: Uint64, - ) -> InstantiateMsg { - InstantiateMsg { - clock_address, - stride_neutron_ibc_transfer_channel_id: self.stride_neutron_ibc_transfer_channel_id, - neutron_stride_ibc_connection_id: self.neutron_stride_ibc_connection_id, - lp_address, - ls_denom: self.ls_denom, - ibc_fee, - ica_timeout, - ibc_transfer_timeout, - } - } -} - -#[clocked] -#[cw_serde] -pub enum ExecuteMsg { - /// The transfer message allows anybody to permissionlessly - /// transfer a specified amount of tokens of the preset ls_denom - /// from the ICA of the host chain to the preset lp_address - Transfer { amount: Uint128 }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(Addr)] - ClockAddress {}, - #[returns(Addr)] - StrideICA {}, - #[returns(Addr)] - LpAddress {}, - #[returns(ContractState)] - ContractState {}, - // this query returns acknowledgement result after interchain transaction - #[returns(Option)] - AcknowledgementResult { - interchain_account_id: String, - sequence_id: u64, - }, - // this query returns non-critical errors list - #[returns(Vec<(Vec, String)>)] - ErrorsQueue {}, - #[returns(RemoteChainInfo)] - RemoteChainInfo {}, -} - -#[cw_serde] -pub enum MigrateMsg { - UpdateConfig { - clock_addr: Option, - lp_address: Option, - remote_chain_info: Option, - }, - UpdateCodeId { - data: Option, - }, -} - -#[cw_serde] -pub struct OpenAckVersion { - pub version: String, - pub controller_connection_id: String, - pub host_connection_id: String, - pub address: String, - pub encoding: String, - pub tx_type: String, -} - -#[cw_serde] -pub enum ContractState { - Instantiated, - ICACreated, -} - -/// SudoPayload is a type that stores information about a transaction that we try to execute -/// on the host chain. This is a type introduced for our convenience. -#[cw_serde] -pub struct SudoPayload { - pub message: String, - pub port_id: String, -} - -/// Serves for storing acknowledgement calls for interchain transactions -#[cw_serde] -pub enum AcknowledgementResult { - /// Success - Got success acknowledgement in sudo with array of message item types in it - Success(Vec), - /// Error - Got error acknowledgement in sudo with payload message in it and error details - Error((String, String)), - /// Timeout - Got timeout acknowledgement in sudo with payload message in it - Timeout(String), -} - -#[cw_serde] -pub struct RemoteChainInfo { - /// connection id from neutron to the remote chain on which - /// we wish to open an ICA - pub connection_id: String, - pub channel_id: String, - pub denom: String, - pub ibc_transfer_timeout: Uint64, - pub ica_timeout: Uint64, - pub ibc_fee: IbcFee, -} - -impl RemoteChainInfo { - pub fn get_response_attributes(&self) -> Vec { - let recv_fee = coin_vec_to_string(&self.ibc_fee.recv_fee); - let ack_fee = coin_vec_to_string(&self.ibc_fee.ack_fee); - let timeout_fee = coin_vec_to_string(&self.ibc_fee.timeout_fee); - - vec![ - Attribute::new("connection_id", &self.connection_id), - Attribute::new("channel_id", &self.channel_id), - Attribute::new("denom", &self.denom), - Attribute::new( - "ibc_transfer_timeout", - self.ibc_transfer_timeout.to_string(), - ), - Attribute::new("ica_timeout", self.ica_timeout.to_string()), - Attribute::new("ibc_recv_fee", recv_fee), - Attribute::new("ibc_ack_fee", ack_fee), - Attribute::new("ibc_timeout_fee", timeout_fee), - ] - } - - pub fn validate(self) -> Result { - if self.ibc_fee.ack_fee.is_empty() - || self.ibc_fee.timeout_fee.is_empty() - || !self.ibc_fee.recv_fee.is_empty() - { - return Err(StdError::GenericErr { - msg: "invalid IbcFee".to_string(), - }); - } - - Ok(self) - } -} - -fn coin_vec_to_string(coins: &Vec) -> String { - let mut str = "".to_string(); - if coins.is_empty() { - str.push_str("[]"); - } else { - for coin in coins { - str.push_str(&coin.to_string()); - } - } - str.to_string() -} diff --git a/contracts/lper/.cargo/config b/contracts/native-router/.cargo/config similarity index 69% rename from contracts/lper/.cargo/config rename to contracts/native-router/.cargo/config index d54cc015..5f6aa466 100644 --- a/contracts/lper/.cargo/config +++ b/contracts/native-router/.cargo/config @@ -1,3 +1,3 @@ [alias] wasm = "build --release --lib --target wasm32-unknown-unknown" -schema = "run --example schema" \ No newline at end of file +schema = "run --bin schema" diff --git a/contracts/native-router/Cargo.toml b/contracts/native-router/Cargo.toml new file mode 100644 index 00000000..3a170f6a --- /dev/null +++ b/contracts/native-router/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "valence-native-router" +edition = { workspace = true } +authors = ["benskey bekauz@protonmail.com"] +description = "Native router contract for covenants" +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } + +exclude = ["contract.wasm", "hash.txt"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +covenant-macros = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +sha2 = { workspace = true } +neutron-sdk = { workspace = true } +cosmos-sdk-proto = { workspace = true } +schemars = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +bech32 = { workspace = true } +covenant-utils = { workspace = true } diff --git a/contracts/native-router/README.md b/contracts/native-router/README.md new file mode 100644 index 00000000..ed5cba6f --- /dev/null +++ b/contracts/native-router/README.md @@ -0,0 +1,8 @@ +# Native Router + +Native Router is a contract that facilitates a predetermined routing of funds. +Each instance of the native router is associated with a single receiver. + +The router continuously attempts to perform bank sends to the receiver. +Upon receiving a `Tick`, the contract queries its own balances and uses them +to generate bank transfer messages to the destination address. diff --git a/contracts/holder/schema/covenant-holder.json b/contracts/native-router/schema/valence-native-router.json similarity index 51% rename from contracts/holder/schema/covenant-holder.json rename to contracts/native-router/schema/valence-native-router.json index f7f4a386..614104a1 100644 --- a/contracts/holder/schema/covenant-holder.json +++ b/contracts/native-router/schema/valence-native-router.json @@ -1,5 +1,5 @@ { - "contract_name": "covenant-holder", + "contract_name": "valence-native-router", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { @@ -7,19 +7,26 @@ "title": "InstantiateMsg", "type": "object", "required": [ - "pool_address" + "clock_address", + "denoms", + "receiver_address" ], "properties": { - "pool_address": { - "description": "pool address is the address of the pool where liquidity has been provided The holder holds LP tokens associated with this pool", + "clock_address": { + "description": "address for the clock. this contract verifies that only the clock can execute ticks", "type": "string" }, - "withdrawer": { - "description": "A withdrawer is the only authorized address that can withdraw from the contract.", - "type": [ - "string", - "null" - ] + "denoms": { + "description": "specified denoms to route", + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "receiver_address": { + "description": "receiver address on local chain", + "type": "string" } }, "additionalProperties": false @@ -29,22 +36,21 @@ "title": "ExecuteMsg", "oneOf": [ { - "description": "The withdraw message can only be called by the withdrawer The withdraw can specify a quanity to be withdrawn. If no quantity is specified, the full balance is withdrawn into withdrawer account", "type": "object", "required": [ - "withdraw" + "distribute_fallback" ], "properties": { - "withdraw": { + "distribute_fallback": { "type": "object", + "required": [ + "denoms" + ], "properties": { - "quantity": { - "type": [ - "array", - "null" - ], + "denoms": { + "type": "array", "items": { - "$ref": "#/definitions/Coin" + "type": "string" } } }, @@ -54,53 +60,45 @@ "additionalProperties": false }, { - "description": "The WithdrawLiqudity message can only be called by the withdrawer When it is called, the LP tokens are burned and the liquity is withdrawn from the pool and lands in the holder", + "description": "Wakes the state machine up. The caller should check the sender of the tick is the clock if they'd like to pause when the clock does.", "type": "object", "required": [ - "withdraw_liquidity" + "tick" ], "properties": { - "withdraw_liquidity": { + "tick": { "type": "object", "additionalProperties": false } }, "additionalProperties": false } - ], - "definitions": { - "Coin": { + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { "type": "object", "required": [ - "amount", - "denom" + "receiver_config" ], "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" + "receiver_config": { + "type": "object", + "additionalProperties": false } - } + }, + "additionalProperties": false }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ { "type": "object", "required": [ - "withdrawer" + "target_denoms" ], "properties": { - "withdrawer": { + "target_denoms": { "type": "object", "additionalProperties": false } @@ -108,12 +106,13 @@ "additionalProperties": false }, { + "description": "Returns the associated clock address authorized to submit ticks", "type": "object", "required": [ - "pool_address" + "clock_address" ], "properties": { - "pool_address": { + "clock_address": { "type": "object", "additionalProperties": false } @@ -135,17 +134,26 @@ "update_config": { "type": "object", "properties": { - "pool_address": { + "clock_addr": { "type": [ "string", "null" ] }, - "withdrawer": { + "receiver_address": { "type": [ "string", "null" ] + }, + "target_denoms": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -188,29 +196,113 @@ }, "sudo": null, "responses": { - "pool_address": { + "clock_address": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Addr", "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" }, - "withdrawer": { + "receiver_config": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_Addr", - "anyOf": [ + "title": "ReceiverConfig", + "oneOf": [ { - "$ref": "#/definitions/Addr" + "description": "party expects to receive funds on the same chain", + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false }, { - "type": "null" + "description": "party expects to receive funds on a remote chain", + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/DestinationConfig" + } + }, + "additionalProperties": false } ], "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "DestinationConfig": { + "type": "object", + "required": [ + "denom_to_pfm_map", + "destination_receiver_addr", + "ibc_transfer_timeout", + "local_to_destination_chain_channel_id" + ], + "properties": { + "denom_to_pfm_map": { + "description": "pfm configurations for denoms", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "destination_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "local_to_destination_chain_channel_id": { + "description": "channel id of the destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" } } + }, + "target_denoms": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Set_of_String", + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true } } } diff --git a/contracts/native-router/src/bin/schema.rs b/contracts/native-router/src/bin/schema.rs new file mode 100644 index 00000000..556c9b51 --- /dev/null +++ b/contracts/native-router/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use valence_native_router::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/native-router/src/contract.rs b/contracts/native-router/src/contract.rs new file mode 100644 index 00000000..d89b5801 --- /dev/null +++ b/contracts/native-router/src/contract.rs @@ -0,0 +1,197 @@ +use std::collections::BTreeSet; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, Attribute, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, + Response, StdError, StdResult, +}; +use cw2::set_contract_version; +use valence_clock::helpers::{enqueue_msg, verify_clock}; + +use crate::{ + error::ContractError, + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + state::{CLOCK_ADDRESS, RECEIVER_ADDRESS, TARGET_DENOMS}, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let clock_addr = deps.api.addr_validate(&msg.clock_address)?; + let receiver_addr = deps.api.addr_validate(&msg.receiver_address)?; + + CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; + RECEIVER_ADDRESS.save(deps.storage, &receiver_addr)?; + TARGET_DENOMS.save(deps.storage, &msg.denoms)?; + + Ok(Response::default() + .add_message(enqueue_msg(clock_addr.as_str())?) + .add_attribute("method", "interchain_router_instantiate") + .add_attribute("clock_address", clock_addr)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Tick {} => { + // Verify caller is the clock + verify_clock(&info.sender, &CLOCK_ADDRESS.load(deps.storage)?)?; + try_route_balances(deps, env) + } + ExecuteMsg::DistributeFallback { denoms } => try_distribute_fallback(deps, env, denoms), + } +} + +fn try_distribute_fallback( + deps: DepsMut, + env: Env, + denoms: Vec, +) -> Result { + let mut available_balances = Vec::with_capacity(denoms.len()); + let receiver_address = RECEIVER_ADDRESS.load(deps.storage)?; + let explicit_denoms = TARGET_DENOMS.load(deps.storage)?; + + for denom in denoms.clone() { + // we do not distribute the main covenant denoms + // according to the fallback split + if explicit_denoms.contains(&denom) { + return Err(ContractError::Std(StdError::generic_err( + "unauthorized denom distribution", + ))); + } + let queried_coin = deps + .querier + .query_balance(env.contract.address.to_string(), denom)?; + available_balances.push(queried_coin); + } + + let bank_sends: Vec = available_balances + .into_iter() + .map(|c| { + BankMsg::Send { + to_address: receiver_address.to_string(), + amount: vec![c], + } + .into() + }) + .collect(); + + Ok(Response::default() + .add_attribute("method", "try_distribute_fallback") + .add_messages(bank_sends)) +} + +/// method that attempts to transfer out all available balances to the receiver +fn try_route_balances(deps: DepsMut, env: Env) -> Result { + let receiver_addr = RECEIVER_ADDRESS.load(deps.storage)?; + let denoms_to_route = TARGET_DENOMS.load(deps.storage)?; + let mut denom_balances = Vec::with_capacity(denoms_to_route.len()); + + for denom in denoms_to_route { + let coin_to_route = deps + .querier + .query_balance(env.contract.address.to_string(), denom)?; + if !coin_to_route.amount.is_zero() { + denom_balances.push(coin_to_route); + } + } + + // if there are no balances, we return early; + // otherwise build up the response attributes + let balance_attributes: Vec = match denom_balances.len() { + 0 => { + return Ok(Response::default() + .add_attribute("method", "try_route_balances") + .add_attribute("balances", "[]")) + } + 1 => vec![Attribute::new( + denom_balances[0].denom.to_string(), + denom_balances[0].amount, + )], + _ => denom_balances + .iter() + .map(|c| Attribute::new(c.denom.to_string(), c.amount)) + .collect(), + }; + + let bank_sends: Vec = denom_balances + .into_iter() + .map(|c| { + BankMsg::Send { + to_address: receiver_addr.to_string(), + amount: vec![c], + } + .into() + }) + .collect(); + + Ok(Response::default() + .add_attribute("method", "try_route_balances") + .add_attributes(balance_attributes) + .add_messages(bank_sends)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ReceiverConfig {} => { + Ok(to_json_binary(&RECEIVER_ADDRESS.may_load(deps.storage)?)?) + } + QueryMsg::ClockAddress {} => Ok(to_json_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::TargetDenoms {} => Ok(to_json_binary(&TARGET_DENOMS.may_load(deps.storage)?)?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + match msg { + MigrateMsg::UpdateConfig { + clock_addr, + receiver_address, + target_denoms, + } => { + let mut response = + Response::default().add_attribute("method", "update_interchain_router"); + + if let Some(addr) = clock_addr { + CLOCK_ADDRESS.save(deps.storage, &deps.api.addr_validate(&addr)?)?; + response = response.add_attribute("clock_addr", addr); + } + + if let Some(denoms) = target_denoms { + let denoms_str = denoms.join(","); + let denom_set: BTreeSet = denoms.into_iter().collect(); + TARGET_DENOMS.save(deps.storage, &denom_set)?; + response = response.add_attribute("target_denoms", denoms_str); + } + + if let Some(addr) = receiver_address { + RECEIVER_ADDRESS.save(deps.storage, &deps.api.addr_validate(&addr)?)?; + response = response.add_attribute("receiver_addr", addr); + } + + Ok(response) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default().add_attribute("method", "update_native_router")) + } + } +} diff --git a/contracts/native-router/src/error.rs b/contracts/native-router/src/error.rs new file mode 100644 index 00000000..7ba7ba0e --- /dev/null +++ b/contracts/native-router/src/error.rs @@ -0,0 +1,18 @@ +use cosmwasm_std::StdError; +use neutron_sdk::NeutronError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + NeutronError(#[from] NeutronError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("unauthorized to distribute explicitly defined denom")] + UnauthorizedDenomDistribution {}, +} diff --git a/contracts/native-router/src/lib.rs b/contracts/native-router/src/lib.rs new file mode 100644 index 00000000..47bc573c --- /dev/null +++ b/contracts/native-router/src/lib.rs @@ -0,0 +1,6 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/native-router/src/msg.rs b/contracts/native-router/src/msg.rs new file mode 100644 index 00000000..c0a768ca --- /dev/null +++ b/contracts/native-router/src/msg.rs @@ -0,0 +1,63 @@ +use std::collections::BTreeSet; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{to_json_binary, Addr, Binary, StdResult, WasmMsg}; +use covenant_macros::{clocked, covenant_clock_address}; +use covenant_utils::{instantiate2_helper::Instantiate2HelperConfig, ReceiverConfig}; + +#[cw_serde] +pub struct InstantiateMsg { + /// address for the clock. this contract verifies + /// that only the clock can execute ticks + pub clock_address: String, + /// receiver address on local chain + pub receiver_address: String, + /// specified denoms to route + pub denoms: BTreeSet, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +#[clocked] +#[cw_serde] +pub enum ExecuteMsg { + DistributeFallback { denoms: Vec }, +} + +#[covenant_clock_address] +#[derive(QueryResponses)] +#[cw_serde] +pub enum QueryMsg { + #[returns(ReceiverConfig)] + ReceiverConfig {}, + #[returns(BTreeSet)] + TargetDenoms {}, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + receiver_address: Option, + target_denoms: Option>, + }, + UpdateCodeId { + data: Option, + }, +} diff --git a/contracts/native-router/src/state.rs b/contracts/native-router/src/state.rs new file mode 100644 index 00000000..5011a912 --- /dev/null +++ b/contracts/native-router/src/state.rs @@ -0,0 +1,8 @@ +use std::collections::BTreeSet; + +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); +pub const RECEIVER_ADDRESS: Item = Item::new("receiver_address"); +pub const TARGET_DENOMS: Item> = Item::new("denoms"); diff --git a/contracts/native-splitter/.cargo/config b/contracts/native-splitter/.cargo/config new file mode 100644 index 00000000..6a6f2852 --- /dev/null +++ b/contracts/native-splitter/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" \ No newline at end of file diff --git a/contracts/native-splitter/Cargo.toml b/contracts/native-splitter/Cargo.toml new file mode 100644 index 00000000..5d249f15 --- /dev/null +++ b/contracts/native-splitter/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "valence-native-splitter" +authors = ["benskey bekauz@protonmail.com"] +description = "Native Splitter module for covenants" +edition = { workspace = true } +license = { workspace = true } +# rust-version = { workspace = true } +version = { workspace = true } + +exclude = ["contract.wasm", "hash.txt"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +covenant-macros = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +covenant-utils = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +thiserror = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } diff --git a/contracts/native-splitter/README.md b/contracts/native-splitter/README.md new file mode 100644 index 00000000..c2d253ff --- /dev/null +++ b/contracts/native-splitter/README.md @@ -0,0 +1,28 @@ +# Interchain Splitter + +Interchain Splitter is a contract meant to facilitate a pre-agreed upon way of distributing funds at its disposal. + +Splitter should remain agnostic to any price changes that may occur during the covenant lifecycle. +It should accept the tokens and distribute them according to the initial agreement. + +## Split Configurations + +In general, we support a per-denom configuration as follows: +``` +OSMO -> [(osmo12323, 40), (cosmos32121, 60)] +ATOM -> [(osmo12323, 50), (cosmo32121, 50)] +_ -> [(osmo12323, 30), (cosmo32121, 70)] +``` + +### Custom Split + +A custom split here refers to a list of addresses with their associated share of the split (in %). +In this example `OSMO -> [(osmo12323, 40), (cosmos32121, 60)]`, all OSMO tokens that the splitter +receives will be split between osmo12323 and cosmos32121, with 40% and 60% shares respectively. +Custom split configuration should always add up to 100 or else an error is returned. + +### Wildcard Split + +For cases where denoms don't really matter, a wildcard split can be provided. Then any denoms that +the splitter holds that do not fall under any of other configurations will be split according to this. + diff --git a/contracts/native-splitter/schema/valence-native-splitter.json b/contracts/native-splitter/schema/valence-native-splitter.json new file mode 100644 index 00000000..c057b0bd --- /dev/null +++ b/contracts/native-splitter/schema/valence-native-splitter.json @@ -0,0 +1,388 @@ +{ + "contract_name": "valence-native-splitter", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "clock_address", + "splits" + ], + "properties": { + "clock_address": { + "description": "address of the associated clock", + "type": "string" + }, + "fallback_split": { + "description": "a split for all denoms that are not covered in the regular `splits` list", + "anyOf": [ + { + "$ref": "#/definitions/SplitConfig" + }, + { + "type": "null" + } + ] + }, + "splits": { + "description": "maps denom to its split configuration", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "distribute_fallback" + ], + "properties": { + "distribute_fallback": { + "type": "object", + "required": [ + "denoms" + ], + "properties": { + "denoms": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Wakes the state machine up. The caller should check the sender of the tick is the clock if they'd like to pause when the clock does.", + "type": "object", + "required": [ + "tick" + ], + "properties": { + "tick": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "denom_split" + ], + "properties": { + "denom_split": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "splits" + ], + "properties": { + "splits": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "fallback_split" + ], + "properties": { + "fallback_split": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated clock address authorized to submit ticks", + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the address a contract expects to receive funds to", + "type": "object", + "required": [ + "deposit_address" + ], + "properties": { + "deposit_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "fallback_split": { + "anyOf": [ + { + "$ref": "#/definitions/SplitConfig" + }, + { + "type": "null" + } + ] + }, + "splits": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + } + } + }, + "sudo": null, + "responses": { + "clock_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "denom_split": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SplitConfig", + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "deposit_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "fallback_split": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SplitConfig", + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + } + } + }, + "splits": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Tuple_of_String_and_SplitConfig", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/SplitConfig" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + } + } + } + } +} diff --git a/contracts/native-splitter/src/bin/schema.rs b/contracts/native-splitter/src/bin/schema.rs new file mode 100644 index 00000000..882b4886 --- /dev/null +++ b/contracts/native-splitter/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use valence_native_splitter::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/native-splitter/src/contract.rs b/contracts/native-splitter/src/contract.rs new file mode 100644 index 00000000..bad59d62 --- /dev/null +++ b/contracts/native-splitter/src/contract.rs @@ -0,0 +1,204 @@ +use std::collections::BTreeMap; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, to_json_binary, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Order, Response, + StdError, StdResult, +}; +use covenant_utils::split::SplitConfig; +use cw2::set_contract_version; +use valence_clock::helpers::{enqueue_msg, verify_clock}; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use crate::state::{CLOCK_ADDRESS, FALLBACK_SPLIT, SPLIT_CONFIG_MAP}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let mut resp = Response::default().add_attribute("method", "native_splitter_instantiate"); + + let clock_address = deps.api.addr_validate(&msg.clock_address)?; + CLOCK_ADDRESS.save(deps.storage, &clock_address)?; + resp = resp.add_attribute("clock_addr", msg.clock_address.to_string()); + + // we validate the splits and store them per-denom + for (denom, split) in msg.splits { + split.validate_shares_and_receiver_addresses(deps.api)?; + SPLIT_CONFIG_MAP.save(deps.storage, denom.to_string(), &split)?; + } + + // if a fallback split is provided we validate and store it + if let Some(split) = msg.fallback_split { + resp = resp.add_attributes(vec![split.get_response_attribute("fallback".to_string())]); + split.validate_shares_and_receiver_addresses(deps.api)?; + FALLBACK_SPLIT.save(deps.storage, &split)?; + } else { + resp = resp.add_attribute("fallback", "None"); + } + + Ok(resp + .add_message(enqueue_msg(msg.clock_address.as_str())?) + .add_attribute("clock_address", clock_address)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Tick {} => { + verify_clock(&info.sender, &CLOCK_ADDRESS.load(deps.storage)?) + .map_err(|_| ContractError::NotClock)?; + + try_distribute(deps, env) + } + ExecuteMsg::DistributeFallback { denoms } => try_distribute_fallback(deps, env, denoms), + } +} + +pub fn try_distribute(deps: DepsMut, env: Env) -> Result { + // first we query the contract balances + let mut distribution_messages: Vec = vec![]; + + // then we iterate over our split config and try to match the entries to available balances + for entry in SPLIT_CONFIG_MAP.range(deps.storage, None, None, Order::Ascending) { + let (denom, config) = entry?; + let balance = deps + .querier + .query_balance(env.contract.address.clone(), denom.to_string())?; + + if !balance.amount.is_zero() { + let mut transfer_messages = + config.get_transfer_messages(balance.amount, balance.denom.to_string(), None)?; + distribution_messages.append(&mut transfer_messages); + } + } + + Ok(Response::default() + .add_attribute("method", "try_distribute") + .add_messages(distribution_messages)) +} + +fn try_distribute_fallback( + deps: DepsMut, + env: Env, + denoms: Vec, +) -> Result { + let mut distribution_messages: Vec = vec![]; + + if let Some(split) = FALLBACK_SPLIT.may_load(deps.storage)? { + for denom in denoms { + // we do not distribute the main covenant denoms + // according to the fallback split + ensure!( + !SPLIT_CONFIG_MAP.has(deps.storage, denom.to_string()), + ContractError::Std(StdError::generic_err("unauthorized denom distribution")) + ); + + let balance = deps + .querier + .query_balance(env.contract.address.to_string(), denom)?; + if !balance.amount.is_zero() { + let mut fallback_messages = + split.get_transfer_messages(balance.amount, balance.denom, None)?; + distribution_messages.append(&mut fallback_messages); + } + } + } else { + return Err(StdError::generic_err("no fallback split defined").into()); + } + + Ok(Response::default() + .add_attribute("method", "try_distribute_fallback") + .add_messages(distribution_messages)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ClockAddress {} => Ok(to_json_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::DenomSplit { denom } => Ok(to_json_binary(&query_split(deps, denom)?)?), + QueryMsg::Splits {} => Ok(to_json_binary(&query_all_splits(deps)?)?), + QueryMsg::FallbackSplit {} => Ok(to_json_binary(&FALLBACK_SPLIT.may_load(deps.storage)?)?), + QueryMsg::DepositAddress {} => Ok(to_json_binary(&Some(env.contract.address))?), + } +} + +pub fn query_all_splits(deps: Deps) -> Result, StdError> { + let mut splits: Vec<(String, SplitConfig)> = vec![]; + + for entry in SPLIT_CONFIG_MAP.range(deps.storage, None, None, Order::Ascending) { + let (denom, config) = entry?; + splits.push((denom, config)); + } + + Ok(splits) +} + +pub fn query_split(deps: Deps, denom: String) -> Result { + for entry in SPLIT_CONFIG_MAP.range(deps.storage, None, None, Order::Ascending) { + let (entry_denom, config) = entry?; + if entry_denom == denom { + return Ok(config); + } + } + + Ok(SplitConfig { + receivers: BTreeMap::new(), + }) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + match msg { + MigrateMsg::UpdateConfig { + clock_addr, + splits, + fallback_split, + } => { + let mut resp = Response::default().add_attribute("method", "update_config"); + + if let Some(clock_addr) = clock_addr { + CLOCK_ADDRESS.save(deps.storage, &deps.api.addr_validate(&clock_addr)?)?; + resp = resp.add_attribute("clock_addr", clock_addr); + } + + if let Some(splits) = splits { + // clear all current split configs before storing new values + SPLIT_CONFIG_MAP.clear(deps.storage); + for (denom, split) in splits { + // we validate each split before storing it + SPLIT_CONFIG_MAP.save(deps.storage, denom.to_string(), &split)?; + } + } + + if let Some(split) = fallback_split { + FALLBACK_SPLIT.save(deps.storage, &split)?; + resp = + resp.add_attributes(vec![split.get_response_attribute("fallback".to_string())]); + } + + Ok(resp) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} diff --git a/contracts/native-splitter/src/error.rs b/contracts/native-splitter/src/error.rs new file mode 100644 index 00000000..ef410152 --- /dev/null +++ b/contracts/native-splitter/src/error.rs @@ -0,0 +1,17 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Caller is not the clock, only clock can tick contracts")] + NotClock, + + #[error("misconfigured split")] + SplitMisconfig {}, + + #[error("unauthorized caller")] + Unauthorized {}, +} diff --git a/contracts/native-splitter/src/lib.rs b/contracts/native-splitter/src/lib.rs new file mode 100644 index 00000000..47bc573c --- /dev/null +++ b/contracts/native-splitter/src/lib.rs @@ -0,0 +1,6 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/native-splitter/src/msg.rs b/contracts/native-splitter/src/msg.rs new file mode 100644 index 00000000..07601e99 --- /dev/null +++ b/contracts/native-splitter/src/msg.rs @@ -0,0 +1,66 @@ +use std::collections::BTreeMap; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{to_json_binary, Addr, Binary, StdResult, WasmMsg}; +use covenant_macros::{clocked, covenant_clock_address, covenant_deposit_address}; +use covenant_utils::{instantiate2_helper::Instantiate2HelperConfig, split::SplitConfig}; + +#[cw_serde] +pub struct InstantiateMsg { + /// address of the associated clock + pub clock_address: String, + /// maps denom to its split configuration + pub splits: BTreeMap, + /// a split for all denoms that are not covered in the + /// regular `splits` list + pub fallback_split: Option, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +#[clocked] +#[cw_serde] +pub enum ExecuteMsg { + DistributeFallback { denoms: Vec }, +} + +#[covenant_clock_address] +#[covenant_deposit_address] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(SplitConfig)] + DenomSplit { denom: String }, + #[returns(Vec<(String, SplitConfig)>)] + Splits {}, + #[returns(SplitConfig)] + FallbackSplit {}, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + fallback_split: Option, + splits: Option>, + }, + UpdateCodeId { + data: Option, + }, +} diff --git a/contracts/native-splitter/src/state.rs b/contracts/native-splitter/src/state.rs new file mode 100644 index 00000000..8b159147 --- /dev/null +++ b/contracts/native-splitter/src/state.rs @@ -0,0 +1,12 @@ +use cosmwasm_std::Addr; +use covenant_utils::split::SplitConfig; +use cw_storage_plus::{Item, Map}; + +/// clock module address to verify the sender of incoming ticks +pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); + +/// maps a denom string to a vec of SplitReceivers +pub const SPLIT_CONFIG_MAP: Map = Map::new("split_config"); + +/// split for all denoms that are not explicitly defined in SPLIT_CONFIG_MAP +pub const FALLBACK_SPLIT: Item = Item::new("fallback_split"); diff --git a/contracts/osmo-liquid-pooler/.cargo/config b/contracts/osmo-liquid-pooler/.cargo/config new file mode 100644 index 00000000..6a6f2852 --- /dev/null +++ b/contracts/osmo-liquid-pooler/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" \ No newline at end of file diff --git a/contracts/osmo-liquid-pooler/Cargo.toml b/contracts/osmo-liquid-pooler/Cargo.toml new file mode 100644 index 00000000..ec150c67 --- /dev/null +++ b/contracts/osmo-liquid-pooler/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "valence-osmo-liquid-pooler" +authors = ["benskey bekauz@protonmail.com"] +description = "Osmosis liquid pooler contract for covenants" +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } +edition = { workspace = true } + +exclude = ["contract.wasm", "hash.txt"] + + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +covenant-macros = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +covenant-utils = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +# the sha2 version here is the same as the one used by +# cosmwasm-std. when bumping cosmwasm-std, this should also be +# updated. to find cosmwasm_std's sha function: +# ```cargo tree --package cosmwasm-std``` +sha2 = { workspace = true } +neutron-sdk = { workspace = true } +schemars = { workspace = true } +bech32 = { workspace = true } +cw20 = { workspace = true } +polytone = { workspace = true } +osmosis-std = "0.20.1" +prost = { workspace = true } +valence-outpost-osmo-liquid-pooler = { workspace = true, features = ["library"] } diff --git a/contracts/osmo-liquid-pooler/README.md b/contracts/osmo-liquid-pooler/README.md new file mode 100644 index 00000000..57f7999c --- /dev/null +++ b/contracts/osmo-liquid-pooler/README.md @@ -0,0 +1,98 @@ +# osmo liquid pooler + +Contract responsible for providing liquidity to a specified pool on the Osmosis dex. +Currently we only support GAMM pools where both tokens have equal weights. + +The contract receives the target denoms, provides liquidity to the specified +pool, and withdraws the liquidity tokens from osmosis to this contract. The +holder is then responsible for calling this contract to redeem the LP tokens +for the underlying assets, which are then forwarder to the holder. + +Works in tandem with the [osmosis liquid pooler outpost](../outpost-osmo-liquid-pooler/README.md), +in order to ensure atomic liquidity provision given the ibc nature of this design. + +## flow + +The expected state transitions are as follows: + +### 1. `Instantiated` + +Ticks incoming to a contract in instantiated state will attempt to create a +proxy account on Osmosis via [Polytone](https://github.com/DA0-DA0/polytone). +This means submitting an empty wasm `Execute` message to the note contract. +Note then relays this message to voice, which will in turn instantiate a proxy +associated with the original caller (this contract). + +After proxy is created, voice will submit a callback to note. With that callback, +note associates this contract address with the created proxy address and exposes +this association via `RemoteAddress` query. Note also calls back into our contract +which is expecting a callback. + +In our contract callback handler, we will query the note for our remote address. +There are two possible cases here: + +First case - no address is returned. This means that something went wrong. +We do not advance our state machine and remain in `Instantiated` state. +This means that upon next tick, we will repeat this process until note returns +an address. + +Second case - an address is returned. We then store that address, and advance +the state machine to `ProxyCreated`. + + +### 2. `ProxyCreated` + +Ticks incoming to a contract with a created proxy will atempt to fund the proxy. +Proxy being funded is a prerequisite for providing liquidity, and we only want to +attempt providing liquidity if we have delivered all of our funds. + +Because of the async nature of IBC, we need to keep things relevant for providing +liquidity up to date. One of such things are balances of our proxy account. Upon +contract instantiation, we do not store balances of the proxy account, because +we do not even have a proxy. + +After proxy is created, the first attempt to deliver funds will find that proxy +balances are unknown. This triggers a proxy denom query. Via polytone, we submit +three query requests of our proxy address to our note - one for each of the +relevant denoms (e.g. ATOM, OSMO, and the relevant LP token). Once again we attach +a callback request. + +Once the balances get queried on osmosis, voice submits the query results to our +note. Note then calls into our contract callback handler, in which we deserialize +the query responses and reflect the fresh balances in our storage. + +After balances are fresh - we once again try to deliver funds. This time we can +see that our proxy has no/insufficient tokens for our desired liquidity provision. + +We then query our own balances of relevant tokens, and attempt to transfer them +directly to our proxy over ibc, without using polytone. + +Upon next tick, if the transfers went well, the contract will not have enough funds +to fund the proxy (as they have already been transferred). This will trigger a +re-query of our proxy balances. + +The tick after that will assert that the latest proxy balances match our expectations +for providing liquidity. The state is then advanced to `ProxyFunded`. If balances are +insufficient, we will try to ibc fund the proxy again, restarting the flow. + +### 3. `ProxyFunded` + +Ticks incoming to a contract with a funded proxy will attempt to provide liquidity. +This means two things. + +First, that we construct a polytone message that will be sent to our osmosis outpost. +This message will contain all balances that the proxy holds, and will attempt to +provide liquidity according to our config. + +The second message will once again trigger a balances query. + +We submit these two messages in order: first we attempt to provide liquidity, and +after that we query the proxy balances. If providing liquidity succeeded, we will +see that reflected by the available gamm token (and reduced target denoms) balance. + +Now, remember that callback we receive from note after querying the proxy balances? +It does one more thing. After deserializing and updating our latest proxy balances, +it checks if there are any LP tokens in the proxy. Nothing happens in case where +that balance is 0. Otherwise, however, we submit a polytone message to the note, +which instructs the proxy to perform an ibc transfer of those balances back to this +contract. diff --git a/contracts/osmo-liquid-pooler/schema/valence-osmo-liquid-pooler.json b/contracts/osmo-liquid-pooler/schema/valence-osmo-liquid-pooler.json new file mode 100644 index 00000000..7313e3f4 --- /dev/null +++ b/contracts/osmo-liquid-pooler/schema/valence-osmo-liquid-pooler.json @@ -0,0 +1,1525 @@ +{ + "contract_name": "valence-osmo-liquid-pooler", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "clock_address", + "funding_duration", + "holder_address", + "lp_token_denom", + "note_address", + "osmo_ibc_timeout", + "osmo_outpost", + "osmo_to_neutron_channel_id", + "party_1_chain_info", + "party_1_denom_info", + "party_2_chain_info", + "party_2_denom_info", + "pool_id", + "pool_price_config", + "single_side_lp_limits" + ], + "properties": { + "clock_address": { + "type": "string" + }, + "funding_duration": { + "$ref": "#/definitions/Duration" + }, + "holder_address": { + "type": "string" + }, + "lp_token_denom": { + "type": "string" + }, + "note_address": { + "type": "string" + }, + "osmo_ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "osmo_outpost": { + "type": "string" + }, + "osmo_to_neutron_channel_id": { + "type": "string" + }, + "party_1_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + }, + "party_1_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "party_2_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + }, + "party_2_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "pool_id": { + "$ref": "#/definitions/Uint64" + }, + "pool_price_config": { + "$ref": "#/definitions/PoolPriceConfig" + }, + "single_side_lp_limits": { + "$ref": "#/definitions/SingleSideLpLimits" + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "ForwardMetadata": { + "type": "object", + "required": [ + "channel", + "port", + "receiver" + ], + "properties": { + "channel": { + "type": "string" + }, + "port": { + "type": "string" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PartyChainInfo": { + "type": "object", + "required": [ + "ibc_timeout", + "neutron_to_party_chain_channel", + "party_chain_to_neutron_channel" + ], + "properties": { + "ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "inwards_pfm": { + "description": "pfm configuration used to route funds from osmosis to local chain via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "neutron_to_party_chain_channel": { + "description": "channel id to route funds from local chain to party chain", + "type": "string" + }, + "outwards_pfm": { + "description": "pfm configuration used to route funds from local chain to osmosis via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "party_chain_to_neutron_channel": { + "description": "channel id to route funds from party chain to local chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PartyDenomInfo": { + "type": "object", + "required": [ + "local_denom", + "osmosis_coin" + ], + "properties": { + "local_denom": { + "description": "ibc denom on liquid pooler chain", + "type": "string" + }, + "osmosis_coin": { + "description": "coin as denominated on osmosis", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "PoolPriceConfig": { + "description": "config for the pool price expectations upon covenant instantiation", + "type": "object", + "required": [ + "acceptable_price_spread", + "expected_spot_price" + ], + "properties": { + "acceptable_price_spread": { + "$ref": "#/definitions/Decimal" + }, + "expected_spot_price": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "SingleSideLpLimits": { + "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", + "type": "object", + "required": [ + "asset_a_limit", + "asset_b_limit" + ], + "properties": { + "asset_a_limit": { + "$ref": "#/definitions/Uint128" + }, + "asset_b_limit": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "callback" + ], + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMessage" + } + }, + "additionalProperties": false + }, + { + "description": "Wakes the state machine up. The caller should check the sender of the tick is the clock if they'd like to pause when the clock does.", + "type": "object", + "required": [ + "tick" + ], + "properties": { + "tick": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Tells the LPer to withdraw his position Should only be called by the holder of the covenant", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "properties": { + "percentage": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Attribute": { + "description": "An key value pair that is used in the context of event attributes in logs", + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Callback": { + "oneOf": [ + { + "description": "Result of executing the requested query, or an error.\n\nresult[i] corresponds to the i'th query and contains the base64 encoded query response.", + "type": "object", + "required": [ + "query" + ], + "properties": { + "query": { + "$ref": "#/definitions/Result_of_Array_of_Binary_or_ErrorResponse" + } + }, + "additionalProperties": false + }, + { + "description": "Result of executing the requested messages, or an error.\n\n14/04/23: if a submessage errors the reply handler can see `codespace: wasm, code: 5`, but not the actual error. as a result, we can't return good errors for Execution and this error string will only tell you the error's codespace. for example, an out-of-gas error is code 11 and looks like `codespace: sdk, code: 11`.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "$ref": "#/definitions/Result_of_ExecutionResponse_or_String" + } + }, + "additionalProperties": false + }, + { + "description": "An error occured that could not be recovered from. The only known way that this can occur is message handling running out of gas, in which case the error will be `codespace: sdk, code: 11`.\n\nThis error is not named becuase it could also occur due to a panic or unhandled error during message processing. We don't expect this to happen and have carefully written the code to avoid it.", + "type": "object", + "required": [ + "fatal_error" + ], + "properties": { + "fatal_error": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "CallbackMessage": { + "description": "Executed on the callback receiver upon message completion. When being executed, the message will be tagged with \"callback\":\n\n```json {\"callback\": { \"initiator\": ..., \"initiator_msg\": ..., \"result\": ..., }} ```", + "type": "object", + "required": [ + "initiator", + "initiator_msg", + "result" + ], + "properties": { + "initiator": { + "description": "Initaitor on the note chain.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "initiator_msg": { + "description": "Message sent by the initaitor. This _must_ be base64 encoded or execution will fail.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "result": { + "description": "Data from the host chain.", + "allOf": [ + { + "$ref": "#/definitions/Callback" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "ErrorResponse": { + "type": "object", + "required": [ + "error", + "message_index" + ], + "properties": { + "error": { + "description": "The error that occured executing the message.", + "type": "string" + }, + "message_index": { + "description": "The index of the first message who's execution failed.", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + } + }, + "additionalProperties": false + }, + "Event": { + "description": "A full [*Cosmos SDK* event].\n\nThis version uses string attributes (similar to [*Cosmos SDK* StringEvent]), which then get magically converted to bytes for Tendermint somewhere between the Rust-Go interface, JSON deserialization and the `NewEvent` call in Cosmos SDK.\n\n[*Cosmos SDK* event]: https://docs.cosmos.network/main/learn/advanced/events [*Cosmos SDK* StringEvent]: https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/base/abci/v1beta1/abci.proto#L56-L70", + "type": "object", + "required": [ + "attributes", + "type" + ], + "properties": { + "attributes": { + "description": "The attributes to be included in the event.\n\nYou can learn more about these from [*Cosmos SDK* docs].\n\n[*Cosmos SDK* docs]: https://docs.cosmos.network/main/learn/advanced/events", + "type": "array", + "items": { + "$ref": "#/definitions/Attribute" + } + }, + "type": { + "description": "The event type. This is renamed to \"ty\" because \"type\" is reserved in Rust. This sucks, we know.", + "type": "string" + } + } + }, + "ExecutionResponse": { + "type": "object", + "required": [ + "executed_by", + "result" + ], + "properties": { + "executed_by": { + "description": "The address on the remote chain that executed the messages.", + "type": "string" + }, + "result": { + "description": "Index `i` corresponds to the result of executing the `i`th message.", + "type": "array", + "items": { + "$ref": "#/definitions/SubMsgResponse" + } + } + }, + "additionalProperties": false + }, + "Result_of_Array_of_Binary_or_ErrorResponse": { + "oneOf": [ + { + "type": "object", + "required": [ + "Ok" + ], + "properties": { + "Ok": { + "type": "array", + "items": { + "$ref": "#/definitions/Binary" + } + } + } + }, + { + "type": "object", + "required": [ + "Err" + ], + "properties": { + "Err": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + ] + }, + "Result_of_ExecutionResponse_or_String": { + "oneOf": [ + { + "type": "object", + "required": [ + "Ok" + ], + "properties": { + "Ok": { + "$ref": "#/definitions/ExecutionResponse" + } + } + }, + { + "type": "object", + "required": [ + "Err" + ], + "properties": { + "Err": { + "type": "string" + } + } + } + ] + }, + "SubMsgResponse": { + "description": "The information we get back from a successful sub message execution, with full Cosmos SDK events.", + "type": "object", + "required": [ + "events" + ], + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/Event" + } + } + } + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "contract_state" + ], + "properties": { + "contract_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "holder_address" + ], + "properties": { + "holder_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "liquidity_provision_config" + ], + "properties": { + "liquidity_provision_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc_config" + ], + "properties": { + "ibc_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "proxy_address" + ], + "properties": { + "proxy_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "callbacks" + ], + "properties": { + "callbacks": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated clock address authorized to submit ticks", + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the address a contract expects to receive funds to", + "type": "object", + "required": [ + "deposit_address" + ], + "properties": { + "deposit_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "holder_address": { + "type": [ + "string", + "null" + ] + }, + "ibc_config": { + "anyOf": [ + { + "$ref": "#/definitions/IbcConfig" + }, + { + "type": "null" + } + ] + }, + "lp_config": { + "anyOf": [ + { + "$ref": "#/definitions/LiquidityProvisionConfig" + }, + { + "type": "null" + } + ] + }, + "note_address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "ForwardMetadata": { + "type": "object", + "required": [ + "channel", + "port", + "receiver" + ], + "properties": { + "channel": { + "type": "string" + }, + "port": { + "type": "string" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + }, + "IbcConfig": { + "type": "object", + "required": [ + "osmo_ibc_timeout", + "osmo_to_neutron_channel_id", + "party_1_chain_info", + "party_2_chain_info" + ], + "properties": { + "osmo_ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "osmo_to_neutron_channel_id": { + "type": "string" + }, + "party_1_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + }, + "party_2_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + } + }, + "additionalProperties": false + }, + "LiquidityProvisionConfig": { + "type": "object", + "required": [ + "funding_duration", + "latest_balances", + "lp_token_denom", + "outpost", + "party_1_denom_info", + "party_2_denom_info", + "pool_id", + "pool_price_config", + "single_side_lp_limits" + ], + "properties": { + "funding_duration": { + "$ref": "#/definitions/Duration" + }, + "latest_balances": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Coin" + } + }, + "lp_token_denom": { + "type": "string" + }, + "outpost": { + "type": "string" + }, + "party_1_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "party_2_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "pool_id": { + "$ref": "#/definitions/Uint64" + }, + "pool_price_config": { + "$ref": "#/definitions/PoolPriceConfig" + }, + "single_side_lp_limits": { + "$ref": "#/definitions/SingleSideLpLimits" + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "PartyChainInfo": { + "type": "object", + "required": [ + "ibc_timeout", + "neutron_to_party_chain_channel", + "party_chain_to_neutron_channel" + ], + "properties": { + "ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "inwards_pfm": { + "description": "pfm configuration used to route funds from osmosis to local chain via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "neutron_to_party_chain_channel": { + "description": "channel id to route funds from local chain to party chain", + "type": "string" + }, + "outwards_pfm": { + "description": "pfm configuration used to route funds from local chain to osmosis via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "party_chain_to_neutron_channel": { + "description": "channel id to route funds from party chain to local chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PartyDenomInfo": { + "type": "object", + "required": [ + "local_denom", + "osmosis_coin" + ], + "properties": { + "local_denom": { + "description": "ibc denom on liquid pooler chain", + "type": "string" + }, + "osmosis_coin": { + "description": "coin as denominated on osmosis", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "PoolPriceConfig": { + "description": "config for the pool price expectations upon covenant instantiation", + "type": "object", + "required": [ + "acceptable_price_spread", + "expected_spot_price" + ], + "properties": { + "acceptable_price_spread": { + "$ref": "#/definitions/Decimal" + }, + "expected_spot_price": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "SingleSideLpLimits": { + "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", + "type": "object", + "required": [ + "asset_a_limit", + "asset_b_limit" + ], + "properties": { + "asset_a_limit": { + "$ref": "#/definitions/Uint128" + }, + "asset_b_limit": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "sudo": null, + "responses": { + "callbacks": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } + }, + "clock_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "contract_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractState", + "description": "state of the LP state machine", + "oneOf": [ + { + "type": "string", + "enum": [ + "instantiated", + "proxy_created", + "active" + ] + }, + { + "type": "object", + "required": [ + "proxy_funded" + ], + "properties": { + "proxy_funded": { + "type": "object", + "required": [ + "funding_expiration" + ], + "properties": { + "funding_expiration": { + "$ref": "#/definitions/Expiration" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distributing" + ], + "properties": { + "distributing": { + "type": "object", + "required": [ + "coins" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pending_withdrawal" + ], + "properties": { + "pending_withdrawal": { + "type": "object", + "required": [ + "share" + ], + "properties": { + "share": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "deposit_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "holder_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ibc_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IbcConfig", + "type": "object", + "required": [ + "osmo_ibc_timeout", + "osmo_to_neutron_channel_id", + "party_1_chain_info", + "party_2_chain_info" + ], + "properties": { + "osmo_ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "osmo_to_neutron_channel_id": { + "type": "string" + }, + "party_1_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + }, + "party_2_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + } + }, + "additionalProperties": false, + "definitions": { + "ForwardMetadata": { + "type": "object", + "required": [ + "channel", + "port", + "receiver" + ], + "properties": { + "channel": { + "type": "string" + }, + "port": { + "type": "string" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PartyChainInfo": { + "type": "object", + "required": [ + "ibc_timeout", + "neutron_to_party_chain_channel", + "party_chain_to_neutron_channel" + ], + "properties": { + "ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "inwards_pfm": { + "description": "pfm configuration used to route funds from osmosis to local chain via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "neutron_to_party_chain_channel": { + "description": "channel id to route funds from local chain to party chain", + "type": "string" + }, + "outwards_pfm": { + "description": "pfm configuration used to route funds from local chain to osmosis via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "party_chain_to_neutron_channel": { + "description": "channel id to route funds from party chain to local chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "liquidity_provision_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LiquidityProvisionConfig", + "type": "object", + "required": [ + "funding_duration", + "latest_balances", + "lp_token_denom", + "outpost", + "party_1_denom_info", + "party_2_denom_info", + "pool_id", + "pool_price_config", + "single_side_lp_limits" + ], + "properties": { + "funding_duration": { + "$ref": "#/definitions/Duration" + }, + "latest_balances": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Coin" + } + }, + "lp_token_denom": { + "type": "string" + }, + "outpost": { + "type": "string" + }, + "party_1_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "party_2_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "pool_id": { + "$ref": "#/definitions/Uint64" + }, + "pool_price_config": { + "$ref": "#/definitions/PoolPriceConfig" + }, + "single_side_lp_limits": { + "$ref": "#/definitions/SingleSideLpLimits" + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "PartyDenomInfo": { + "type": "object", + "required": [ + "local_denom", + "osmosis_coin" + ], + "properties": { + "local_denom": { + "description": "ibc denom on liquid pooler chain", + "type": "string" + }, + "osmosis_coin": { + "description": "coin as denominated on osmosis", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "PoolPriceConfig": { + "description": "config for the pool price expectations upon covenant instantiation", + "type": "object", + "required": [ + "acceptable_price_spread", + "expected_spot_price" + ], + "properties": { + "acceptable_price_spread": { + "$ref": "#/definitions/Decimal" + }, + "expected_spot_price": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "SingleSideLpLimits": { + "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", + "type": "object", + "required": [ + "asset_a_limit", + "asset_b_limit" + ], + "properties": { + "asset_a_limit": { + "$ref": "#/definitions/Uint128" + }, + "asset_b_limit": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "proxy_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/osmo-liquid-pooler/src/bin/schema.rs b/contracts/osmo-liquid-pooler/src/bin/schema.rs new file mode 100644 index 00000000..a2a51c7e --- /dev/null +++ b/contracts/osmo-liquid-pooler/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use valence_osmo_liquid_pooler::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/osmo-liquid-pooler/src/contract.rs b/contracts/osmo-liquid-pooler/src/contract.rs new file mode 100644 index 00000000..03f72eeb --- /dev/null +++ b/contracts/osmo-liquid-pooler/src/contract.rs @@ -0,0 +1,854 @@ +use std::collections::HashMap; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, to_json_binary, to_json_string, Attribute, Binary, Coin, CosmosMsg, Decimal, Env, + Fraction, IbcTimeout, MessageInfo, Response, StdError, StdResult, Uint128, WasmMsg, +}; +use covenant_utils::{ + polytone::get_polytone_execute_msg_binary, withdraw_lp_helper::WithdrawLPMsgs, ForwardMetadata, + PacketMetadata, +}; +use cw2::set_contract_version; +use neutron_sdk::{ + bindings::{ + msg::{IbcFee, NeutronMsg}, + query::NeutronQuery, + }, + query::min_ibc_fee::MinIbcFeeResponse, + sudo::msg::RequestPacketTimeoutHeight, + NeutronResult, +}; +use polytone::callbacks::CallbackRequest; +use valence_clock::helpers::{enqueue_msg, verify_clock}; +use valence_outpost_osmo_liquid_pooler::msg::OutpostWithdrawLiquidityConfig; + +use crate::{ + error::ContractError, + msg::{ + ContractState, ExecuteMsg, IbcConfig, InstantiateMsg, LiquidityProvisionConfig, MigrateMsg, + PartyChainInfo, QueryMsg, + }, + polytone_handlers::{ + get_ibc_pfm_withdraw_coin_message, get_ibc_withdraw_coin_message, + get_note_execute_neutron_msg, get_proxy_query_balances_message, try_handle_callback, + }, + state::{ + HOLDER_ADDRESS, IBC_CONFIG, LIQUIDITY_PROVISIONING_CONFIG, NOTE_ADDRESS, + POLYTONE_CALLBACKS, PROXY_ADDRESS, + }, +}; + +use crate::state::{CLOCK_ADDRESS, CONTRACT_STATE}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub(crate) const PROVIDE_LIQUIDITY_CALLBACK_ID: u8 = 1; +pub(crate) const PROXY_BALANCES_QUERY_CALLBACK_ID: u8 = 2; +pub(crate) const CREATE_PROXY_CALLBACK_ID: u8 = 3; +pub(crate) const WITHDRAW_LIQUIDITY_CALLBACK_ID: u8 = 4; + +type ExecuteDeps<'a> = cosmwasm_std::DepsMut<'a, NeutronQuery>; +type QueryDeps<'a> = cosmwasm_std::Deps<'a, NeutronQuery>; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: ExecuteDeps, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + // validate the contract addresses + let clock_addr = deps.api.addr_validate(&msg.clock_address)?; + let holder_addr = deps.api.addr_validate(&msg.holder_address)?; + let note_addr = deps.api.addr_validate(&msg.note_address)?; + + // contract starts at Instantiated state + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + + // store the relevant contract addresses + CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; + HOLDER_ADDRESS.save(deps.storage, &holder_addr)?; + NOTE_ADDRESS.save(deps.storage, ¬e_addr)?; + + // initialize polytone state sync related items + let latest_balances: HashMap = HashMap::new(); + let lp_config = LiquidityProvisionConfig { + latest_balances, + party_1_denom_info: msg.party_1_denom_info, + party_2_denom_info: msg.party_2_denom_info, + pool_id: msg.pool_id, + outpost: msg.osmo_outpost, + lp_token_denom: msg.lp_token_denom, + slippage_tolerance: msg.slippage_tolerance, + pool_price_config: msg.pool_price_config, + funding_duration: msg.funding_duration, + single_side_lp_limits: msg.single_side_lp_limits, + }; + LIQUIDITY_PROVISIONING_CONFIG.save(deps.storage, &lp_config)?; + + let ibc_config = IbcConfig { + party_1_chain_info: msg.party_1_chain_info, + party_2_chain_info: msg.party_2_chain_info, + osmo_to_neutron_channel_id: msg.osmo_to_neutron_channel_id, + osmo_ibc_timeout: msg.osmo_ibc_timeout, + }; + IBC_CONFIG.save(deps.storage, &ibc_config)?; + + Ok(Response::default() + .add_message(enqueue_msg(clock_addr.as_str())?) + .add_attribute("method", "osmosis_lp_instantiate") + .add_attribute("contract_state", "instantiated") + .add_attributes(lp_config.to_response_attributes()) + .add_attributes(ibc_config.to_response_attributes()) + .add_attribute("note_address", note_addr) + .add_attribute("holder_address", holder_addr) + .add_attribute("clock_address", clock_addr)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: ExecuteDeps, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> NeutronResult> { + match msg { + ExecuteMsg::Tick {} => try_tick(deps, env, info), + ExecuteMsg::Callback(callback_msg) => try_handle_callback(env, deps, info, callback_msg), + ExecuteMsg::Withdraw { percentage } => try_initiate_withdrawal(deps, info, percentage), + } +} + +/// we initiate the withdrawal phase by setting the contract state to `PendingWithdrawal` +fn try_initiate_withdrawal( + deps: ExecuteDeps, + info: MessageInfo, + percentage: Option, +) -> NeutronResult> { + ensure!( + info.sender == HOLDER_ADDRESS.load(deps.storage)?, + ContractError::NotHolder {}.to_neutron_std() + ); + + let withdraw_share = percentage.unwrap_or(Decimal::one()); + + ensure!( + withdraw_share <= Decimal::one() && withdraw_share > Decimal::zero(), + ContractError::Std(StdError::generic_err(format!( + "withdraw percentage must be in range (0, 1], got {:?}", + withdraw_share + ),)) + .to_neutron_std() + ); + + // we advance the contract state to `PendingWithdrawal` and force latest balances sync + CONTRACT_STATE.save( + deps.storage, + &ContractState::PendingWithdrawal { + share: withdraw_share, + }, + )?; + LIQUIDITY_PROVISIONING_CONFIG.update(deps.storage, |mut lp_config| -> StdResult<_> { + lp_config.reset_latest_proxy_balances(); + Ok(lp_config) + })?; + + Ok(Response::default() + .add_attribute("method", "try_initiate_withdrawal") + .add_attribute("contract_state", "pending_withdrawal") + .add_attribute("pending_withdrawal", withdraw_share.to_string())) +} + +/// this method will attempt to set the contract state to `Distributing`, +/// with any non-lp token denoms available on our remote proxy. +/// doing so will cause upcoming ticks to try to send the withdrawn coins +/// to the holder, completing the withdrawal flow and reverting this contract +/// to active state. +fn withdraw_party_denoms( + deps: ExecuteDeps, + p1_proxy_bal: &Coin, + p2_proxy_bal: &Coin, +) -> NeutronResult> { + // if either denom balance is non-zero, collect them + let mut withdraw_coins: Vec = Vec::with_capacity(2); + if p1_proxy_bal.amount > Uint128::zero() { + withdraw_coins.push(p1_proxy_bal.clone()); + } + if p2_proxy_bal.amount > Uint128::zero() { + withdraw_coins.push(p2_proxy_bal.clone()); + } + + if withdraw_coins.is_empty() { + // both both balances are zero, we revert the state to active + // and ping a WithdrawFailed message to the holder. + let withdraw_failed_msg = WasmMsg::Execute { + contract_addr: HOLDER_ADDRESS.load(deps.storage)?.to_string(), + msg: to_json_binary(&WithdrawLPMsgs::WithdrawFailed {})?, + funds: vec![], + }; + CONTRACT_STATE.save(deps.storage, &ContractState::Active)?; + Ok(Response::default() + .add_attribute("method", "withdraw_party_denoms") + .add_attribute("contract_state", "active") + .add_attribute("p1_balance", p1_proxy_bal.to_string()) + .add_attribute("p2_balance", p2_proxy_bal.to_string()) + .add_message(withdraw_failed_msg)) + } else { + // otherwise we set the contract state to distributing. + // this will cause incoming ticks to assert whether this contract had received the funds. + // if the funds are not received, the tick will attempt to withdraw them again. + // if all expected coins are received, the contract will submit `Distribute` message to the holder. + CONTRACT_STATE.save( + deps.storage, + &ContractState::Distributing { + coins: withdraw_coins.clone(), + }, + )?; + + Ok(Response::default() + .add_attribute("method", "withdraw_party_denoms") + .add_attribute("contract_state", "distributing") + .add_attribute("p1_balance", p1_proxy_bal.to_string()) + .add_attribute("p2_balance", p2_proxy_bal.to_string()) + .add_attribute("coins", to_json_string(&withdraw_coins)?)) + } +} + +pub fn try_withdraw( + deps: ExecuteDeps, + env: Env, + withdraw_share: Decimal, + (party_1_bal, party_2_bal, lp_bal): (&Coin, &Coin, &Coin), + lp_config: LiquidityProvisionConfig, +) -> NeutronResult> { + let note_address = NOTE_ADDRESS.load(deps.storage)?; + let ibc_config = IBC_CONFIG.load(deps.storage)?; + + // if there are 0 available lp token balances, we attempt to + // withdraw the party denoms directly. + if lp_bal.amount.is_zero() { + return withdraw_party_denoms(deps, party_1_bal, party_2_bal); + } + + let lp_redeem_amount = lp_bal + .amount + .checked_multiply_ratio(withdraw_share.numerator(), withdraw_share.denominator()) + .map_err(|e| ContractError::CheckedMultiplyError(e).to_neutron_std())?; + + let exit_pool_message: CosmosMsg = WasmMsg::Execute { + contract_addr: lp_config.outpost.to_string(), + msg: to_json_binary( + &valence_outpost_osmo_liquid_pooler::msg::ExecuteMsg::WithdrawLiquidity { + config: OutpostWithdrawLiquidityConfig { + pool_id: lp_config.pool_id, + }, + }, + )?, + funds: vec![Coin { + denom: lp_config.lp_token_denom.to_string(), + amount: lp_redeem_amount, + }], + } + .into(); + + POLYTONE_CALLBACKS.save( + deps.storage, + format!( + "neutron_try_withdraw_liquidity : {:?}", + env.block.height.to_string() + ), + &to_json_string(&exit_pool_message)?, + )?; + + let exit_pool_note_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: note_address.to_string(), + msg: get_polytone_execute_msg_binary( + vec![exit_pool_message], + Some(CallbackRequest { + receiver: env.contract.address.to_string(), + msg: to_json_binary(&WITHDRAW_LIQUIDITY_CALLBACK_ID)?, + }), + ibc_config.osmo_ibc_timeout, + )?, + funds: vec![], + }); + + Ok(Response::default().add_messages(vec![exit_pool_note_msg])) +} + +/// attempts to advance the state machine. performs `info.sender` validation. +fn try_tick(deps: ExecuteDeps, env: Env, info: MessageInfo) -> NeutronResult> { + // Verify caller is the clock + verify_clock(&info.sender, &CLOCK_ADDRESS.load(deps.storage)?)?; + + match CONTRACT_STATE.load(deps.storage)? { + // create a proxy account + ContractState::Instantiated => try_create_proxy(deps, env), + // fund the proxy account + ContractState::ProxyCreated => try_deliver_funds(deps, env), + // attempt to provide liquidity + ContractState::ProxyFunded { funding_expiration } => { + // the funding expiration is due, we advance the state to + // Active. it will enable withdrawals and start pulling + // any non-LP tokens from proxy back to this contract. + if funding_expiration.is_expired(&env.block) { + CONTRACT_STATE.save(deps.storage, &ContractState::Active)?; + Ok(Response::default() + .add_attribute("method", "tick") + .add_attribute("contract_state", "active")) + } else { + // otherwise we attempt to provide liquidity + try_provide_liquidity(deps, env) + } + } + ContractState::Active => try_sync_proxy_balances(deps, env), + ContractState::PendingWithdrawal { share } => { + let lp_config = LIQUIDITY_PROVISIONING_CONFIG.load(deps.storage)?; + match lp_config.get_proxy_balances() { + Some((party_1_bal, party_2_bal, lp_bal)) => try_withdraw( + deps, + env, + share, + (party_1_bal, party_2_bal, lp_bal), + lp_config.clone(), + ), + None => try_sync_proxy_balances(deps, env), + } + } + ContractState::Distributing { coins } => try_distribute(deps, env, coins), + } +} + +fn try_distribute( + deps: ExecuteDeps, + env: Env, + coins: Vec, +) -> NeutronResult> { + let mut lp_config = LIQUIDITY_PROVISIONING_CONFIG.load(deps.storage)?; + // query our own relevant token denoms + let denom_1_balance = deps.querier.query_balance( + env.contract.address.to_string(), + lp_config.party_1_denom_info.local_denom.to_string(), + )?; + let denom_2_balance = deps.querier.query_balance( + env.contract.address.to_string(), + lp_config.party_2_denom_info.local_denom.to_string(), + )?; + + // we map the withdrawn coins to match the party denoms + let withdrawn_coin_1 = match coins + .iter() + .find(|c| c.denom == lp_config.party_1_denom_info.osmosis_coin.denom) + { + Some(c) => c.clone(), + None => Coin { + denom: lp_config.party_1_denom_info.osmosis_coin.denom.to_string(), + amount: Uint128::zero(), + }, + }; + + let withdrawn_coin_2 = match coins + .iter() + .find(|c| c.denom == lp_config.party_2_denom_info.osmosis_coin.denom) + { + Some(c) => c.clone(), + None => Coin { + denom: lp_config.party_2_denom_info.osmosis_coin.denom.to_string(), + amount: Uint128::zero(), + }, + }; + + // verify if denom 1 was successfully withdrawn + let denom_1_withdrawn = denom_1_balance.amount >= withdrawn_coin_1.amount; + let denom_2_withdrawn = denom_2_balance.amount >= withdrawn_coin_2.amount; + + // send tokens to holder and revert state to active + if denom_1_withdrawn && denom_2_withdrawn { + let holder_addr = HOLDER_ADDRESS.load(deps.storage)?; + CONTRACT_STATE.save(deps.storage, &ContractState::Active)?; + + // submit a Distribute message to the holder, + // which carries the withdrawn coins along. + // this is basically a callback confirming that + // the withdrawal flow had been handled successfully. + let holder_distribute_callback_msg = WasmMsg::Execute { + contract_addr: holder_addr.to_string(), + msg: to_json_binary(&WithdrawLPMsgs::Distribute {})?, + funds: vec![denom_1_balance, denom_2_balance], + }; + + // reset the proxy balances to trigger a query + lp_config.reset_latest_proxy_balances(); + LIQUIDITY_PROVISIONING_CONFIG.save(deps.storage, &lp_config)?; + + return Ok(Response::default() + .add_attribute("method", "holder_distribute_callback") + .add_message(holder_distribute_callback_msg)); + } + + let mut ibc_withdraw_msgs: Vec = vec![]; + let ibc_config = IBC_CONFIG.load(deps.storage)?; + let note_address = NOTE_ADDRESS.load(deps.storage)?; + let proxy_address = PROXY_ADDRESS.load(deps.storage)?; + + let mut attributes: Vec = vec![ + Attribute::new("withdrawn_coin_1", to_json_string(&withdrawn_coin_1)?), + Attribute::new("withdrawn_coin_2", to_json_string(&withdrawn_coin_2)?), + Attribute::new("denom_1_balance", to_json_string(&denom_1_balance)?), + Attribute::new("denom_2_balance", to_json_string(&denom_2_balance)?), + ]; + + if !denom_1_withdrawn && !withdrawn_coin_1.amount.is_zero() { + // withdraw denom 1 + match &ibc_config.party_1_chain_info.inwards_pfm { + Some(forward_metadata) => { + let msg = get_ibc_pfm_withdraw_coin_message( + forward_metadata.channel.to_string(), + proxy_address.to_string(), + forward_metadata.receiver.to_string(), + withdrawn_coin_1, + env.block + .time + .plus_seconds(2 * ibc_config.osmo_ibc_timeout.u64()) + .nanos(), + to_json_string(&PacketMetadata { + forward: Some(ForwardMetadata { + receiver: env.contract.address.to_string(), + port: forward_metadata.port.to_string(), + channel: ibc_config.party_1_chain_info.party_chain_to_neutron_channel, + }), + })?, + ); + attributes.push(Attribute::new( + "denom_2_ibc_distribution", + to_json_string(&msg)?, + )); + ibc_withdraw_msgs.push(msg); + } + None => { + let msg = get_ibc_withdraw_coin_message( + ibc_config.osmo_to_neutron_channel_id.to_string(), + env.contract.address.to_string(), + withdrawn_coin_1, + IbcTimeout::with_timestamp( + env.block + .time + .plus_seconds(2 * ibc_config.osmo_ibc_timeout.u64()), + ), + ); + attributes.push(Attribute::new( + "denom_2_ibc_distribution", + to_json_string(&msg)?, + )); + ibc_withdraw_msgs.push(msg); + } + } + } + if !denom_2_withdrawn && !withdrawn_coin_2.amount.is_zero() { + // withdraw denom 2 + match &ibc_config.party_2_chain_info.inwards_pfm { + Some(forward_metadata) => { + let msg = get_ibc_pfm_withdraw_coin_message( + forward_metadata.channel.to_string(), + proxy_address.to_string(), + forward_metadata.receiver.to_string(), + withdrawn_coin_2, + env.block + .time + .plus_seconds(2 * ibc_config.osmo_ibc_timeout.u64()) + .nanos(), + to_json_string(&PacketMetadata { + forward: Some(ForwardMetadata { + receiver: env.contract.address.to_string(), + port: forward_metadata.port.to_string(), + channel: ibc_config.party_2_chain_info.party_chain_to_neutron_channel, + }), + })?, + ); + attributes.push(Attribute::new( + "denom_2_ibc_distribution", + to_json_string(&msg)?, + )); + ibc_withdraw_msgs.push(msg); + } + None => { + let msg = get_ibc_withdraw_coin_message( + ibc_config.osmo_to_neutron_channel_id.to_string(), + env.contract.address.to_string(), + withdrawn_coin_2, + IbcTimeout::with_timestamp( + env.block + .time + .plus_seconds(2 * ibc_config.osmo_ibc_timeout.u64()), + ), + ); + attributes.push(Attribute::new( + "denom_2_ibc_distribution", + to_json_string(&msg)?, + )); + ibc_withdraw_msgs.push(msg); + } + } + } + + if !ibc_withdraw_msgs.is_empty() { + let note_msg = get_note_execute_neutron_msg( + ibc_withdraw_msgs, + ibc_config.osmo_ibc_timeout, + note_address, + None, + )?; + Ok(Response::default() + .add_attributes(attributes) + .add_message(note_msg)) + } else { + Ok(Response::default().add_attributes(attributes)) + } +} + +fn try_sync_proxy_balances(deps: ExecuteDeps, env: Env) -> NeutronResult> { + let note_address = NOTE_ADDRESS.load(deps.storage)?; + let ibc_config = IBC_CONFIG.load(deps.storage)?; + let mut lp_config = LIQUIDITY_PROVISIONING_CONFIG.load(deps.storage)?; + let proxy_address = PROXY_ADDRESS.load(deps.storage)?; + + // get the message to query proxy for its balances + let note_query_balances_msg = get_proxy_query_balances_message( + env, + proxy_address, + note_address.to_string(), + lp_config.clone(), + ibc_config, + PROXY_BALANCES_QUERY_CALLBACK_ID, + )?; + // reset the proxy balances as they will be updated + lp_config.reset_latest_proxy_balances(); + LIQUIDITY_PROVISIONING_CONFIG.save(deps.storage, &lp_config)?; + + Ok(Response::default().add_message(note_query_balances_msg)) +} + +/// fires an empty message to the note contract. this in turn triggers +/// the voice contract to create a proxy for this contract. +/// state is advanced from `instantiated` to `proxy_created` on the +/// polytone callback, where we query the note for remote address. +/// if address is found, we store it in PROXY_ADDRESS and advance the +/// state to `proxy_created`. +/// see polytone_handlers `process_execute_callback` match statement +/// handling the CREATE_PROXY_CALLBACK_ID for details. +fn try_create_proxy(deps: ExecuteDeps, env: Env) -> NeutronResult> { + let note_address = NOTE_ADDRESS.load(deps.storage)?; + let ibc_config = IBC_CONFIG.load(deps.storage)?; + + let polytone_execute_msg_binary = get_polytone_execute_msg_binary( + vec![], + Some(CallbackRequest { + receiver: env.contract.address.to_string(), + msg: to_json_binary(&CREATE_PROXY_CALLBACK_ID)?, + }), + ibc_config.osmo_ibc_timeout, + )?; + + let note_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: note_address.to_string(), + msg: polytone_execute_msg_binary, + funds: vec![], + }); + + Ok(Response::default() + .add_message(note_msg) + .add_attribute("method", "try_create_proxy")) +} + +fn try_deliver_funds(deps: ExecuteDeps, env: Env) -> NeutronResult> { + let mut lp_config = LIQUIDITY_PROVISIONING_CONFIG.load(deps.storage)?; + + // check if both balances have a recent query + match ( + lp_config.get_party_1_proxy_balance(), + lp_config.get_party_2_proxy_balance(), + ) { + (Some(proxy_party_1_coin), Some(proxy_party_2_coin)) => { + // if proxy holds both party contributions, we advance the state machine + if lp_config.proxy_received_party_contributions(proxy_party_1_coin, proxy_party_2_coin) + { + // otherwise we advance the state machine and store an + // expiration time for the funding period + let funding_expiration = lp_config.funding_duration.after(&env.block); + CONTRACT_STATE.save( + deps.storage, + &ContractState::ProxyFunded { funding_expiration }, + )?; + Ok(Response::default() + .add_attribute("method", "try_tick") + .add_attribute("contract_state", "proxy_funded")) + } else { + // otherwise we attempt to deliver the funds + try_fund_proxy(deps, env) + } + } + // if either balance is unknown, we requery + _ => { + // reset the balances and submit the query + lp_config.reset_latest_proxy_balances(); + LIQUIDITY_PROVISIONING_CONFIG.save(deps.storage, &lp_config)?; + query_proxy_balances(deps, env) + } + } +} + +fn try_fund_proxy(deps: ExecuteDeps, env: Env) -> NeutronResult> { + let mut lp_config = LIQUIDITY_PROVISIONING_CONFIG.load(deps.storage)?; + let proxy_address = PROXY_ADDRESS.load(deps.storage)?; + let ibc_config = IBC_CONFIG.load(deps.storage)?; + + // we get our target denom balances which we should transfer to the proxy + let coin_1_bal = deps.querier.query_balance( + env.contract.address.to_string(), + lp_config.party_1_denom_info.local_denom.to_string(), + )?; + + let coin_2_bal = deps.querier.query_balance( + env.contract.address.to_string(), + lp_config.party_2_denom_info.local_denom.to_string(), + )?; + + // if either available balance is not sufficient, + // we reset the latest proxy balance to `None`. + // this will trigger a query on following tick. + if lp_config.party_1_denom_info.osmosis_coin.amount > coin_1_bal.amount + || lp_config.party_2_denom_info.osmosis_coin.amount > coin_2_bal.amount + { + // remove party denom entries from the balances map. + // this will trigger a proxy balance query on the following tick. + lp_config.reset_latest_proxy_balances(); + LIQUIDITY_PROVISIONING_CONFIG.save(deps.storage, &lp_config)?; + + return Ok(Response::default() + .add_attribute("method", "try_fund_proxy") + .add_attribute("result", "insufficient_balances")); + } + + let mut transfer_messages = vec![]; + let min_ibc_fee: MinIbcFeeResponse = deps.querier.query(&NeutronQuery::MinIbcFee {}.into())?; + if coin_1_bal.amount > Uint128::zero() { + transfer_messages.push(get_ibc_transfer_message( + ibc_config.party_1_chain_info, + env.clone(), + coin_1_bal, + proxy_address.to_string(), + &min_ibc_fee.min_fee, + )?); + } + if coin_2_bal.amount > Uint128::zero() { + transfer_messages.push(get_ibc_transfer_message( + ibc_config.party_2_chain_info, + env, + coin_2_bal, + proxy_address, + &min_ibc_fee.min_fee, + )?); + } + + Ok(Response::default() + .add_messages(transfer_messages) + .add_attribute("method", "try_fund_proxy")) +} + +fn try_provide_liquidity(deps: ExecuteDeps, env: Env) -> NeutronResult> { + let note_address = NOTE_ADDRESS.load(deps.storage)?; + let ibc_config = IBC_CONFIG.load(deps.storage)?; + let mut lp_config = LIQUIDITY_PROVISIONING_CONFIG.load(deps.storage)?; + let proxy_address = PROXY_ADDRESS.load(deps.storage)?; + + // we generate a provide_liquidity message for the outpost + // and wrap it in a note message + let outpost_msg = lp_config.get_osmo_outpost_provide_liquidity_message()?; + let note_outpost_liquidity_msg = get_note_execute_neutron_msg( + vec![outpost_msg], + ibc_config.osmo_ibc_timeout, + note_address.clone(), + Some(CallbackRequest { + receiver: env.contract.address.to_string(), + msg: to_json_binary(&PROVIDE_LIQUIDITY_CALLBACK_ID)?, + }), + )?; + + // following the liquidity provision message we perform a proxy balances query. + // this gets executed after the lp attempt, so on callback we can know if + // our lp attempt succeeded. + let note_query_balances_msg = get_proxy_query_balances_message( + env, + proxy_address, + note_address.to_string(), + lp_config.clone(), + ibc_config, + PROXY_BALANCES_QUERY_CALLBACK_ID, + )?; + + // reset the prices as they have expired + lp_config.reset_latest_proxy_balances(); + LIQUIDITY_PROVISIONING_CONFIG.save(deps.storage, &lp_config)?; + + Ok(Response::default() + .add_message(note_outpost_liquidity_msg) + .add_message(note_query_balances_msg) + .add_attribute("method", "try_lp")) +} + +fn query_proxy_balances(deps: ExecuteDeps, env: Env) -> NeutronResult> { + let note_address = NOTE_ADDRESS.load(deps.storage)?; + let ibc_config = IBC_CONFIG.load(deps.storage)?; + let proxy_address = PROXY_ADDRESS.load(deps.storage)?; + let lp_config = LIQUIDITY_PROVISIONING_CONFIG.load(deps.storage)?; + + let note_balance_query_msg = get_proxy_query_balances_message( + env, + proxy_address, + note_address.to_string(), + lp_config, + ibc_config, + PROXY_BALANCES_QUERY_CALLBACK_ID, + )?; + Ok(Response::default() + .add_message(note_balance_query_msg) + .add_attribute("method", "try_query_proxy_balances")) +} + +fn get_ibc_transfer_message( + party_chain_info: PartyChainInfo, + env: Env, + coin: Coin, + proxy_address: String, + ibc_fee: &IbcFee, +) -> StdResult { + // depending on whether pfm is configured, + // we return a ibc transfer message + match party_chain_info.outwards_pfm { + // pfm necesary, we configure the memo + Some(forward_metadata) => Ok(NeutronMsg::IbcTransfer { + source_port: "transfer".to_string(), + source_channel: party_chain_info.neutron_to_party_chain_channel, + token: coin, + sender: env.contract.address.to_string(), + receiver: forward_metadata.receiver, + timeout_height: RequestPacketTimeoutHeight { + revision_number: None, + revision_height: None, + }, + timeout_timestamp: env + .block + .time + .plus_seconds(party_chain_info.ibc_timeout.u64()) + .nanos(), + memo: to_json_string(&PacketMetadata { + forward: Some(ForwardMetadata { + receiver: proxy_address.to_string(), + port: forward_metadata.port, + channel: forward_metadata.channel, + }), + })?, + fee: ibc_fee.clone(), + }), + // no pfm necessary, we do a regular transfer + None => Ok(NeutronMsg::IbcTransfer { + source_port: "transfer".to_string(), + source_channel: party_chain_info.neutron_to_party_chain_channel, + token: coin, + sender: env.contract.address.to_string(), + receiver: proxy_address.to_string(), + timeout_height: RequestPacketTimeoutHeight { + revision_number: None, + revision_height: None, + }, + timeout_timestamp: env + .block + .time + .plus_seconds(party_chain_info.ibc_timeout.u64()) + .nanos(), + memo: "".to_string(), + fee: ibc_fee.clone(), + }), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: QueryDeps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ClockAddress {} => Ok(to_json_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::ContractState {} => Ok(to_json_binary(&CONTRACT_STATE.may_load(deps.storage)?)?), + QueryMsg::HolderAddress {} => Ok(to_json_binary(&HOLDER_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::DepositAddress {} => Ok(to_json_binary(&env.contract.address)?), + QueryMsg::ProxyAddress {} => Ok(to_json_binary(&PROXY_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::IbcConfig {} => Ok(to_json_binary(&IBC_CONFIG.may_load(deps.storage)?)?), + QueryMsg::LiquidityProvisionConfig {} => Ok(to_json_binary( + &LIQUIDITY_PROVISIONING_CONFIG.may_load(deps.storage)?, + )?), + QueryMsg::Callbacks {} => { + let mut vals = vec![]; + POLYTONE_CALLBACKS + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .for_each(|c| { + if let Ok((k, v)) = c { + vals.push(format!("{:?} : {:?}", k, v)) + } + }); + + Ok(to_json_binary(&vals)?) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: ExecuteDeps, _env: Env, msg: MigrateMsg) -> NeutronResult { + match msg { + MigrateMsg::UpdateConfig { + clock_addr, + holder_address, + note_address, + ibc_config, + lp_config, + } => { + let mut response = Response::default().add_attribute("method", "update_config"); + + if let Some(clock_addr) = clock_addr { + CLOCK_ADDRESS.save(deps.storage, &deps.api.addr_validate(&clock_addr)?)?; + response = response.add_attribute("clock_addr", clock_addr); + } + + if let Some(holder_address) = holder_address { + HOLDER_ADDRESS.save(deps.storage, &deps.api.addr_validate(&holder_address)?)?; + response = response.add_attribute("holder_address", holder_address); + } + + if let Some(config) = *ibc_config { + IBC_CONFIG.save(deps.storage, &config)?; + response = response.add_attributes(config.to_response_attributes()); + } + + if let Some(address) = note_address { + let note = deps.api.addr_validate(&address)?; + NOTE_ADDRESS.save(deps.storage, ¬e)?; + response = response.add_attribute("note_address", note); + } + + if let Some(config) = *lp_config { + LIQUIDITY_PROVISIONING_CONFIG.save(deps.storage, &config)?; + response = response.add_attributes(config.to_response_attributes()); + } + + Ok(response) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} diff --git a/contracts/osmo-liquid-pooler/src/error.rs b/contracts/osmo-liquid-pooler/src/error.rs new file mode 100644 index 00000000..ed4e64a4 --- /dev/null +++ b/contracts/osmo-liquid-pooler/src/error.rs @@ -0,0 +1,54 @@ +use cosmwasm_std::{CheckedMultiplyRatioError, OverflowError, StdError}; +use neutron_sdk::NeutronError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + CheckedMultiplyError(#[from] CheckedMultiplyRatioError), + + #[error("{0}")] + NeutronError(#[from] NeutronError), + + #[error("{0}")] + OverflowError(#[from] OverflowError), + + #[error("Not clock")] + ClockVerificationError {}, + + #[error("Unknown holder address. Migrate update to set it.")] + MissingHolderError {}, + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Osmosis pool error: {0}")] + OsmosisPoolError(String), + + #[error("Fund deposit error: expected {0} bal {1}, got {2}")] + FundsDepositError(String, String, String), + + #[error("state machine: {0}")] + StateMachineError(String), + + #[error("polytone error: {0}")] + PolytoneError(String), + + #[error("Only holder can withdraw the position")] + NotHolder {}, +} + +impl ContractError { + pub fn to_std(&self) -> StdError { + StdError::GenericErr { + msg: self.to_string(), + } + } + + pub fn to_neutron_std(&self) -> NeutronError { + NeutronError::Std(self.to_std()) + } +} diff --git a/contracts/osmo-liquid-pooler/src/lib.rs b/contracts/osmo-liquid-pooler/src/lib.rs new file mode 100644 index 00000000..da4a6ad7 --- /dev/null +++ b/contracts/osmo-liquid-pooler/src/lib.rs @@ -0,0 +1,7 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod polytone_handlers; +pub mod state; diff --git a/contracts/osmo-liquid-pooler/src/msg.rs b/contracts/osmo-liquid-pooler/src/msg.rs new file mode 100644 index 00000000..afc6a374 --- /dev/null +++ b/contracts/osmo-liquid-pooler/src/msg.rs @@ -0,0 +1,381 @@ +use std::collections::HashMap; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{ + to_json_binary, Addr, Attribute, Binary, Coin, CosmosMsg, Decimal, StdResult, Uint128, Uint64, + WasmMsg, +}; +use covenant_macros::{ + clocked, covenant_clock_address, covenant_deposit_address, covenant_lper_withdraw, +}; +use covenant_utils::{ + instantiate2_helper::Instantiate2HelperConfig, ForwardMetadata, PoolPriceConfig, + SingleSideLpLimits, +}; +use cw_utils::{Duration, Expiration}; +use polytone::callbacks::CallbackMessage; +use valence_outpost_osmo_liquid_pooler::msg::OutpostProvideLiquidityConfig; + +#[cw_serde] +pub struct InstantiateMsg { + pub clock_address: String, + pub holder_address: String, + pub note_address: String, + pub pool_id: Uint64, + pub osmo_ibc_timeout: Uint64, + pub party_1_chain_info: PartyChainInfo, + pub party_2_chain_info: PartyChainInfo, + pub osmo_to_neutron_channel_id: String, + pub party_1_denom_info: PartyDenomInfo, + pub party_2_denom_info: PartyDenomInfo, + pub osmo_outpost: String, + pub lp_token_denom: String, + pub slippage_tolerance: Option, + pub pool_price_config: PoolPriceConfig, + pub funding_duration: Duration, + pub single_side_lp_limits: SingleSideLpLimits, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +#[cw_serde] +pub struct OsmosisLiquidPoolerConfig { + pub note_address: String, + pub pool_id: Uint64, + pub osmo_ibc_timeout: Uint64, + pub osmo_outpost: String, + pub party_1_chain_info: PartyChainInfo, + pub party_2_chain_info: PartyChainInfo, + pub lp_token_denom: String, + pub osmo_to_neutron_channel_id: String, + pub party_1_denom_info: PartyDenomInfo, + pub party_2_denom_info: PartyDenomInfo, + pub funding_duration: Duration, + pub single_side_lp_limits: SingleSideLpLimits, +} + +impl OsmosisLiquidPoolerConfig { + pub fn to_instantiate_msg( + &self, + clock_address: String, + holder_address: String, + pool_price_config: PoolPriceConfig, + ) -> InstantiateMsg { + InstantiateMsg { + clock_address, + holder_address, + note_address: self.note_address.to_string(), + pool_id: self.pool_id, + osmo_ibc_timeout: self.osmo_ibc_timeout, + party_1_chain_info: self.party_1_chain_info.clone(), + party_2_chain_info: self.party_2_chain_info.clone(), + osmo_to_neutron_channel_id: self.osmo_to_neutron_channel_id.to_string(), + party_1_denom_info: self.party_1_denom_info.clone(), + party_2_denom_info: self.party_2_denom_info.clone(), + osmo_outpost: self.osmo_outpost.to_string(), + lp_token_denom: self.lp_token_denom.to_string(), + slippage_tolerance: None, + pool_price_config, + funding_duration: self.funding_duration, + single_side_lp_limits: self.single_side_lp_limits.clone(), + } + } +} + +#[cw_serde] +pub struct LiquidityProvisionConfig { + pub latest_balances: HashMap, + pub party_1_denom_info: PartyDenomInfo, + pub party_2_denom_info: PartyDenomInfo, + pub pool_id: Uint64, + pub outpost: String, + pub lp_token_denom: String, + pub slippage_tolerance: Option, + pub pool_price_config: PoolPriceConfig, + pub funding_duration: Duration, + pub single_side_lp_limits: SingleSideLpLimits, +} + +#[cw_serde] +pub struct IbcConfig { + pub party_1_chain_info: PartyChainInfo, + pub party_2_chain_info: PartyChainInfo, + pub osmo_to_neutron_channel_id: String, + pub osmo_ibc_timeout: Uint64, +} + +impl IbcConfig { + pub fn to_response_attributes(self) -> Vec { + let mut attributes = vec![ + Attribute::new( + "osmo_to_neutron_channel_id", + self.osmo_to_neutron_channel_id, + ), + Attribute::new("osmo_ibc_timeout", self.osmo_ibc_timeout.to_string()), + ]; + attributes.extend( + self.party_1_chain_info + .to_response_attributes("party_1".to_string()), + ); + attributes.extend( + self.party_2_chain_info + .to_response_attributes("party_2".to_string()), + ); + + attributes + } +} + +impl LiquidityProvisionConfig { + pub fn get_party_1_proxy_balance(&self) -> Option<&Coin> { + self.latest_balances + .get(&self.party_1_denom_info.osmosis_coin.denom) + } + + pub fn get_party_2_proxy_balance(&self) -> Option<&Coin> { + self.latest_balances + .get(&self.party_2_denom_info.osmosis_coin.denom) + } + + pub fn get_lp_token_proxy_balance(&self) -> Option<&Coin> { + self.latest_balances.get(&self.lp_token_denom) + } + + pub fn get_osmo_outpost_provide_liquidity_message(&self) -> StdResult { + let mut funds = vec![]; + if let Some(c) = self.get_party_1_proxy_balance() { + funds.push(c.clone()); + } + if let Some(c) = self.get_party_2_proxy_balance() { + funds.push(c.clone()); + } + + let outpost_config = OutpostProvideLiquidityConfig { + pool_id: Uint64::new(self.pool_id.u64()), + expected_spot_price: self.pool_price_config.expected_spot_price, + acceptable_price_spread: self.pool_price_config.acceptable_price_spread, + // if no slippage tolerance is passed, we use 0 + slippage_tolerance: self.slippage_tolerance.unwrap_or_default(), + asset_1_single_side_lp_limit: self.single_side_lp_limits.asset_a_limit, + asset_2_single_side_lp_limit: self.single_side_lp_limits.asset_b_limit, + }; + + Ok(WasmMsg::Execute { + contract_addr: self.outpost.to_string(), + msg: to_json_binary( + &valence_outpost_osmo_liquid_pooler::msg::ExecuteMsg::ProvideLiquidity { + config: outpost_config, + }, + )?, + funds, + } + .into()) + } + + pub fn reset_latest_proxy_balances(&mut self) { + self.latest_balances + .remove(&self.party_1_denom_info.osmosis_coin.denom); + self.latest_balances + .remove(&self.party_2_denom_info.osmosis_coin.denom); + self.latest_balances.remove(&self.lp_token_denom); + } + + pub fn get_proxy_balances(&self) -> Option<(&Coin, &Coin, &Coin)> { + match ( + self.get_party_1_proxy_balance(), + self.get_party_2_proxy_balance(), + self.get_lp_token_proxy_balance(), + ) { + (Some(p1), Some(p2), Some(lp)) => Some((p1, p2, lp)), + _ => None, + } + } + + pub fn proxy_received_party_contributions(&self, p1_coin: &Coin, p2_coin: &Coin) -> bool { + let p1_funded = p1_coin.amount >= self.party_1_denom_info.get_osmo_bal(); + let p2_funded = p2_coin.amount >= self.party_2_denom_info.get_osmo_bal(); + p1_funded && p2_funded + } + + pub fn to_response_attributes(self) -> Vec { + let slippage_tolerance = match self.slippage_tolerance { + Some(val) => val.to_string(), + None => "None".to_string(), + }; + let proxy_bals: Vec = self + .latest_balances + .iter() + .map(|(denom, coin)| Attribute::new(denom, coin.to_string())) + .collect(); + let mut attributes = vec![ + Attribute::new("pool_id", self.pool_id.to_string()), + Attribute::new("outpost", self.outpost), + Attribute::new("lp_token_denom", self.lp_token_denom), + Attribute::new("slippage_tolerance", slippage_tolerance), + Attribute::new( + "expected_spot_price", + self.pool_price_config.expected_spot_price.to_string(), + ), + Attribute::new( + "acceptable_price_spread", + self.pool_price_config.acceptable_price_spread.to_string(), + ), + ]; + attributes.extend( + self.party_1_denom_info + .to_response_attributes("party_1".to_string()), + ); + attributes.extend( + self.party_1_denom_info + .to_response_attributes("party_2".to_string()), + ); + attributes.extend(proxy_bals); + + attributes + } +} + +#[cw_serde] +pub struct PartyDenomInfo { + /// coin as denominated on osmosis + pub osmosis_coin: Coin, + /// ibc denom on liquid pooler chain + pub local_denom: String, +} + +impl PartyDenomInfo { + pub fn get_osmo_bal(&self) -> Uint128 { + self.osmosis_coin.amount + } + + pub fn to_response_attributes(&self, party: String) -> Vec { + vec![ + Attribute { + key: format!("{:?}_neutron_denom", party), + value: self.local_denom.to_string(), + }, + Attribute { + key: format!("{:?}_osmosis_coin", party), + value: self.osmosis_coin.to_string(), + }, + ] + } +} + +#[clocked] +#[covenant_lper_withdraw] +#[cw_serde] +pub enum ExecuteMsg { + // polytone callback listener + Callback(CallbackMessage), +} + +#[covenant_clock_address] +#[covenant_deposit_address] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(ContractState)] + ContractState {}, + #[returns(Addr)] + HolderAddress {}, + #[returns(LiquidityProvisionConfig)] + LiquidityProvisionConfig {}, + #[returns(IbcConfig)] + IbcConfig {}, + #[returns(Option)] + ProxyAddress {}, + #[returns(Vec)] + Callbacks {}, +} + +/// state of the LP state machine +#[cw_serde] +pub enum ContractState { + Instantiated, + ProxyCreated, + ProxyFunded { funding_expiration: Expiration }, + Active, + Distributing { coins: Vec }, + PendingWithdrawal { share: Decimal }, +} + +#[cw_serde] +pub struct PartyChainInfo { + /// channel id to route funds from local chain to party chain + pub neutron_to_party_chain_channel: String, + /// channel id to route funds from party chain to local chain + pub party_chain_to_neutron_channel: String, + /// pfm configuration used to route funds from local chain + /// to osmosis via origin chain + pub outwards_pfm: Option, + /// pfm configuration used to route funds from osmosis + /// to local chain via origin chain + pub inwards_pfm: Option, + pub ibc_timeout: Uint64, +} + +impl PartyChainInfo { + pub fn to_response_attributes(&self, party: String) -> Vec { + let pfm_attributes: Vec = match &self.outwards_pfm { + Some(val) => { + vec![ + Attribute::new( + format!("{:?}_pfm_receiver", party), + val.receiver.to_string(), + ), + Attribute::new(format!("{:?}_pfm_port", party), val.port.to_string()), + Attribute::new(format!("{:?}_pfm_channel", party), val.channel.to_string()), + ] + } + None => { + vec![Attribute::new(format!("{:?}_pfm", party), "none")] + } + }; + + let mut attributes = vec![ + Attribute::new( + format!("{:?}_neutron_to_party_chain_port", party), + "transfer".to_string(), + ), + Attribute::new( + format!("{:?}_neutron_to_party_chain_channel", party), + self.neutron_to_party_chain_channel.to_string(), + ), + Attribute::new(format!("{:?}_ibc_timeout", party), self.ibc_timeout), + ]; + attributes.extend(pfm_attributes); + + attributes + } +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + holder_address: Option, + note_address: Option, + ibc_config: Box>, + lp_config: Box>, + }, + UpdateCodeId { + data: Option, + }, +} diff --git a/contracts/osmo-liquid-pooler/src/polytone_handlers.rs b/contracts/osmo-liquid-pooler/src/polytone_handlers.rs new file mode 100644 index 00000000..412576a8 --- /dev/null +++ b/contracts/osmo-liquid-pooler/src/polytone_handlers.rs @@ -0,0 +1,394 @@ +use std::str::FromStr; + +use cosmwasm_std::{ + coin, ensure, from_json, to_json_binary, Addr, Binary, Coin, CosmosMsg, DepsMut, Empty, Env, + IbcMsg, IbcTimeout, MessageInfo, QueryRequest, Response, StdResult, Uint128, Uint64, WasmMsg, +}; +use covenant_utils::{ + polytone::{ + get_polytone_execute_msg_binary, get_polytone_query_msg_binary, + query_polytone_proxy_address, + }, + withdraw_lp_helper::WithdrawLPMsgs, +}; +use neutron_sdk::{ + bindings::{msg::NeutronMsg, query::NeutronQuery}, + NeutronResult, +}; +use osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceResponse; + +use crate::{ + contract::{ + CREATE_PROXY_CALLBACK_ID, PROVIDE_LIQUIDITY_CALLBACK_ID, PROXY_BALANCES_QUERY_CALLBACK_ID, + WITHDRAW_LIQUIDITY_CALLBACK_ID, + }, + error::ContractError, + msg::{ContractState, IbcConfig, LiquidityProvisionConfig}, + state::{ + CONTRACT_STATE, HOLDER_ADDRESS, LIQUIDITY_PROVISIONING_CONFIG, NOTE_ADDRESS, + POLYTONE_CALLBACKS, PROXY_ADDRESS, + }, +}; + +use polytone::callbacks::{ + Callback as PolytoneCallback, CallbackMessage, CallbackRequest, ErrorResponse, + ExecutionResponse, +}; + +type ExecuteDeps<'a> = DepsMut<'a, NeutronQuery>; + +/// attempts to advance the state machine. performs `info.sender` validation. +pub fn try_handle_callback( + env: Env, + deps: ExecuteDeps, + info: MessageInfo, + msg: CallbackMessage, +) -> NeutronResult> { + // only the note can submit a callback + ensure!( + info.sender == NOTE_ADDRESS.load(deps.storage)?, + ContractError::Unauthorized {}.to_neutron_std() + ); + + match msg.result { + PolytoneCallback::Query(resp) => process_query_callback(env, deps, resp, msg.initiator_msg), + PolytoneCallback::Execute(resp) => { + process_execute_callback(env, deps, resp, msg.initiator_msg) + } + PolytoneCallback::FatalError(resp) => process_fatal_error_callback(env, deps, resp), + } +} + +fn process_query_callback( + env: Env, + deps: ExecuteDeps, + query_callback_result: Result, ErrorResponse>, + initiator_msg: Binary, +) -> NeutronResult> { + // decode the initiator message callback id into u8 + let initiator_msg: u8 = from_json(initiator_msg)?; + + match initiator_msg { + PROXY_BALANCES_QUERY_CALLBACK_ID => { + handle_proxy_balances_callback(deps, env, query_callback_result) + } + _ => Err(ContractError::PolytoneError(format!( + "unexpected callback id: {:?}", + initiator_msg + )) + .to_neutron_std()), + } +} + +fn process_execute_callback( + env: Env, + deps: ExecuteDeps, + execute_callback_result: Result, + initiator_msg: Binary, +) -> NeutronResult> { + let initiator_msg: u8 = from_json(initiator_msg)?; + let callback_result: ExecutionResponse = match execute_callback_result { + Ok(val) => val, + Err(e) => return Err(ContractError::PolytoneError(e).to_neutron_std()), + }; + + match initiator_msg { + PROVIDE_LIQUIDITY_CALLBACK_ID => { + POLYTONE_CALLBACKS.save( + deps.storage, + format!( + "provide_liquidity_callback : {:?}", + env.block.height.to_string() + ), + &to_json_binary(&callback_result)?.to_string(), + )?; + + for submsg_response in callback_result.result { + if submsg_response.data.is_some() { + if let Some(response_binary) = submsg_response.data { + POLYTONE_CALLBACKS.save( + deps.storage, + response_binary.to_string(), + &response_binary.to_base64(), + )?; + } + } + } + } + CREATE_PROXY_CALLBACK_ID => { + let note_address = NOTE_ADDRESS.load(deps.storage)?; + + let proxy_address = query_polytone_proxy_address( + env.contract.address.to_string(), + note_address.to_string(), + deps.querier, + )?; + // result contains nothing + POLYTONE_CALLBACKS.save( + deps.storage, + format!("create_proxy_callback : {:?}", env.block.height.to_string()), + &to_json_binary(&callback_result)?.to_string(), + )?; + if let Some(addr) = proxy_address { + PROXY_ADDRESS.save(deps.storage, &addr)?; + CONTRACT_STATE.update(deps.storage, |state| -> StdResult<_> { + // little sanity check. should not end up in catchall arm, + // but if for some reason we receive a proxy created callback + // when we are not in Instantiated state, we do not update + match state { + ContractState::Instantiated => Ok(ContractState::ProxyCreated), + _ => Ok(state), + } + })?; + } + } + WITHDRAW_LIQUIDITY_CALLBACK_ID => { + // decode the response attribute here + // callback_result.result[0] contains the events + // query the events for one that has "type" == "wasm" + // and search its attributes for one where key == "refund_tokens". + // type is polytone ExecutionResponse + for callback_response in callback_result.clone().result { + for event in callback_response.events { + if event.ty == "wasm" { + for attr in event.attributes { + if attr.key == "refund_tokens" { + let refunded_coins: Vec = match from_json(&attr.value) { + Ok(coins) => coins, + Err(e) => { + POLYTONE_CALLBACKS.save( + deps.storage, + format!( + "withdraw_liquidity_callback_REFUND_TOKENS_error : {:?}", + env.block.height.to_string() + ), + &e.to_string(), + )?; + vec![] + } + }; + + CONTRACT_STATE.save( + deps.storage, + &ContractState::Distributing { + coins: refunded_coins, + }, + )?; + + POLYTONE_CALLBACKS.save( + deps.storage, + format!( + "withdraw_liquidity_callback_REFUND_TOKENS : {:?}", + env.block.height.to_string() + ), + &attr.value, + )?; + } + } + } + } + } + POLYTONE_CALLBACKS.save( + deps.storage, + format!( + "withdraw_liquidity_callback : {:?}", + env.block.height.to_string() + ), + &to_json_binary(&callback_result)?.to_string(), + )?; + + match CONTRACT_STATE.load(deps.storage)? { + ContractState::Distributing { coins: _ } => (), + _ => { + POLYTONE_CALLBACKS.save( + deps.storage, + format!( + "withdraw_liquidity_callback : {:?}", + env.block.height.to_string() + ), + &"non-distributing-state".to_string(), + )?; + + // if we are not in a distributing state, withdraw had failed. + // we submit the appropriate callback to the holder. + let holder = HOLDER_ADDRESS.load(deps.storage)?; + return Ok(Response::default().add_message(CosmosMsg::Wasm( + WasmMsg::Execute { + contract_addr: holder.to_string(), + msg: to_json_binary(&WithdrawLPMsgs::WithdrawFailed {})?, + funds: vec![], + }, + ))); + } + } + } + _ => (), + } + + Ok(Response::default()) +} + +fn process_fatal_error_callback( + env: Env, + deps: ExecuteDeps, + response: String, +) -> NeutronResult> { + POLYTONE_CALLBACKS.save( + deps.storage, + format!("fatal_error : {:?}", env.block.height.to_string()), + &response, + )?; + Ok(Response::default()) +} + +fn handle_proxy_balances_callback( + deps: ExecuteDeps, + env: Env, + query_callback_result: Result, ErrorResponse>, +) -> NeutronResult> { + // decode the query callback result into a vec of binaries, + // or error out if it fails + let response_binaries = match query_callback_result { + Ok(val) => { + for bin in val.clone() { + POLYTONE_CALLBACKS.save( + deps.storage, + format!( + "proxy_balances_callback : {:?}", + env.block.height.to_string() + ), + &bin.to_base64(), + )?; + } + val + } + Err(err) => return Err(ContractError::PolytoneError(err.error).to_neutron_std()), + }; + + // store the latest prices in lp config + LIQUIDITY_PROVISIONING_CONFIG.update(deps.storage, |mut lp_config| -> StdResult<_> { + // process the balance responses one by one + for response_binary in response_binaries { + // parse binary into an osmosis QueryBalanceResponse + let balance_response: QueryBalanceResponse = from_json(response_binary.clone())?; + if let Some(balance) = balance_response.balance { + // update the latest balances map with the processed balance + lp_config.latest_balances.insert( + balance.denom.to_string(), + coin(Uint128::from_str(&balance.amount)?.u128(), balance.denom), + ); + } + } + Ok(lp_config) + })?; + + Ok(Response::default()) +} + +pub fn get_note_execute_neutron_msg( + msgs: Vec, + ibc_timeout: Uint64, + note_address: Addr, + callback: Option, +) -> NeutronResult> { + let polytone_msg = get_polytone_execute_msg_binary(msgs, callback, ibc_timeout)?; + + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: note_address.to_string(), + msg: polytone_msg, + funds: vec![], + })) +} + +pub fn get_ibc_withdraw_coin_message( + channel_id: String, + to_address: String, + amount: Coin, + timeout: IbcTimeout, +) -> CosmosMsg { + let msg = IbcMsg::Transfer { + channel_id, + to_address, + amount, + timeout, + }; + + msg.into() +} + +pub fn get_ibc_pfm_withdraw_coin_message( + channel_id: String, + from_address: String, + to_address: String, + amount: Coin, + timeout_timestamp_nanos: u64, + memo: String, +) -> CosmosMsg { + use prost::Message; + + let ibc_message = osmosis_std::types::ibc::applications::transfer::v1::MsgTransfer { + source_port: "transfer".to_string(), + source_channel: channel_id, + token: Some(osmosis_std::types::cosmos::base::v1beta1::Coin { + denom: amount.denom, + amount: amount.amount.to_string(), + }), + sender: from_address, + receiver: to_address, + timeout_height: None, + timeout_timestamp: timeout_timestamp_nanos, + memo, + }; + + cosmwasm_std::CosmosMsg::Stargate { + type_url: "/ibc.applications.transfer.v1.MsgTransfer".to_string(), + value: Binary(ibc_message.encode_to_vec()), + } +} + +pub fn get_proxy_query_balances_message( + env: Env, + proxy_address: String, + note_address: String, + lp_config: LiquidityProvisionConfig, + ibc_config: IbcConfig, + callback_id: u8, +) -> StdResult { + let proxy_coin_1_balance_request: QueryRequest = + osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest { + address: proxy_address.to_string(), + denom: lp_config.party_1_denom_info.osmosis_coin.denom, + } + .into(); + let proxy_coin_2_balance_request: QueryRequest = + osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest { + address: proxy_address.to_string(), + denom: lp_config.party_2_denom_info.osmosis_coin.denom, + } + .into(); + let proxy_gamm_balance_request: QueryRequest = + osmosis_std::types::cosmos::bank::v1beta1::QueryBalanceRequest { + address: proxy_address, + denom: lp_config.lp_token_denom, + } + .into(); + + let polytone_query_msg_binary = get_polytone_query_msg_binary( + vec![ + proxy_coin_1_balance_request, + proxy_coin_2_balance_request, + proxy_gamm_balance_request, + ], + CallbackRequest { + receiver: env.contract.address.to_string(), + msg: to_json_binary(&callback_id)?, + }, + ibc_config.osmo_ibc_timeout, + )?; + + Ok(WasmMsg::Execute { + contract_addr: note_address.to_string(), + msg: polytone_query_msg_binary, + funds: vec![], + }) +} diff --git a/contracts/osmo-liquid-pooler/src/state.rs b/contracts/osmo-liquid-pooler/src/state.rs new file mode 100644 index 00000000..8120790b --- /dev/null +++ b/contracts/osmo-liquid-pooler/src/state.rs @@ -0,0 +1,27 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; + +use crate::msg::{ContractState, IbcConfig, LiquidityProvisionConfig}; + +/// contract state tracks the state machine progress +pub const CONTRACT_STATE: Item = Item::new("contract_state"); + +/// clock module address to verify the incoming ticks sender +pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); + +/// holder module address to verify withdrawal requests +pub const HOLDER_ADDRESS: Item = Item::new("holder_address"); + +// polytone note address +pub const NOTE_ADDRESS: Item = Item::new("note_address"); +// our address on osmosis created by polytone +pub const PROXY_ADDRESS: Item = Item::new("proxy_address"); + +// fields relevant for providing liquidity +pub const LIQUIDITY_PROVISIONING_CONFIG: Item = Item::new("lp_config"); + +// ibc-related fields +pub const IBC_CONFIG: Item = Item::new("ibc_config"); + +// timestamp to message +pub const POLYTONE_CALLBACKS: Map = Map::new("callbacks"); diff --git a/contracts/outpost-osmo-liquid-pooler/.cargo/config b/contracts/outpost-osmo-liquid-pooler/.cargo/config new file mode 100644 index 00000000..5f6aa466 --- /dev/null +++ b/contracts/outpost-osmo-liquid-pooler/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" diff --git a/contracts/outpost-osmo-liquid-pooler/Cargo.toml b/contracts/outpost-osmo-liquid-pooler/Cargo.toml new file mode 100644 index 00000000..88fc0225 --- /dev/null +++ b/contracts/outpost-osmo-liquid-pooler/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "valence-outpost-osmo-liquid-pooler" +authors = ["benskey bekauz@protonmail.com"] +description = "Osmosis outpost for liquid pooler contract for covenants" +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } +edition = { workspace = true } + +exclude = ["contract.wasm", "hash.txt"] + + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { version = "1.5.4", features = [ + "cosmwasm_1_1", + "cosmwasm_1_2", +] } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +schemars = { workspace = true } +osmosis-std = "0.13.2" +prost = { workspace = true } diff --git a/contracts/outpost-osmo-liquid-pooler/README.md b/contracts/outpost-osmo-liquid-pooler/README.md new file mode 100644 index 00000000..9a028235 --- /dev/null +++ b/contracts/outpost-osmo-liquid-pooler/README.md @@ -0,0 +1,62 @@ +# osmo liquid pooler outpost + +This is a stateless outpost contract designed to provide liquidity iff +some preconditions are met. + +Contract has no notion of state, and therefore queries. + +It has one execute message, which contains all of the aforementioned conditions: + +```rust +pub enum ExecuteMsg { + ProvideLiquidity { + /// id of the pool we wish to provide liquidity to + pool_id: Uint64, + /// the price which we expect to provide liquidity at + expected_spot_price: Decimal, + /// acceptable delta (both ways) of the expected price + acceptable_price_spread: Decimal, + /// slippage tolerance + slippage_tolerance: Decimal, + /// limits for single-side liquidity provision + asset_1_single_side_lp_limit: Uint128, + asset_2_single_side_lp_limit: Uint128, + }, +} +``` + +## Liquidity provision conditions + +### pool id + +id of the pool we wish to interact with. + +### expected spot price and acceptable spread + +when submitting a message to provide liquidity from a remote chain, we should +have an idea of what price we wish to provide liquidity at. many things can +happen between submitting a transaction on a remote chain signalling our intent +to provide liquidity, and relayers delivering that message to osmosis. + +to circumvent that, we pass our expectations as arguments to this message. +for instance, we could say that for an atom/osmo pool, we expect there to be +10 times as many osmo as there are atom (1:10, or `0.1` in decimal). +prices can fluctuate every block, however, so we also pass an acceptable spread +decimal which we express in absolute terms relative to the actual pool spot price. +meaning, spread could be `0.02`. this will mean that we are fine with joining the +pool if and only if the pool spot price at the time of execution is between `0.08` +and `0.12`. + +### slippage tolerance + +on top of the acceptable price range, we can also pass a slippage tolerance. +also expressed in decimal, it would be applied by reducing the amount of LP tokens +we would expect to receive from providing liquidity. + +after we calculate the expected lp token amount during the execution, we deduct +the slippage tolerance % from that amount, and treat this new number as our target. + +### single side lp limits + +for both denoms, we pass single-side lp limits. this is an additional layer of safe +guards to avoid providing liquidity at undesirable conditions. diff --git a/contracts/outpost-osmo-liquid-pooler/schema/valence-outpost-osmo-liquid-pooler.json b/contracts/outpost-osmo-liquid-pooler/schema/valence-outpost-osmo-liquid-pooler.json new file mode 100644 index 00000000..a6515e69 --- /dev/null +++ b/contracts/outpost-osmo-liquid-pooler/schema/valence-outpost-osmo-liquid-pooler.json @@ -0,0 +1,187 @@ +{ + "contract_name": "valence-outpost-osmo-liquid-pooler", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "provide_liquidity" + ], + "properties": { + "provide_liquidity": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/OutpostProvideLiquidityConfig" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/OutpostWithdrawLiquidityConfig" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "OutpostProvideLiquidityConfig": { + "type": "object", + "required": [ + "acceptable_price_spread", + "asset_1_single_side_lp_limit", + "asset_2_single_side_lp_limit", + "expected_spot_price", + "pool_id", + "slippage_tolerance" + ], + "properties": { + "acceptable_price_spread": { + "description": "acceptable delta (both ways) of the expected price", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "asset_1_single_side_lp_limit": { + "description": "limits for single-side liquidity provision", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "asset_2_single_side_lp_limit": { + "$ref": "#/definitions/Uint128" + }, + "expected_spot_price": { + "description": "the price which we expect to provide liquidity at", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "pool_id": { + "description": "id of the pool we wish to provide liquidity to", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "slippage_tolerance": { + "description": "slippage tolerance", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "OutpostWithdrawLiquidityConfig": { + "type": "object", + "required": [ + "pool_id" + ], + "properties": { + "pool_id": { + "description": "id of the pool we wish to withdraw liquidity from", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "query": null, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + } + } + }, + "sudo": null, + "responses": null +} diff --git a/contracts/outpost-osmo-liquid-pooler/src/bin/schema.rs b/contracts/outpost-osmo-liquid-pooler/src/bin/schema.rs new file mode 100644 index 00000000..bab92669 --- /dev/null +++ b/contracts/outpost-osmo-liquid-pooler/src/bin/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use valence_outpost_osmo_liquid_pooler::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/outpost-osmo-liquid-pooler/src/contract.rs b/contracts/outpost-osmo-liquid-pooler/src/contract.rs new file mode 100644 index 00000000..ee995086 --- /dev/null +++ b/contracts/outpost-osmo-liquid-pooler/src/contract.rs @@ -0,0 +1,426 @@ +use std::str::FromStr; + +use crate::{ + error::ContractError, + msg::{ + CallerContext, ExecuteMsg, InstantiateMsg, MigrateMsg, OsmosisPool, + OutpostProvideLiquidityConfig, OutpostWithdrawLiquidityConfig, QueryMsg, + }, + state::PENDING_REPLY, +}; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, to_json_string, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, + Fraction, MessageInfo, Reply, Response, StdError, StdResult, SubMsg, Uint128, +}; +use cw2::set_contract_version; +use cw_utils::must_pay; +use osmosis_std::{ + shim::Any, + types::{ + cosmos::base::v1beta1::Coin as ProtoCoin, + osmosis::gamm::v1beta1::{ + MsgExitPool, MsgJoinPool, MsgJoinSwapExternAmountIn, Pool, + QueryCalcExitPoolCoinsFromSharesRequest, QueryCalcExitPoolCoinsFromSharesResponse, + QueryCalcJoinPoolNoSwapSharesRequest, QueryCalcJoinPoolNoSwapSharesResponse, + QueryCalcJoinPoolSharesRequest, QueryCalcJoinPoolSharesResponse, QueryPoolRequest, + QueryPoolResponse, + }, + }, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +const OSMO_POOL_REPLY_ID: u64 = 1; + +#[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::default().add_attribute("outpost", env.contract.address.to_string())) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::ProvideLiquidity { config } => try_provide_liquidity(deps, env, info, config), + ExecuteMsg::WithdrawLiquidity { config } => try_withdraw_liquidity(deps, env, info, config), + } +} + +fn try_withdraw_liquidity( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: OutpostWithdrawLiquidityConfig, +) -> Result { + // first we query the pool for validation and info + let query_response: QueryPoolResponse = deps.querier.query( + &QueryPoolRequest { + pool_id: config.pool_id.u64(), + } + .into(), + )?; + let osmo_pool: Pool = decode_osmo_pool_binary(query_response.pool)?; + + let pool_shares_coin = match osmo_pool.total_shares { + Some(coin) => coin, + None => { + return Err(ContractError::OsmosisPoolError( + "no shares coin in pool".to_string(), + )) + } + }; + + // we assert that the correct lp token is being redeemed + let shares_to_redeem = must_pay(&info, &pool_shares_coin.denom)?; + + // we now estimate the underlying assets from those shares + let calc_exit_query_response: QueryCalcExitPoolCoinsFromSharesResponse = deps.querier.query( + &QueryCalcExitPoolCoinsFromSharesRequest { + pool_id: config.pool_id.u64(), + share_in_amount: shares_to_redeem.to_string(), + } + .into(), + )?; + + // ensure that two assets are to be expected + ensure!( + calc_exit_query_response.tokens_out.len() == 2, + ContractError::OsmosisPoolError("exit pool simulation must return 2 denoms".to_string()) + ); + + // build the exit pool request based on the exit pool simulation + let exit_pool_request: CosmosMsg = MsgExitPool { + sender: env.contract.address.to_string(), + pool_id: config.pool_id.u64(), + share_in_amount: shares_to_redeem.to_string(), + token_out_mins: calc_exit_query_response.tokens_out.clone(), + } + .into(); + + // we build a context helper that will be used to + // return the resulting funds (and/or leftovers) to the sender + let callback_context = CallerContext { + sender: info.sender.to_string(), + gamm_denom: pool_shares_coin.denom.to_string(), + pool_denom_1: calc_exit_query_response.tokens_out[0].denom.to_string(), + pool_denom_2: calc_exit_query_response.tokens_out[1].denom.to_string(), + }; + + // store the callback context to be loaded in the callback + PENDING_REPLY.save(deps.storage, &callback_context)?; + + Ok(Response::default() + .add_attribute("method", "try_withdraw_liquidity") + .add_submessage(SubMsg::reply_always(exit_pool_request, OSMO_POOL_REPLY_ID))) +} + +fn try_provide_liquidity( + deps: DepsMut, + env: Env, + info: MessageInfo, + config: OutpostProvideLiquidityConfig, +) -> Result { + ensure!( + config.slippage_tolerance < Decimal::one(), + ContractError::SlippageError {} + ); + // first we query the pool for validation and info + let query_response: QueryPoolResponse = deps.querier.query( + &QueryPoolRequest { + pool_id: config.pool_id.u64(), + } + .into(), + )?; + let osmo_pool: Pool = decode_osmo_pool_binary(query_response.pool)?; + + // validate that the pool we wish to provide liquidity + // to is composed of two assets + osmo_pool.validate_pool_assets_length()?; + + // only gamm 50:50 pools are supported (for now) + osmo_pool.validate_pool_asset_weights()?; + + // collect the pool assets into cw coins + let pool_assets = osmo_pool.get_pool_cw_coins()?; + // get the total gamm shares cw_std coin + let gamm_shares_coin = osmo_pool.get_gamm_cw_coin()?; + + // validate the price against our expectations + let pool_spot_price = Decimal::from_ratio(pool_assets[0].amount, pool_assets[1].amount); + let min_acceptable_spot_price = config.expected_spot_price - config.acceptable_price_spread; + let max_acceptable_spot_price = config.expected_spot_price + config.acceptable_price_spread; + + if min_acceptable_spot_price > pool_spot_price || max_acceptable_spot_price < pool_spot_price { + return Err(ContractError::PriceRangeError {}); + } + + // get the amounts paid of pool denoms + let asset_1_received = Coin { + denom: pool_assets[0].denom.to_string(), + amount: get_paid_denom_amount(&info, &pool_assets[0].denom).unwrap_or(Uint128::zero()), + }; + let asset_2_received = Coin { + denom: pool_assets[1].denom.to_string(), + amount: get_paid_denom_amount(&info, &pool_assets[1].denom).unwrap_or(Uint128::zero()), + }; + + // we build a context helper that will be used to + // return the resulting funds to the sender + let callback_context = CallerContext { + sender: info.sender.to_string(), + gamm_denom: gamm_shares_coin.denom.to_string(), + pool_denom_1: asset_1_received.denom.to_string(), + pool_denom_2: asset_2_received.denom.to_string(), + }; + + // depending on which assets we have available, + // we construct different liquidity provision message + match ( + !asset_1_received.amount.is_zero(), + !asset_2_received.amount.is_zero(), + ) { + // both assets provided, attempt to provide two sided liquidity + (true, true) => provide_double_sided_liquidity( + deps, + env, + osmo_pool, + vec![asset_1_received, asset_2_received], + config.slippage_tolerance, + callback_context, + ), + // only asset 1 is provided, attempt to provide single sided + (true, false) => provide_single_sided_liquidity( + deps, + osmo_pool, + asset_1_received, + env.contract.address.to_string(), + config.slippage_tolerance, + config.asset_1_single_side_lp_limit, + callback_context, + ), + // only asset 2 is provided, attempt to provide single sided + (false, true) => provide_single_sided_liquidity( + deps, + osmo_pool, + asset_2_received, + env.contract.address.to_string(), + config.slippage_tolerance, + config.asset_2_single_side_lp_limit, + callback_context, + ), + // no funds provided, error out + (false, false) => Err(ContractError::LiquidityProvisionError( + "no funds provided".to_string(), + )), + } +} + +fn provide_double_sided_liquidity( + deps: DepsMut, + env: Env, + pool: Pool, + assets_paid: Vec, + slippage_tolerance: Decimal, + callback_ctx: CallerContext, +) -> Result { + let token_in_maxs: Vec = + vec![assets_paid[0].clone().into(), assets_paid[1].clone().into()]; + + // first we query the expected gamm amount + let query_response: QueryCalcJoinPoolNoSwapSharesResponse = deps.querier.query( + &QueryCalcJoinPoolNoSwapSharesRequest { + pool_id: pool.id, + tokens_in: token_in_maxs.clone(), + } + .into(), + )?; + + // expected gamm tokens + let response_gamm_coin = Coin { + denom: callback_ctx.gamm_denom.to_string(), + amount: Uint128::from_str(&query_response.shares_out)?, + }; + let expected_gamm_coin = apply_slippage(slippage_tolerance, response_gamm_coin)?; + + let osmo_msg: CosmosMsg = MsgJoinPool { + sender: env.contract.address.to_string(), + pool_id: pool.id, + // exact number of shares we wish to receive + share_out_amount: expected_gamm_coin.amount.to_string(), + token_in_maxs, + } + .into(); + + // store the callback context to be loaded in the callback + PENDING_REPLY.save(deps.storage, &callback_ctx)?; + + Ok(Response::default() + .add_attribute("method", "try_join_pool") + .add_submessage(SubMsg::reply_always(osmo_msg, OSMO_POOL_REPLY_ID))) +} + +fn provide_single_sided_liquidity( + deps: DepsMut, + pool: Pool, + asset_paid: Coin, + outpost: String, + slippage_tolerance: Decimal, + single_side_limit: Uint128, + callback_ctx: CallerContext, +) -> Result { + ensure!( + asset_paid.amount <= single_side_limit, + ContractError::SingleSideLiquidityProvisionError( + single_side_limit.to_string(), + asset_paid.amount.to_string(), + ) + ); + // first we query the expected gamm amount + let query_response: QueryCalcJoinPoolSharesResponse = deps.querier.query( + &QueryCalcJoinPoolSharesRequest { + pool_id: pool.id, + tokens_in: vec![asset_paid.clone().into()], + } + .into(), + )?; + + let response_gamm_coin = Coin { + denom: callback_ctx.gamm_denom.to_string(), + amount: Uint128::from_str(&query_response.share_out_amount)?, + }; + let expected_gamm_coin = apply_slippage(slippage_tolerance, response_gamm_coin)?; + + let join_pool_msg: CosmosMsg = MsgJoinSwapExternAmountIn { + sender: outpost, + pool_id: pool.id, + token_in: Some(asset_paid.clone().into()), + share_out_min_amount: expected_gamm_coin.amount.to_string(), + } + .into(); + + // store the callback context to be loaded in the callback + PENDING_REPLY.save(deps.storage, &callback_ctx)?; + + Ok(Response::default() + .add_attribute("method", "try_join_pool") + .add_submessage(SubMsg::reply_always(join_pool_msg, OSMO_POOL_REPLY_ID))) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult { + Err(StdError::NotFound { + kind: "not implemented".to_string(), + }) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { + match msg.id { + OSMO_POOL_REPLY_ID => handle_pool_interaction_reply(deps, env), + _ => Err(ContractError::UnknownReplyId(msg.id)), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult { + match msg { + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} + +fn handle_pool_interaction_reply(deps: DepsMut, env: Env) -> Result { + // load and clear the pending reply that we are processing + let callback_ctx = PENDING_REPLY.load(deps.storage)?; + PENDING_REPLY.remove(deps.storage); + + // we query the balances of relevant denoms + let available_gamm = deps.querier.query_balance( + env.contract.address.to_string(), + callback_ctx.gamm_denom.to_string(), + )?; + let leftover_asset_1 = deps + .querier + .query_balance(env.contract.address.to_string(), callback_ctx.pool_denom_1)?; + let leftover_asset_2 = deps + .querier + .query_balance(env.contract.address.to_string(), callback_ctx.pool_denom_2)?; + + // and collect them into tokens to be refunded (if any) + let refund_tokens: Vec = vec![available_gamm, leftover_asset_1, leftover_asset_2] + .into_iter() + .filter(|c| c.amount > Uint128::zero()) + .collect(); + + let mut response = Response::default().add_attribute("method", "handle_pool_interaction_reply"); + + if !refund_tokens.is_empty() { + response = response.add_message(BankMsg::Send { + to_address: callback_ctx.sender, + amount: refund_tokens.clone(), + }); + } + + Ok(response.add_attribute("refund_tokens", to_json_string(&refund_tokens)?)) +} + +/// cw-utils must pay requires specifically one coin, this is a helper +/// for multi-coin inputs +fn get_paid_denom_amount(info: &MessageInfo, target_denom: &str) -> StdResult { + for coin in &info.funds { + if coin.denom == target_denom { + return Ok(coin.amount); + } + } + Err(StdError::not_found(target_denom)) +} + +fn decode_osmo_pool_binary(pool: Option) -> StdResult { + let osmo_shim = match pool { + Some(shim) => shim, + None => { + return Err(StdError::NotFound { + kind: "shim not found".to_string(), + }) + } + }; + + match osmo_shim.try_into() { + Ok(result) => Ok(result), + Err(err) => Err(StdError::InvalidBase64 { + msg: err.to_string(), + }), + } +} + +fn apply_slippage(slippage: Decimal, coin: Coin) -> Result { + let applied_slippage_amount = match coin + .amount + .checked_multiply_ratio(slippage.numerator(), slippage.denominator()) + { + Ok(val) => val, + Err(e) => return Err(StdError::generic_err(e.to_string()).into()), + }; + + Ok(Coin { + denom: coin.denom, + amount: coin.amount - applied_slippage_amount, + }) +} diff --git a/contracts/outpost-osmo-liquid-pooler/src/error.rs b/contracts/outpost-osmo-liquid-pooler/src/error.rs new file mode 100644 index 00000000..bedfa5b9 --- /dev/null +++ b/contracts/outpost-osmo-liquid-pooler/src/error.rs @@ -0,0 +1,44 @@ +use cosmwasm_std::StdError; +use cw_utils::PaymentError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("only 50:50 pools are supported, got {0}")] + PoolRatioError(String), + + #[error("Osmosis pool error: {0}")] + OsmosisPoolError(String), + + #[error("liquidity provision error: {0}")] + LiquidityProvisionError(String), + + #[error("Fund deposit error: expected {0} bal {1}, got {2}")] + FundsDepositError(String, String, String), + + #[error("Slippage tolerance cannot be >= 1.0")] + SlippageError {}, + + #[error("Price range error")] + PriceRangeError {}, + + #[error("single side lp error: limit = {0}, got = {1}")] + SingleSideLiquidityProvisionError(String, String), + + #[error("unknown reply id: {0}")] + UnknownReplyId(u64), +} + +impl ContractError { + pub fn to_std(&self) -> StdError { + StdError::GenericErr { + msg: self.to_string(), + } + } +} diff --git a/contracts/outpost-osmo-liquid-pooler/src/lib.rs b/contracts/outpost-osmo-liquid-pooler/src/lib.rs new file mode 100644 index 00000000..47bc573c --- /dev/null +++ b/contracts/outpost-osmo-liquid-pooler/src/lib.rs @@ -0,0 +1,6 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/outpost-osmo-liquid-pooler/src/msg.rs b/contracts/outpost-osmo-liquid-pooler/src/msg.rs new file mode 100644 index 00000000..28d21e2c --- /dev/null +++ b/contracts/outpost-osmo-liquid-pooler/src/msg.rs @@ -0,0 +1,120 @@ +use std::str::FromStr; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Binary, Coin, Decimal, Uint128, Uint64}; +use osmosis_std::types::osmosis::gamm::v1beta1::Pool; + +use crate::error::ContractError; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub enum ExecuteMsg { + ProvideLiquidity { + config: OutpostProvideLiquidityConfig, + }, + WithdrawLiquidity { + config: OutpostWithdrawLiquidityConfig, + }, +} + +#[cw_serde] +pub struct OutpostProvideLiquidityConfig { + /// id of the pool we wish to provide liquidity to + pub pool_id: Uint64, + /// the price which we expect to provide liquidity at + pub expected_spot_price: Decimal, + /// acceptable delta (both ways) of the expected price + pub acceptable_price_spread: Decimal, + /// slippage tolerance + pub slippage_tolerance: Decimal, + /// limits for single-side liquidity provision + pub asset_1_single_side_lp_limit: Uint128, + pub asset_2_single_side_lp_limit: Uint128, +} + +#[cw_serde] +pub struct OutpostWithdrawLiquidityConfig { + /// id of the pool we wish to withdraw liquidity from + pub pool_id: Uint64, +} + +#[cw_serde] +pub struct CallerContext { + pub sender: String, + pub pool_denom_1: String, + pub pool_denom_2: String, + pub gamm_denom: String, +} + +#[cw_serde] +pub enum QueryMsg {} + +#[cw_serde] +pub enum MigrateMsg { + UpdateCodeId { data: Option }, +} + +pub trait OsmosisPool { + fn validate_pool_assets_length(&self) -> Result<(), ContractError>; + fn validate_pool_asset_weights(&self) -> Result<(), ContractError>; + fn get_pool_cw_coins(&self) -> Result, ContractError>; + fn get_gamm_cw_coin(&self) -> Result; +} + +impl OsmosisPool for Pool { + /// validate that the pool we wish to provide liquidity + /// to is composed of two assets + fn validate_pool_assets_length(&self) -> Result<(), ContractError> { + match self.pool_assets.len() { + 2 => Ok(()), + _ => Err(ContractError::OsmosisPoolError( + "pool must have 2 assets".to_string(), + )), + } + } + + /// only gamm 50:50 pools are supported (for now) + fn validate_pool_asset_weights(&self) -> Result<(), ContractError> { + if self.pool_assets[0].weight != self.pool_assets[1].weight { + Err(ContractError::PoolRatioError(format!( + "{:?}:{:?}", + self.pool_assets[0].weight, self.pool_assets[1].weight + ))) + } else { + Ok(()) + } + } + + /// collect the pool assets into cw coins + fn get_pool_cw_coins(&self) -> Result, ContractError> { + let mut pool_assets: Vec = vec![]; + for pool_asset in self.clone().pool_assets { + match pool_asset.token { + Some(t) => pool_assets.push(Coin { + denom: t.denom, + amount: Uint128::from_str(&t.amount)?, + }), + None => { + return Err(ContractError::OsmosisPoolError( + "failed to get pool token".to_string(), + )) + } + } + } + Ok(pool_assets) + } + + fn get_gamm_cw_coin(&self) -> Result { + match &self.total_shares { + Some(coin) => Ok(Coin { + denom: coin.denom.to_string(), + amount: Uint128::from_str(&coin.amount)?, + }), + None => Err(ContractError::OsmosisPoolError( + "expected Some(total_shares), found None".to_string(), + )), + } + } +} diff --git a/contracts/outpost-osmo-liquid-pooler/src/state.rs b/contracts/outpost-osmo-liquid-pooler/src/state.rs new file mode 100644 index 00000000..05677b26 --- /dev/null +++ b/contracts/outpost-osmo-liquid-pooler/src/state.rs @@ -0,0 +1,4 @@ +use crate::msg::CallerContext; +use cw_storage_plus::Item; + +pub const PENDING_REPLY: Item = Item::new("pending_reply"); diff --git a/contracts/remote-chain-splitter/.cargo/config b/contracts/remote-chain-splitter/.cargo/config new file mode 100644 index 00000000..6a6f2852 --- /dev/null +++ b/contracts/remote-chain-splitter/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" \ No newline at end of file diff --git a/contracts/remote-chain-splitter/Cargo.toml b/contracts/remote-chain-splitter/Cargo.toml new file mode 100644 index 00000000..31199a4d --- /dev/null +++ b/contracts/remote-chain-splitter/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "valence-remote-chain-splitter" +authors = ["benskey bekauz@protonmail.com"] +description = "contract to split funds on a remote chain" +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# disables #[entry_point] (i.e. instantiate/execute/query) export +library = [] + +[dependencies] +covenant-macros = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +covenant-utils = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +thiserror = { workspace = true } +schemars = { workspace = true } +serde-json-wasm = { workspace = true } +serde = { workspace = true } +neutron-sdk = { workspace = true } +cosmos-sdk-proto = { workspace = true } +cw-utils = { workspace = true } \ No newline at end of file diff --git a/contracts/remote-chain-splitter/README.md b/contracts/remote-chain-splitter/README.md new file mode 100644 index 00000000..70ce8d4f --- /dev/null +++ b/contracts/remote-chain-splitter/README.md @@ -0,0 +1,13 @@ +# Remote chain splitter + +Remote Chain Splitter is a module meant to facilitate predefined splitting of funds on a remote chain. + +First, splitter creates an ICA on the specified chain. +Once the ICA address is known, splitter waits for the funds to arrive. + +During instantiation, a vector of forwarder modules along with their respective amounts (`Vec`) are specified. +The forwarder modules are then queried for their deposit addresses, which are going to be their respective ICA addresses. + +A combined `BankSend` is then performed to the ICAs on the same remote chain. + +Remote chain splitter does not complete. In the future, it will be up to the top level covenant to dequeue it from the clock. diff --git a/contracts/ls/schema/covenant-ls.json b/contracts/remote-chain-splitter/schema/valence-remote-chain-splitter.json similarity index 52% rename from contracts/ls/schema/covenant-ls.json rename to contracts/remote-chain-splitter/schema/valence-remote-chain-splitter.json index 9dd06b12..d8f4e572 100644 --- a/contracts/ls/schema/covenant-ls.json +++ b/contracts/remote-chain-splitter/schema/valence-remote-chain-splitter.json @@ -1,5 +1,5 @@ { - "contract_name": "covenant-ls", + "contract_name": "valence-remote-chain-splitter", "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { @@ -7,26 +7,30 @@ "title": "InstantiateMsg", "type": "object", "required": [ + "amount", "clock_address", - "ibc_fee", + "denom", "ibc_transfer_timeout", "ica_timeout", - "lp_address", - "ls_denom", - "neutron_stride_ibc_connection_id", - "stride_neutron_ibc_transfer_channel_id" + "remote_chain_channel_id", + "remote_chain_connection_id", + "splits" ], "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, "clock_address": { "description": "Address for the clock. This contract verifies that only the clock can execute Ticks", "type": "string" }, - "ibc_fee": { - "description": "Neutron requires fees to be set to refund relayers for submission of ack and timeout messages. recv_fee and ack_fee paid in untrn from this contract", - "allOf": [ - { - "$ref": "#/definitions/IbcFee" - } + "denom": { + "type": "string" + }, + "fallback_address": { + "type": [ + "string", + "null" ] }, "ibc_transfer_timeout": { @@ -45,71 +49,40 @@ } ] }, - "lp_address": { - "description": "Address for the covenant's LP contract. We send the liquid staked amount to this address", - "type": "string" - }, - "ls_denom": { - "description": "The liquid staked denom (e.g., stuatom). This is required because we only allow transfers of this denom out of the LSer", + "remote_chain_channel_id": { "type": "string" }, - "neutron_stride_ibc_connection_id": { - "description": "IBC connection ID on Neutron for Stride We make an Interchain Account over this connection", + "remote_chain_connection_id": { "type": "string" }, - "stride_neutron_ibc_transfer_channel_id": { - "description": "IBC transfer channel on Stride for Neutron This is used to IBC transfer stuatom on Stride to the LP contract", - "type": "string" + "splits": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } } }, "additionalProperties": false, "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" }, - "IbcFee": { - "description": "IbcFee defines struct for fees that refund the relayer for `SudoMsg` messages submission. Unused fee kind will be returned back to message sender. Please refer to these links for more information: IBC transaction structure - General mechanics of fee payments - ", + "SplitConfig": { "type": "object", "required": [ - "ack_fee", - "recv_fee", - "timeout_fee" + "receivers" ], "properties": { - "ack_fee": { - "description": "*ack_fee** is an amount of coins to refund relayer for submitting ack message for a particular IBC packet.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "recv_fee": { - "description": "**recv_fee** currently is used for compatibility with ICS-29 interface only and must be set to zero (i.e. 0untrn), because Neutron's fee module can't refund relayer for submission of Recv IBC packets due to compatibility with target chains.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "timeout_fee": { - "description": "*timeout_fee** amount of coins to refund relayer for submitting timeout message for a particular IBC packet.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" } } - } + }, + "additionalProperties": false }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", @@ -126,20 +99,22 @@ "title": "ExecuteMsg", "oneOf": [ { - "description": "The transfer message allows anybody to permissionlessly transfer a specified amount of tokens of the preset ls_denom from the ICA of the host chain to the preset lp_address", "type": "object", "required": [ - "transfer" + "distribute_fallback" ], "properties": { - "transfer": { + "distribute_fallback": { "type": "object", "required": [ - "amount" + "coins" ], "properties": { - "amount": { - "$ref": "#/definitions/Uint128" + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } } }, "additionalProperties": false @@ -163,6 +138,21 @@ } ], "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -176,10 +166,10 @@ { "type": "object", "required": [ - "clock_address" + "contract_state" ], "properties": { - "clock_address": { + "contract_state": { "type": "object", "additionalProperties": false } @@ -189,10 +179,10 @@ { "type": "object", "required": [ - "stride_i_c_a" + "split_config" ], "properties": { - "stride_i_c_a": { + "split_config": { "type": "object", "additionalProperties": false } @@ -202,10 +192,10 @@ { "type": "object", "required": [ - "lp_address" + "transfer_amount" ], "properties": { - "lp_address": { + "transfer_amount": { "type": "object", "additionalProperties": false } @@ -215,10 +205,10 @@ { "type": "object", "required": [ - "contract_state" + "fallback_address" ], "properties": { - "contract_state": { + "fallback_address": { "type": "object", "additionalProperties": false } @@ -226,39 +216,27 @@ "additionalProperties": false }, { + "description": "Returns the associated clock address authorized to submit ticks", "type": "object", "required": [ - "acknowledgement_result" + "clock_address" ], "properties": { - "acknowledgement_result": { + "clock_address": { "type": "object", - "required": [ - "interchain_account_id", - "sequence_id" - ], - "properties": { - "interchain_account_id": { - "type": "string" - }, - "sequence_id": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, "additionalProperties": false } }, "additionalProperties": false }, { + "description": "Returns the associated remote chain information", "type": "object", "required": [ - "errors_queue" + "remote_chain_info" ], "properties": { - "errors_queue": { + "remote_chain_info": { "type": "object", "additionalProperties": false } @@ -266,12 +244,27 @@ "additionalProperties": false }, { + "description": "Returns the address a contract expects to receive funds to", "type": "object", "required": [ - "remote_chain_info" + "deposit_address" ], "properties": { - "remote_chain_info": { + "deposit_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated remote chain information", + "type": "object", + "required": [ + "ica_address" + ], + "properties": { + "ica_address": { "type": "object", "additionalProperties": false } @@ -299,10 +292,14 @@ "null" ] }, - "lp_address": { - "type": [ - "string", - "null" + "fallback_address": { + "anyOf": [ + { + "$ref": "#/definitions/FallbackAddressUpdateConfig" + }, + { + "type": "null" + } ] }, "remote_chain_info": { @@ -314,6 +311,15 @@ "type": "null" } ] + }, + "splits": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } } }, "additionalProperties": false @@ -352,52 +358,38 @@ "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", "type": "string" }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" }, - "IbcFee": { - "description": "IbcFee defines struct for fees that refund the relayer for `SudoMsg` messages submission. Unused fee kind will be returned back to message sender. Please refer to these links for more information: IBC transaction structure - General mechanics of fee payments - ", - "type": "object", - "required": [ - "ack_fee", - "recv_fee", - "timeout_fee" - ], - "properties": { - "ack_fee": { - "description": "*ack_fee** is an amount of coins to refund relayer for submitting ack message for a particular IBC packet.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "recv_fee": { - "description": "**recv_fee** currently is used for compatibility with ICS-29 interface only and must be set to zero (i.e. 0untrn), because Neutron's fee module can't refund relayer for submission of Recv IBC packets due to compatibility with target chains.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "FallbackAddressUpdateConfig": { + "oneOf": [ + { + "type": "object", + "required": [ + "explicit_address" + ], + "properties": { + "explicit_address": { + "type": "string" + } + }, + "additionalProperties": false }, - "timeout_fee": { - "description": "*timeout_fee** amount of coins to refund relayer for submitting timeout message for a particular IBC packet.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + { + "type": "object", + "required": [ + "disable" + ], + "properties": { + "disable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } - } + ] }, "RemoteChainInfo": { "type": "object", @@ -405,7 +397,6 @@ "channel_id", "connection_id", "denom", - "ibc_fee", "ibc_transfer_timeout", "ica_timeout" ], @@ -420,9 +411,6 @@ "denom": { "type": "string" }, - "ibc_fee": { - "$ref": "#/definitions/IbcFee" - }, "ibc_transfer_timeout": { "$ref": "#/definitions/Uint64" }, @@ -432,9 +420,21 @@ }, "additionalProperties": false }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false }, "Uint64": { "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", @@ -444,77 +444,6 @@ }, "sudo": null, "responses": { - "acknowledgement_result": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_AcknowledgementResult", - "anyOf": [ - { - "$ref": "#/definitions/AcknowledgementResult" - }, - { - "type": "null" - } - ], - "definitions": { - "AcknowledgementResult": { - "description": "Serves for storing acknowledgement calls for interchain transactions", - "oneOf": [ - { - "description": "Success - Got success acknowledgement in sudo with array of message item types in it", - "type": "object", - "required": [ - "success" - ], - "properties": { - "success": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - { - "description": "Error - Got error acknowledgement in sudo with payload message in it and error details", - "type": "object", - "required": [ - "error" - ], - "properties": { - "error": { - "type": "array", - "items": [ - { - "type": "string" - }, - { - "type": "string" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "description": "Timeout - Got timeout acknowledgement in sudo with payload message in it", - "type": "object", - "required": [ - "timeout" - ], - "properties": { - "timeout": { - "type": "string" - } - }, - "additionalProperties": false - } - ] - } - } - }, "clock_address": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Addr", @@ -527,37 +456,32 @@ "type": "string", "enum": [ "instantiated", - "i_c_a_created" + "ica_created" ] }, - "errors_queue": { + "deposit_address": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_Tuple_of_Array_of_uint8_and_String", - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "array", - "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - } - }, - { - "type": "string" - } - ], - "maxItems": 2, - "minItems": 2 - } + "title": "Nullable_String", + "type": [ + "string", + "null" + ] }, - "lp_address": { + "fallback_address": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "ica_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] }, "remote_chain_info": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -567,7 +491,6 @@ "channel_id", "connection_id", "denom", - "ibc_fee", "ibc_transfer_timeout", "ica_timeout" ], @@ -582,9 +505,6 @@ "denom": { "type": "string" }, - "ibc_fee": { - "$ref": "#/definitions/IbcFee" - }, "ibc_transfer_timeout": { "$ref": "#/definitions/Uint64" }, @@ -594,67 +514,56 @@ }, "additionalProperties": false, "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "split_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Tuple_of_String_and_SplitConfig", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/SplitConfig" } + ], + "maxItems": 2, + "minItems": 2 + }, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" }, - "IbcFee": { - "description": "IbcFee defines struct for fees that refund the relayer for `SudoMsg` messages submission. Unused fee kind will be returned back to message sender. Please refer to these links for more information: IBC transaction structure - General mechanics of fee payments - ", + "SplitConfig": { "type": "object", "required": [ - "ack_fee", - "recv_fee", - "timeout_fee" + "receivers" ], "properties": { - "ack_fee": { - "description": "*ack_fee** is an amount of coins to refund relayer for submitting ack message for a particular IBC packet.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "recv_fee": { - "description": "**recv_fee** currently is used for compatibility with ICS-29 interface only and must be set to zero (i.e. 0untrn), because Neutron's fee module can't refund relayer for submission of Recv IBC packets due to compatibility with target chains.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - }, - "timeout_fee": { - "description": "*timeout_fee** amount of coins to refund relayer for submitting timeout message for a particular IBC packet.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" } } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" + }, + "additionalProperties": false } } }, - "stride_i_c_a": { + "transfer_amount": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Addr", - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" } } diff --git a/contracts/depositor/examples/schema.rs b/contracts/remote-chain-splitter/src/bin/schema.rs similarity index 57% rename from contracts/depositor/examples/schema.rs rename to contracts/remote-chain-splitter/src/bin/schema.rs index e552a322..1a0618bd 100644 --- a/contracts/depositor/examples/schema.rs +++ b/contracts/remote-chain-splitter/src/bin/schema.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::write_api; -use covenant_depositor::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use neutron_sdk::sudo::msg::SudoMsg; +use valence_remote_chain_splitter::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { @@ -8,6 +7,5 @@ fn main() { execute: ExecuteMsg, query: QueryMsg, migrate: MigrateMsg, - sudo: SudoMsg, } } diff --git a/contracts/remote-chain-splitter/src/contract.rs b/contracts/remote-chain-splitter/src/contract.rs new file mode 100644 index 00000000..fbe72dd7 --- /dev/null +++ b/contracts/remote-chain-splitter/src/contract.rs @@ -0,0 +1,509 @@ +use std::collections::{BTreeSet, HashSet}; +use std::str::FromStr; + +use cosmos_sdk_proto::cosmos::bank::v1beta1::{Input, MsgMultiSend, Output}; +use cosmos_sdk_proto::cosmos::base::v1beta1::Coin; +use cosmos_sdk_proto::traits::Message; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, to_json_binary, Attribute, Binary, Deps, DepsMut, Env, Fraction, MessageInfo, Reply, + Response, StdError, StdResult, Uint128, +}; +use covenant_utils::ica::{ + get_ica, msg_with_sudo_callback, prepare_sudo_payload, query_ica_registration_fee, sudo_error, + sudo_open_ack, sudo_response, sudo_timeout, INTERCHAIN_ACCOUNT_ID, +}; +use covenant_utils::neutron::{ + assert_ibc_fee_coverage, get_proto_coin, query_ibc_fee, RemoteChainInfo, SudoPayload, +}; +use covenant_utils::{neutron, soft_validate_remote_chain_addr}; +use cw2::set_contract_version; +use neutron_sdk::bindings::types::ProtobufAny; +use neutron_sdk::interchain_txs::helpers::get_port_id; +use neutron_sdk::query::min_ibc_fee::MinIbcFeeResponse; +use neutron_sdk::sudo::msg::SudoMsg; +use neutron_sdk::NeutronError; +use valence_clock::helpers::{enqueue_msg, verify_clock}; + +use crate::error::ContractError; +use crate::msg::{ + ContractState, ExecuteMsg, FallbackAddressUpdateConfig, InstantiateMsg, MigrateMsg, QueryMsg, +}; +use crate::state::{ + RemoteChainSplitteIcaStateHelper, CLOCK_ADDRESS, CONTRACT_STATE, FALLBACK_ADDRESS, + INTERCHAIN_ACCOUNTS, REMOTE_CHAIN_INFO, SPLIT_CONFIG_MAP, TRANSFER_AMOUNT, +}; +use neutron_sdk::{ + bindings::{msg::NeutronMsg, query::NeutronQuery}, + NeutronResult, +}; + +type QueryDeps<'a> = Deps<'a, NeutronQuery>; +type ExecuteDeps<'a> = DepsMut<'a, NeutronQuery>; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub const SUDO_PAYLOAD_REPLY_ID: u64 = 1u64; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: ExecuteDeps, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> NeutronResult> { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let clock_addr = deps.api.addr_validate(&msg.clock_address)?; + CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; + + let remote_chain_info = RemoteChainInfo { + connection_id: msg.remote_chain_connection_id, + channel_id: msg.remote_chain_channel_id, + denom: msg.denom, + ibc_transfer_timeout: msg.ibc_transfer_timeout, + ica_timeout: msg.ica_timeout, + }; + REMOTE_CHAIN_INFO.save(deps.storage, &remote_chain_info)?; + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + TRANSFER_AMOUNT.save(deps.storage, &msg.amount)?; + if let Some(addr) = &msg.fallback_address { + soft_validate_remote_chain_addr(deps.api, addr)?; + FALLBACK_ADDRESS.save(deps.storage, addr)?; + } + + // validate each split and store it in a map + let mut split_resp_attributes: Vec = Vec::with_capacity(msg.splits.len()); + + for (denom, split_config) in msg.splits { + split_config.validate_shares_and_receiver_addresses(deps.api)?; + split_resp_attributes.push(split_config.get_response_attribute(denom.to_string())); + SPLIT_CONFIG_MAP.save(deps.storage, denom, &split_config)?; + } + + Ok(Response::default() + .add_message(enqueue_msg(clock_addr.as_str())?) + .add_attribute("method", "remote_chain_splitter_instantiate") + .add_attribute("clock_address", clock_addr) + .add_attributes(remote_chain_info.get_response_attributes()) + .add_attributes(split_resp_attributes)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: ExecuteDeps, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> NeutronResult> { + match msg { + ExecuteMsg::Tick {} => try_tick(deps, env, info), + ExecuteMsg::DistributeFallback { coins } => try_distribute_fallback(deps, env, info, coins), + } +} + +fn try_distribute_fallback( + mut deps: ExecuteDeps, + env: Env, + info: MessageInfo, + coins: Vec, +) -> NeutronResult> { + // load the fallback address or error out if its not set + let destination = match FALLBACK_ADDRESS.may_load(deps.storage)? { + Some(addr) => addr, + None => return Err(ContractError::MissingFallbackAddress {}.into()), + }; + let remote_chain_info = REMOTE_CHAIN_INFO.load(deps.storage)?; + let ibc_fee_response = query_ibc_fee(deps.querier)?; + + assert_ibc_fee_coverage(info, ibc_fee_response.total_ntrn_fee, Uint128::one())?; + + // we iterate over coins to be distributed, validate them, and generate the proto coins to be sent + let mut encountered_denoms: BTreeSet = BTreeSet::new(); + let mut proto_coins: Vec = vec![]; + + for coin in coins { + // validate that target denom is not passed for fallback distribution + ensure!( + coin.denom != remote_chain_info.denom, + Into::::into(ContractError::UnauthorizedDenomDistribution {}) + ); + + // error out if denom is duplicated + ensure!( + encountered_denoms.insert(coin.denom.to_string()), + Into::::into(ContractError::DuplicateDenomDistribution {}) + ); + + proto_coins.push(get_proto_coin(coin.denom, coin.amount)); + } + + let port_id = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + let interchain_account = INTERCHAIN_ACCOUNTS.may_load(deps.storage, port_id.clone())?; + if let Some(Some((address, controller_conn_id))) = interchain_account { + let multi_send_msg = MsgMultiSend { + inputs: vec![Input { + address, + coins: proto_coins.clone(), + }], + outputs: vec![Output { + address: destination, + coins: proto_coins, + }], + }; + + let mut buf = Vec::with_capacity(multi_send_msg.encoded_len()); + if let Err(e) = multi_send_msg.encode(&mut buf) { + return Err(NeutronError::Std(StdError::generic_err(format!( + "Encode error: {e:}", + )))); + } + + let any_msg = ProtobufAny { + type_url: "/cosmos.bank.v1beta1.MsgMultiSend".to_string(), + value: Binary::from(buf), + }; + let submit_msg = NeutronMsg::submit_tx( + controller_conn_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + vec![any_msg], + "".to_string(), + remote_chain_info.ica_timeout.u64(), + ibc_fee_response.ibc_fee, + ); + let sudo_msg = msg_with_sudo_callback( + &RemoteChainSplitteIcaStateHelper, + deps.branch(), + submit_msg, + SudoPayload { + port_id, + message: "distribute_fallback_multisend".to_string(), + }, + SUDO_PAYLOAD_REPLY_ID, + )?; + + Ok(Response::default() + .add_attribute("method", "try_forward_fallback") + .add_submessages(vec![sudo_msg])) + } else { + Err(NeutronError::Std(StdError::generic_err("no ica found"))) + } +} + +/// attempts to advance the state machine. performs `info.sender` validation +fn try_tick(deps: ExecuteDeps, env: Env, info: MessageInfo) -> NeutronResult> { + // Verify caller is the clock + verify_clock(&info.sender, &CLOCK_ADDRESS.load(deps.storage)?)?; + + match CONTRACT_STATE.load(deps.storage)? { + ContractState::Instantiated => try_register_ica(deps, env), + ContractState::IcaCreated => try_split_funds(deps, env), + } +} + +fn try_register_ica(deps: ExecuteDeps, env: Env) -> NeutronResult> { + let remote_chain_info = REMOTE_CHAIN_INFO.load(deps.storage)?; + let ica_registration_fee = query_ica_registration_fee(deps.querier)?; + + let register: NeutronMsg = NeutronMsg::register_interchain_account( + remote_chain_info.connection_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + Some(ica_registration_fee), + ); + let key = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + + // we are saving empty data here because we handle response of registering ICA in sudo_open_ack method + INTERCHAIN_ACCOUNTS.save(deps.storage, key, &None)?; + + Ok(Response::new() + .add_attribute("method", "try_register_ica") + .add_message(register)) +} + +fn try_split_funds(mut deps: ExecuteDeps, env: Env) -> NeutronResult> { + let port_id = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + let interchain_account = INTERCHAIN_ACCOUNTS.load(deps.storage, port_id.clone())?; + let amount = TRANSFER_AMOUNT.load(deps.storage)?; + let min_fee_query_response: MinIbcFeeResponse = + deps.querier.query(&NeutronQuery::MinIbcFee {}.into())?; + + match interchain_account { + Some((address, controller_conn_id)) => { + let remote_chain_info = REMOTE_CHAIN_INFO.load(deps.storage)?; + + let splits = SPLIT_CONFIG_MAP + .load(deps.storage, remote_chain_info.denom.to_string())? + .receivers; + + let mut outputs: Vec = Vec::with_capacity(splits.len()); + let mut total_allocated = Uint128::zero(); + for (split_receiver, share) in splits.iter() { + // query the ibc forwarders for their ICA addresses + // if either does not exist yet, error out + let forwarder_deposit_address: Option = deps.querier.query_wasm_smart( + split_receiver.to_string(), + &neutron::CovenantQueryMsg::DepositAddress {}, + )?; + + let receiver_ica = match forwarder_deposit_address { + Some(ica) => ica, + None => { + return Err(NeutronError::Std(StdError::NotFound { + kind: "forwarder ica not created".to_string(), + })) + } + }; + + // get the fraction dedicated to this receiver + let amt = amount + .checked_multiply_ratio(share.numerator(), share.denominator()) + .map_err(|e: cosmwasm_std::CheckedMultiplyRatioError| { + NeutronError::Std(StdError::GenericErr { msg: e.to_string() }) + })?; + + let coin = Coin { + denom: remote_chain_info.denom.to_string(), + amount: amt.to_string(), + }; + let output = Output { + address: receiver_ica, + coins: vec![coin.clone()], + }; + total_allocated += amt; + outputs.push(output); + } + + // if there is no leftover, nothing happens. + // otherwise we add the leftover to the first receiver. + if let Some(output) = outputs.first_mut() { + output.coins[0].amount = (Uint128::from_str(&output.coins[0].amount)? + + (amount - total_allocated)) + .to_string(); + } + + let mut inputs: Vec = Vec::new(); + let input = Input { + address: address.to_string(), + coins: vec![Coin { + denom: remote_chain_info.denom, + amount: amount.to_string(), + }], + }; + inputs.push(input); + + let multi_send_msg = MsgMultiSend { inputs, outputs }; + + // Serialize the multi send message. + let mut buf = Vec::with_capacity(multi_send_msg.encoded_len()); + + if let Err(e) = multi_send_msg.encode(&mut buf) { + return Err(NeutronError::Std(StdError::generic_err(format!( + "Encode error: {}", + e + )))); + } + + let any_msg = ProtobufAny { + type_url: "/cosmos.bank.v1beta1.MsgMultiSend".to_string(), + value: Binary::from(buf), + }; + let submit_msg = NeutronMsg::submit_tx( + controller_conn_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + vec![any_msg], + "".to_string(), + remote_chain_info.ica_timeout.u64(), + min_fee_query_response.min_fee, + ); + let sudo_msg = msg_with_sudo_callback( + &RemoteChainSplitteIcaStateHelper, + deps.branch(), + submit_msg, + SudoPayload { + port_id, + message: "split_funds_msg".to_string(), + }, + SUDO_PAYLOAD_REPLY_ID, + )?; + Ok(Response::default() + .add_attribute("method", "try_split_funds") + .add_submessages(vec![sudo_msg])) + } + None => { + // I can't think of a case of how we could end up here as `sudo_open_ack` + // callback advances the state to `ICACreated` and stores the ICA. + // just in case, we revert the state to `Instantiated` to restart the flow. + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + Ok(Response::default() + .add_attribute("method", "try_execute_split_funds") + .add_attribute("error", "no_ica_found")) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: QueryDeps, env: Env, msg: QueryMsg) -> NeutronResult { + match msg { + QueryMsg::ClockAddress {} => Ok(to_json_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::ContractState {} => Ok(to_json_binary(&CONTRACT_STATE.may_load(deps.storage)?)?), + QueryMsg::DepositAddress {} => { + let ica = query_deposit_address(deps, env)?; + // up to the querying module to make sense of the response + Ok(to_json_binary(&ica)?) + } + QueryMsg::RemoteChainInfo {} => { + Ok(to_json_binary(&REMOTE_CHAIN_INFO.may_load(deps.storage)?)?) + } + QueryMsg::SplitConfig {} => { + let vec = SPLIT_CONFIG_MAP + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .collect::, StdError>>()?; + + Ok(to_json_binary(&vec)?) + } + QueryMsg::TransferAmount {} => { + Ok(to_json_binary(&TRANSFER_AMOUNT.may_load(deps.storage)?)?) + } + QueryMsg::IcaAddress {} => Ok(to_json_binary( + &get_ica( + &RemoteChainSplitteIcaStateHelper, + deps.storage, + env.contract.address.as_str(), + INTERCHAIN_ACCOUNT_ID, + )? + .0, + )?), + QueryMsg::FallbackAddress {} => { + Ok(to_json_binary(&FALLBACK_ADDRESS.may_load(deps.storage)?)?) + } + } +} + +fn query_deposit_address(deps: QueryDeps, env: Env) -> Result, StdError> { + let key = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + /* + here we cover three possible cases: + - 1. ICA had been created -> nice + - 2. ICA creation request had been submitted but did not receive + the channel_open_ack yet -> None + - 3. ICA creation request hadn't been submitted yet -> None + */ + INTERCHAIN_ACCOUNTS + .may_load(deps.storage, key) + .map(|entry| entry.flatten().map(|x| x.0)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: ExecuteDeps, env: Env, msg: SudoMsg) -> StdResult> { + match msg { + // For handling successful (non-error) acknowledgements. + SudoMsg::Response { request, data } => sudo_response(request, data), + // For handling error acknowledgements. + SudoMsg::Error { request, details } => sudo_error(request, details), + // For handling error timeouts. + SudoMsg::Timeout { request } => { + sudo_timeout(&RemoteChainSplitteIcaStateHelper, deps, env, request) + } + // For handling successful registering of ICA + SudoMsg::OpenAck { + port_id, + channel_id, + counterparty_channel_id, + counterparty_version, + } => sudo_open_ack( + &RemoteChainSplitteIcaStateHelper, + deps, + env, + port_id, + channel_id, + counterparty_channel_id, + counterparty_version, + ), + _ => Ok(Response::default()), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: ExecuteDeps, _env: Env, msg: MigrateMsg) -> StdResult> { + match msg { + MigrateMsg::UpdateConfig { + clock_addr, + remote_chain_info, + splits, + fallback_address, + } => { + let mut resp = Response::default().add_attribute("method", "update_config"); + + if let Some(addr) = clock_addr { + let clock_address = deps.api.addr_validate(&addr)?; + CLOCK_ADDRESS.save(deps.storage, &clock_address)?; + resp = resp.add_attribute("clock_addr", addr); + } + + if let Some(remote_chain_info) = remote_chain_info { + REMOTE_CHAIN_INFO.save(deps.storage, &remote_chain_info)?; + resp = resp.add_attribute("remote_chain_info", format!("{remote_chain_info:?}")); + } + + if let Some(splits) = splits { + let mut split_resp_attributes: Vec = Vec::with_capacity(splits.len()); + let mut encountered_denoms: HashSet = HashSet::with_capacity(splits.len()); + + for (denom, split) in splits { + // if denom had not yet been encountered we proceed, otherwise error + if encountered_denoms.insert(denom.to_string()) { + split.validate_shares_and_receiver_addresses(deps.api)?; + split_resp_attributes.push(split.get_response_attribute(denom.to_string())); + SPLIT_CONFIG_MAP.save(deps.storage, denom.to_string(), &split)?; + + resp = resp.add_attribute( + format!("split-{}", denom), + format!("{:?}", split.receivers), + ); + } else { + return Err(StdError::generic_err(format!( + "multiple {:?} entries", + denom + ))); + } + } + } + + if let Some(config) = fallback_address { + match config { + FallbackAddressUpdateConfig::ExplicitAddress(addr) => { + FALLBACK_ADDRESS.save(deps.storage, &addr)?; + resp = resp.add_attribute("fallback_address", addr); + } + FallbackAddressUpdateConfig::Disable {} => { + FALLBACK_ADDRESS.remove(deps.storage); + resp = resp.add_attribute("fallback_address", "removed"); + } + } + } + + Ok(resp) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: ExecuteDeps, env: Env, msg: Reply) -> StdResult> { + match msg.id { + SUDO_PAYLOAD_REPLY_ID => { + prepare_sudo_payload(&RemoteChainSplitteIcaStateHelper, deps, env, msg) + } + _ => Err(StdError::generic_err(format!( + "unsupported reply message id {}", + msg.id + ))), + } +} diff --git a/contracts/remote-chain-splitter/src/error.rs b/contracts/remote-chain-splitter/src/error.rs new file mode 100644 index 00000000..8c403f55 --- /dev/null +++ b/contracts/remote-chain-splitter/src/error.rs @@ -0,0 +1,24 @@ +use cosmwasm_std::StdError; +use neutron_sdk::NeutronError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Missing fallback address")] + MissingFallbackAddress {}, + + #[error("Cannot distribute target denom via fallback distribution")] + UnauthorizedDenomDistribution {}, + + #[error("Attempt to distribute duplicate denoms via fallback distribution")] + DuplicateDenomDistribution {}, +} + +impl From for NeutronError { + fn from(value: ContractError) -> Self { + NeutronError::Std(StdError::generic_err(value.to_string())) + } +} diff --git a/contracts/remote-chain-splitter/src/lib.rs b/contracts/remote-chain-splitter/src/lib.rs new file mode 100644 index 00000000..47bc573c --- /dev/null +++ b/contracts/remote-chain-splitter/src/lib.rs @@ -0,0 +1,6 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/remote-chain-splitter/src/msg.rs b/contracts/remote-chain-splitter/src/msg.rs new file mode 100644 index 00000000..af4687be --- /dev/null +++ b/contracts/remote-chain-splitter/src/msg.rs @@ -0,0 +1,107 @@ +use std::collections::BTreeMap; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{to_json_binary, Addr, Binary, Coin, StdResult, Uint128, Uint64, WasmMsg}; +use covenant_macros::{ + clocked, covenant_clock_address, covenant_deposit_address, covenant_ica_address, + covenant_remote_chain, +}; + +use covenant_utils::{ + instantiate2_helper::Instantiate2HelperConfig, neutron::RemoteChainInfo, split::SplitConfig, +}; + +#[cw_serde] +pub struct InstantiateMsg { + /// Address for the clock. This contract verifies + /// that only the clock can execute Ticks + pub clock_address: String, + + pub remote_chain_connection_id: String, + pub remote_chain_channel_id: String, + pub denom: String, + pub amount: Uint128, + + pub splits: BTreeMap, + + /// Time in seconds for ICA SubmitTX messages from Neutron + /// Note that ICA uses ordered channels, a timeout implies + /// channel closed. We can reopen the channel by reregistering + /// the ICA with the same port id and connection id + pub ica_timeout: Uint64, + /// Timeout in seconds. This is used to craft a timeout timestamp + /// that will be attached to the IBC transfer message from the ICA + /// on the host chain (Stride) to its destination. Typically + /// this timeout should be greater than the ICA timeout, otherwise + /// if the ICA times out, the destination chain receiving the funds + /// will also receive the IBC packet with an expired timestamp. + pub ibc_transfer_timeout: Uint64, + // fallback address on the remote chain + pub fallback_address: Option, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +#[clocked] +#[cw_serde] +pub enum ExecuteMsg { + DistributeFallback { coins: Vec }, +} + +#[covenant_clock_address] +#[covenant_remote_chain] +#[covenant_deposit_address] +#[covenant_ica_address] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(ContractState)] + ContractState {}, + #[returns(Vec<(String, SplitConfig)>)] + SplitConfig {}, + #[returns(Uint128)] + TransferAmount {}, + #[returns(Option)] + FallbackAddress {}, +} + +#[cw_serde] +pub enum ContractState { + Instantiated, + IcaCreated, +} + +#[cw_serde] +pub enum FallbackAddressUpdateConfig { + ExplicitAddress(String), + Disable {}, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + remote_chain_info: Option, + splits: Option>, + fallback_address: Option, + }, + UpdateCodeId { + data: Option, + }, +} diff --git a/contracts/remote-chain-splitter/src/state.rs b/contracts/remote-chain-splitter/src/state.rs new file mode 100644 index 00000000..05fa2953 --- /dev/null +++ b/contracts/remote-chain-splitter/src/state.rs @@ -0,0 +1,89 @@ +use cosmwasm_std::{from_json, to_json_vec, Addr, Binary, StdError, StdResult, Storage, Uint128}; +use covenant_utils::{ + ica::IcaStateHelper, + neutron::{RemoteChainInfo, SudoPayload}, + split::SplitConfig, +}; +use cw_storage_plus::{Item, Map}; + +use crate::msg::ContractState; + +/// tracks the current state of state machine +pub const CONTRACT_STATE: Item = Item::new("contract_state"); + +/// clock module address to verify the sender of incoming ticks +pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); + +pub const TRANSFER_AMOUNT: Item = Item::new("transfer_amount"); + +// maps a denom string to a vec of SplitReceivers +pub const SPLIT_CONFIG_MAP: Map = Map::new("split_config"); + +/// information needed for an ibc transfer to the remote chain +pub const REMOTE_CHAIN_INFO: Item = Item::new("r_c_info"); + +pub const FALLBACK_ADDRESS: Item = Item::new("fallback_address"); + +/// interchain accounts storage in form of (port_id) -> (address, controller_connection_id) +pub const INTERCHAIN_ACCOUNTS: Map> = + Map::new("interchain_accounts"); + +pub const REPLY_ID_STORAGE: Item> = Item::new("reply_queue_id"); +pub const SUDO_PAYLOAD: Map<(String, u64), Vec> = Map::new("sudo_payload"); +pub const ERRORS_QUEUE: Map = Map::new("errors_queue"); + +pub(crate) struct RemoteChainSplitteIcaStateHelper; + +impl IcaStateHelper for RemoteChainSplitteIcaStateHelper { + fn reset_state(&self, storage: &mut dyn Storage) -> StdResult<()> { + CONTRACT_STATE.save(storage, &ContractState::Instantiated)?; + Ok(()) + } + + fn clear_ica(&self, storage: &mut dyn Storage) -> StdResult<()> { + INTERCHAIN_ACCOUNTS.clear(storage); + Ok(()) + } + + fn save_ica( + &self, + storage: &mut dyn Storage, + port_id: String, + address: String, + controller_connection_id: String, + ) -> StdResult<()> { + INTERCHAIN_ACCOUNTS.save(storage, port_id, &Some((address, controller_connection_id)))?; + Ok(()) + } + + fn save_state_ica_created(&self, storage: &mut dyn Storage) -> StdResult<()> { + CONTRACT_STATE.save(storage, &ContractState::IcaCreated)?; + Ok(()) + } + + fn save_reply_payload(&self, storage: &mut dyn Storage, payload: SudoPayload) -> StdResult<()> { + REPLY_ID_STORAGE.save(storage, &to_json_vec(&payload)?)?; + Ok(()) + } + + fn read_reply_payload(&self, storage: &mut dyn Storage) -> StdResult { + let data = REPLY_ID_STORAGE.load(storage)?; + from_json(Binary(data)) + } + + fn save_sudo_payload( + &self, + storage: &mut dyn Storage, + channel_id: String, + seq_id: u64, + payload: SudoPayload, + ) -> StdResult<()> { + SUDO_PAYLOAD.save(storage, (channel_id, seq_id), &to_json_vec(&payload)?) + } + + fn get_ica(&self, storage: &dyn Storage, key: String) -> StdResult<(String, String)> { + INTERCHAIN_ACCOUNTS + .load(storage, key)? + .ok_or_else(|| StdError::generic_err("Interchain account is not created yet")) + } +} diff --git a/contracts/single-party-pol-covenant/.cargo/config b/contracts/single-party-pol-covenant/.cargo/config new file mode 100644 index 00000000..5f6aa466 --- /dev/null +++ b/contracts/single-party-pol-covenant/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" diff --git a/contracts/single-party-pol-covenant/Cargo.toml b/contracts/single-party-pol-covenant/Cargo.toml new file mode 100644 index 00000000..369ef664 --- /dev/null +++ b/contracts/single-party-pol-covenant/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "valence-covenant-single-party-pol" +edition = { workspace = true } +authors = ["benskey bekauz@protonmail.com", "Art3miX "] +description = "Single Party POL covenant" +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } + +exclude = ["contract.wasm", "hash.txt"] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +sha2 = { workspace = true } +neutron-sdk = { workspace = true } +cosmos-sdk-proto = { workspace = true } +schemars = { workspace = true } +serde-json-wasm = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +bech32 = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +covenant-utils = { workspace = true } +valence-ibc-forwarder = { workspace = true, features = ["library"] } +valence-interchain-router = { workspace = true, features = ["library"] } +valence-single-party-pol-holder = { workspace = true, features = ["library"] } +valence-astroport-liquid-pooler = { workspace = true, features = ["library"] } +valence-stride-liquid-staker = { workspace = true, features = ["library"] } +valence-remote-chain-splitter = { workspace = true, features = ["library"] } +valence-osmo-liquid-pooler = { workspace = true, features = ["library"] } +astroport = { workspace = true } diff --git a/contracts/single-party-pol-covenant/README.md b/contracts/single-party-pol-covenant/README.md new file mode 100644 index 00000000..049f19b3 --- /dev/null +++ b/contracts/single-party-pol-covenant/README.md @@ -0,0 +1,3 @@ +# single party POL covenant + +TODO diff --git a/contracts/single-party-pol-covenant/schema/valence-covenant-single-party-pol.json b/contracts/single-party-pol-covenant/schema/valence-covenant-single-party-pol.json new file mode 100644 index 00000000..b03ea07c --- /dev/null +++ b/contracts/single-party-pol-covenant/schema/valence-covenant-single-party-pol.json @@ -0,0 +1,2480 @@ +{ + "contract_name": "valence-covenant-single-party-pol", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "contract_codes", + "covenant_party_config", + "label", + "liquid_pooler_config", + "lockup_period", + "lp_forwarder_config", + "ls_forwarder_config", + "ls_info", + "pool_price_config", + "remote_chain_splitter_config", + "timeouts" + ], + "properties": { + "clock_tick_max_gas": { + "anyOf": [ + { + "$ref": "#/definitions/Uint64" + }, + { + "type": "null" + } + ] + }, + "contract_codes": { + "$ref": "#/definitions/CovenantContractCodeIds" + }, + "covenant_party_config": { + "$ref": "#/definitions/InterchainCovenantParty" + }, + "emergency_committee": { + "type": [ + "string", + "null" + ] + }, + "label": { + "type": "string" + }, + "liquid_pooler_config": { + "$ref": "#/definitions/LiquidPoolerConfig" + }, + "lockup_period": { + "$ref": "#/definitions/Expiration" + }, + "lp_forwarder_config": { + "$ref": "#/definitions/CovenantPartyConfig" + }, + "ls_forwarder_config": { + "$ref": "#/definitions/CovenantPartyConfig" + }, + "ls_info": { + "$ref": "#/definitions/LsInfo" + }, + "pool_price_config": { + "$ref": "#/definitions/PoolPriceConfig" + }, + "remote_chain_splitter_config": { + "$ref": "#/definitions/RemoteChainSplitterConfig" + }, + "timeouts": { + "$ref": "#/definitions/Timeouts" + } + }, + "additionalProperties": false, + "definitions": { + "AstroportLiquidPoolerConfig": { + "type": "object", + "required": [ + "asset_a_denom", + "asset_b_denom", + "pool_address", + "pool_pair_type", + "single_side_lp_limits" + ], + "properties": { + "asset_a_denom": { + "type": "string" + }, + "asset_b_denom": { + "type": "string" + }, + "pool_address": { + "type": "string" + }, + "pool_pair_type": { + "$ref": "#/definitions/PairType" + }, + "single_side_lp_limits": { + "$ref": "#/definitions/SingleSideLpLimits" + } + }, + "additionalProperties": false + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CovenantContractCodeIds": { + "type": "object", + "required": [ + "clock_code", + "holder_code", + "ibc_forwarder_code", + "interchain_router_code", + "liquid_pooler_code", + "liquid_staker_code", + "remote_chain_splitter_code" + ], + "properties": { + "clock_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "holder_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "ibc_forwarder_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interchain_router_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "liquid_pooler_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "liquid_staker_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "remote_chain_splitter_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "CovenantPartyConfig": { + "oneOf": [ + { + "type": "object", + "required": [ + "interchain" + ], + "properties": { + "interchain": { + "$ref": "#/definitions/InterchainCovenantParty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "$ref": "#/definitions/NativeCovenantParty" + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "ForwardMetadata": { + "type": "object", + "required": [ + "channel", + "port", + "receiver" + ], + "properties": { + "channel": { + "type": "string" + }, + "port": { + "type": "string" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + }, + "InterchainCovenantParty": { + "type": "object", + "required": [ + "addr", + "contribution", + "denom_to_pfm_map", + "host_to_party_chain_channel_id", + "ibc_transfer_timeout", + "native_denom", + "party_chain_connection_id", + "party_receiver_addr", + "party_to_host_chain_channel_id", + "remote_chain_denom" + ], + "properties": { + "addr": { + "description": "authorized address of the party on neutron", + "type": "string" + }, + "contribution": { + "description": "coin provided by the party on its native chain", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "denom_to_pfm_map": { + "description": "configuration for unwinding the denoms via pfm", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "fallback_address": { + "description": "fallback refund address on the remote chain", + "type": [ + "string", + "null" + ] + }, + "host_to_party_chain_channel_id": { + "description": "channel id from host chain to the party chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "native_denom": { + "description": "denom provided by the party on neutron", + "type": "string" + }, + "party_chain_connection_id": { + "description": "connection id to the party chain", + "type": "string" + }, + "party_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "party_to_host_chain_channel_id": { + "description": "channel id from party to host chain", + "type": "string" + }, + "remote_chain_denom": { + "description": "denom provided by the party on its native chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "LiquidPoolerConfig": { + "oneOf": [ + { + "type": "object", + "required": [ + "osmosis" + ], + "properties": { + "osmosis": { + "$ref": "#/definitions/OsmosisLiquidPoolerConfig" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "astroport" + ], + "properties": { + "astroport": { + "$ref": "#/definitions/AstroportLiquidPoolerConfig" + } + }, + "additionalProperties": false + } + ] + }, + "LsInfo": { + "type": "object", + "required": [ + "ls_chain_to_neutron_channel_id", + "ls_denom", + "ls_denom_on_neutron", + "ls_neutron_connection_id" + ], + "properties": { + "ls_chain_to_neutron_channel_id": { + "type": "string" + }, + "ls_denom": { + "type": "string" + }, + "ls_denom_on_neutron": { + "type": "string" + }, + "ls_neutron_connection_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "NativeCovenantParty": { + "type": "object", + "required": [ + "addr", + "contribution", + "native_denom", + "party_receiver_addr" + ], + "properties": { + "addr": { + "description": "authorized address of the party on neutron", + "type": "string" + }, + "contribution": { + "description": "coin provided by the party on its native chain", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "native_denom": { + "description": "denom provided by the party on neutron", + "type": "string" + }, + "party_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "OsmosisLiquidPoolerConfig": { + "type": "object", + "required": [ + "funding_duration", + "lp_token_denom", + "note_address", + "osmo_ibc_timeout", + "osmo_outpost", + "osmo_to_neutron_channel_id", + "party_1_chain_info", + "party_1_denom_info", + "party_2_chain_info", + "party_2_denom_info", + "pool_id", + "single_side_lp_limits" + ], + "properties": { + "funding_duration": { + "$ref": "#/definitions/Duration" + }, + "lp_token_denom": { + "type": "string" + }, + "note_address": { + "type": "string" + }, + "osmo_ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "osmo_outpost": { + "type": "string" + }, + "osmo_to_neutron_channel_id": { + "type": "string" + }, + "party_1_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + }, + "party_1_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "party_2_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + }, + "party_2_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "pool_id": { + "$ref": "#/definitions/Uint64" + }, + "single_side_lp_limits": { + "$ref": "#/definitions/SingleSideLpLimits" + } + }, + "additionalProperties": false + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", + "type": "object", + "required": [ + "stable" + ], + "properties": { + "stable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "PartyChainInfo": { + "type": "object", + "required": [ + "ibc_timeout", + "neutron_to_party_chain_channel", + "party_chain_to_neutron_channel" + ], + "properties": { + "ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "inwards_pfm": { + "description": "pfm configuration used to route funds from osmosis to local chain via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "neutron_to_party_chain_channel": { + "description": "channel id to route funds from local chain to party chain", + "type": "string" + }, + "outwards_pfm": { + "description": "pfm configuration used to route funds from local chain to osmosis via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "party_chain_to_neutron_channel": { + "description": "channel id to route funds from party chain to local chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PartyDenomInfo": { + "type": "object", + "required": [ + "local_denom", + "osmosis_coin" + ], + "properties": { + "local_denom": { + "description": "ibc denom on liquid pooler chain", + "type": "string" + }, + "osmosis_coin": { + "description": "coin as denominated on osmosis", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "PoolPriceConfig": { + "description": "config for the pool price expectations upon covenant instantiation", + "type": "object", + "required": [ + "acceptable_price_spread", + "expected_spot_price" + ], + "properties": { + "acceptable_price_spread": { + "$ref": "#/definitions/Decimal" + }, + "expected_spot_price": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "RemoteChainSplitterConfig": { + "type": "object", + "required": [ + "amount", + "channel_id", + "connection_id", + "denom", + "ls_share", + "native_share" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "channel_id": { + "type": "string" + }, + "connection_id": { + "type": "string" + }, + "denom": { + "type": "string" + }, + "fallback_address": { + "type": [ + "string", + "null" + ] + }, + "ls_share": { + "$ref": "#/definitions/Decimal" + }, + "native_share": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "SingleSideLpLimits": { + "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", + "type": "object", + "required": [ + "asset_a_limit", + "asset_b_limit" + ], + "properties": { + "asset_a_limit": { + "$ref": "#/definitions/Uint128" + }, + "asset_b_limit": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Timeouts": { + "type": "object", + "required": [ + "ibc_transfer_timeout", + "ica_timeout" + ], + "properties": { + "ibc_transfer_timeout": { + "description": "ibc transfer timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "ica_timeout": { + "description": "ica timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": null, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "holder_address" + ], + "properties": { + "holder_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc_forwarder_address" + ], + "properties": { + "ibc_forwarder_address": { + "type": "object", + "required": [ + "ty" + ], + "properties": { + "ty": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "liquid_pooler_address" + ], + "properties": { + "liquid_pooler_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "liquid_staker_address" + ], + "properties": { + "liquid_staker_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "splitter_address" + ], + "properties": { + "splitter_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "party_deposit_address" + ], + "properties": { + "party_deposit_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "interchain_router_address" + ], + "properties": { + "interchain_router_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "contract_codes" + ], + "properties": { + "contract_codes": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "migrate_contracts" + ], + "properties": { + "migrate_contracts": { + "type": "object", + "properties": { + "clock": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg" + }, + { + "type": "null" + } + ] + }, + "codes": { + "anyOf": [ + { + "$ref": "#/definitions/CovenantContractCodeIds" + }, + { + "type": "null" + } + ] + }, + "holder": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg2" + }, + { + "type": "null" + } + ] + }, + "liquid_pooler": { + "anyOf": [ + { + "$ref": "#/definitions/LiquidPoolerMigrateMsg" + }, + { + "type": "null" + } + ] + }, + "liquid_staker": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg7" + }, + { + "type": "null" + } + ] + }, + "lp_forwarder": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg3" + }, + { + "type": "null" + } + ] + }, + "ls_forwarder": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg3" + }, + { + "type": "null" + } + ] + }, + "router": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg8" + }, + { + "type": "null" + } + ] + }, + "splitter": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg4" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AssetData": { + "description": "holds the both asset denoms relevant for providing liquidity", + "type": "object", + "required": [ + "asset_a_denom", + "asset_b_denom" + ], + "properties": { + "asset_a_denom": { + "type": "string" + }, + "asset_b_denom": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CovenantContractCodeIds": { + "type": "object", + "required": [ + "clock_code", + "holder_code", + "ibc_forwarder_code", + "interchain_router_code", + "liquid_pooler_code", + "liquid_staker_code", + "remote_chain_splitter_code" + ], + "properties": { + "clock_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "holder_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "ibc_forwarder_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interchain_router_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "liquid_pooler_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "liquid_staker_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "remote_chain_splitter_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "DecimalRange": { + "type": "object", + "required": [ + "max", + "min" + ], + "properties": { + "max": { + "$ref": "#/definitions/Decimal" + }, + "min": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "DestinationConfig": { + "type": "object", + "required": [ + "denom_to_pfm_map", + "destination_receiver_addr", + "ibc_transfer_timeout", + "local_to_destination_chain_channel_id" + ], + "properties": { + "denom_to_pfm_map": { + "description": "pfm configurations for denoms", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "destination_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "local_to_destination_chain_channel_id": { + "description": "channel id of the destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "FallbackAddressUpdateConfig": { + "oneOf": [ + { + "type": "object", + "required": [ + "explicit_address" + ], + "properties": { + "explicit_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "disable" + ], + "properties": { + "disable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "FallbackAddressUpdateConfig2": { + "oneOf": [ + { + "type": "object", + "required": [ + "explicit_address" + ], + "properties": { + "explicit_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "disable" + ], + "properties": { + "disable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "ForwardMetadata": { + "type": "object", + "required": [ + "channel", + "port", + "receiver" + ], + "properties": { + "channel": { + "type": "string" + }, + "port": { + "type": "string" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + }, + "IbcConfig": { + "type": "object", + "required": [ + "osmo_ibc_timeout", + "osmo_to_neutron_channel_id", + "party_1_chain_info", + "party_2_chain_info" + ], + "properties": { + "osmo_ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "osmo_to_neutron_channel_id": { + "type": "string" + }, + "party_1_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + }, + "party_2_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + } + }, + "additionalProperties": false + }, + "LiquidPoolerMigrateMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "osmosis" + ], + "properties": { + "osmosis": { + "$ref": "#/definitions/MigrateMsg5" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "astroport" + ], + "properties": { + "astroport": { + "$ref": "#/definitions/MigrateMsg6" + } + }, + "additionalProperties": false + } + ] + }, + "LiquidityProvisionConfig": { + "type": "object", + "required": [ + "funding_duration", + "latest_balances", + "lp_token_denom", + "outpost", + "party_1_denom_info", + "party_2_denom_info", + "pool_id", + "pool_price_config", + "single_side_lp_limits" + ], + "properties": { + "funding_duration": { + "$ref": "#/definitions/Duration" + }, + "latest_balances": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Coin" + } + }, + "lp_token_denom": { + "type": "string" + }, + "outpost": { + "type": "string" + }, + "party_1_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "party_2_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "pool_id": { + "$ref": "#/definitions/Uint64" + }, + "pool_price_config": { + "$ref": "#/definitions/PoolPriceConfig" + }, + "single_side_lp_limits": { + "$ref": "#/definitions/SingleSideLpLimits" + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "LpConfig": { + "type": "object", + "required": [ + "asset_data", + "expected_pool_ratio_range", + "pair_type", + "pool_address", + "single_side_lp_limits" + ], + "properties": { + "asset_data": { + "description": "denoms of both parties", + "allOf": [ + { + "$ref": "#/definitions/AssetData" + } + ] + }, + "expected_pool_ratio_range": { + "description": "expected price range", + "allOf": [ + { + "$ref": "#/definitions/DecimalRange" + } + ] + }, + "pair_type": { + "description": "pair type specified in the covenant", + "allOf": [ + { + "$ref": "#/definitions/PairType" + } + ] + }, + "pool_address": { + "description": "address of the liquidity pool we plan to enter", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "single_side_lp_limits": { + "description": "amounts of both tokens we consider ok to single-side lp", + "allOf": [ + { + "$ref": "#/definitions/SingleSideLpLimits" + } + ] + }, + "slippage_tolerance": { + "description": "slippage tolerance parameter for liquidity provisioning", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MigrateMsg": { + "oneOf": [ + { + "description": "Pauses the clock. No `ExecuteMsg` messages will be executable until the clock is unpaused. Callable only if the clock is unpaused.", + "type": "object", + "required": [ + "pause" + ], + "properties": { + "pause": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Unpauses the clock. Callable only if the clock is paused.", + "type": "object", + "required": [ + "unpause" + ], + "properties": { + "unpause": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Updates the max gas allowed to be consumed by a tick. This should be no larger than 100_000 less the block max gas so as to save enough gas to process the tick's error.", + "type": "object", + "required": [ + "update_tick_max_gas" + ], + "properties": { + "update_tick_max_gas": { + "type": "object", + "required": [ + "new_value" + ], + "properties": { + "new_value": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "manage_whitelist" + ], + "properties": { + "manage_whitelist": { + "type": "object", + "properties": { + "add": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "remove": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg2": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "emergency_committee": { + "type": [ + "string", + "null" + ] + }, + "lockup_period": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "pooler_address": { + "type": [ + "string", + "null" + ] + }, + "withdraw_to": { + "type": [ + "string", + "null" + ] + }, + "withdrawer": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg3": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "fallback_address": { + "anyOf": [ + { + "$ref": "#/definitions/FallbackAddressUpdateConfig" + }, + { + "type": "null" + } + ] + }, + "next_contract": { + "type": [ + "string", + "null" + ] + }, + "remote_chain_info": { + "anyOf": [ + { + "$ref": "#/definitions/RemoteChainInfo" + }, + { + "type": "null" + } + ] + }, + "transfer_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg4": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "fallback_address": { + "anyOf": [ + { + "$ref": "#/definitions/FallbackAddressUpdateConfig2" + }, + { + "type": "null" + } + ] + }, + "remote_chain_info": { + "anyOf": [ + { + "$ref": "#/definitions/RemoteChainInfo" + }, + { + "type": "null" + } + ] + }, + "splits": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg5": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "holder_address": { + "type": [ + "string", + "null" + ] + }, + "ibc_config": { + "anyOf": [ + { + "$ref": "#/definitions/IbcConfig" + }, + { + "type": "null" + } + ] + }, + "lp_config": { + "anyOf": [ + { + "$ref": "#/definitions/LiquidityProvisionConfig" + }, + { + "type": "null" + } + ] + }, + "note_address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg6": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "holder_address": { + "type": [ + "string", + "null" + ] + }, + "lp_config": { + "anyOf": [ + { + "$ref": "#/definitions/LpConfig" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg7": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "next_contract": { + "type": [ + "string", + "null" + ] + }, + "remote_chain_info": { + "anyOf": [ + { + "$ref": "#/definitions/RemoteChainInfo" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg8": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "destination_config": { + "anyOf": [ + { + "$ref": "#/definitions/DestinationConfig" + }, + { + "type": "null" + } + ] + }, + "target_denoms": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", + "type": "object", + "required": [ + "stable" + ], + "properties": { + "stable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "PartyChainInfo": { + "type": "object", + "required": [ + "ibc_timeout", + "neutron_to_party_chain_channel", + "party_chain_to_neutron_channel" + ], + "properties": { + "ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "inwards_pfm": { + "description": "pfm configuration used to route funds from osmosis to local chain via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "neutron_to_party_chain_channel": { + "description": "channel id to route funds from local chain to party chain", + "type": "string" + }, + "outwards_pfm": { + "description": "pfm configuration used to route funds from local chain to osmosis via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "party_chain_to_neutron_channel": { + "description": "channel id to route funds from party chain to local chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PartyDenomInfo": { + "type": "object", + "required": [ + "local_denom", + "osmosis_coin" + ], + "properties": { + "local_denom": { + "description": "ibc denom on liquid pooler chain", + "type": "string" + }, + "osmosis_coin": { + "description": "coin as denominated on osmosis", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "PoolPriceConfig": { + "description": "config for the pool price expectations upon covenant instantiation", + "type": "object", + "required": [ + "acceptable_price_spread", + "expected_spot_price" + ], + "properties": { + "acceptable_price_spread": { + "$ref": "#/definitions/Decimal" + }, + "expected_spot_price": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "RemoteChainInfo": { + "type": "object", + "required": [ + "channel_id", + "connection_id", + "denom", + "ibc_transfer_timeout", + "ica_timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "connection_id": { + "description": "connection id from neutron to the remote chain on which we wish to open an ICA", + "type": "string" + }, + "denom": { + "type": "string" + }, + "ibc_transfer_timeout": { + "$ref": "#/definitions/Uint64" + }, + "ica_timeout": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + }, + "SingleSideLpLimits": { + "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", + "type": "object", + "required": [ + "asset_a_limit", + "asset_b_limit" + ], + "properties": { + "asset_a_limit": { + "$ref": "#/definitions/Uint128" + }, + "asset_b_limit": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "sudo": null, + "responses": { + "clock_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "contract_codes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CovenantContractCodeIds", + "type": "object", + "required": [ + "clock_code", + "holder_code", + "ibc_forwarder_code", + "interchain_router_code", + "liquid_pooler_code", + "liquid_staker_code", + "remote_chain_splitter_code" + ], + "properties": { + "clock_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "holder_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "ibc_forwarder_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interchain_router_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "liquid_pooler_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "liquid_staker_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "remote_chain_splitter_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "holder_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ibc_forwarder_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "interchain_router_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "liquid_pooler_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "liquid_staker_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "party_deposit_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "splitter_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/single-party-pol-covenant/src/bin/schema.rs b/contracts/single-party-pol-covenant/src/bin/schema.rs new file mode 100644 index 00000000..387b41a6 --- /dev/null +++ b/contracts/single-party-pol-covenant/src/bin/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use valence_covenant_single_party_pol::msg::{InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/single-party-pol-covenant/src/contract.rs b/contracts/single-party-pol-covenant/src/contract.rs new file mode 100644 index 00000000..7107a340 --- /dev/null +++ b/contracts/single-party-pol-covenant/src/contract.rs @@ -0,0 +1,448 @@ +use std::collections::{BTreeMap, BTreeSet}; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, Addr, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + WasmMsg, +}; +use covenant_utils::split::SplitConfig; +use covenant_utils::{instantiate2_helper::get_instantiate2_salt_and_address, DestinationConfig}; +use cw2::set_contract_version; +use valence_ibc_forwarder::msg::InstantiateMsg as IbcForwarderInstantiateMsg; +use valence_interchain_router::msg::InstantiateMsg as RouterInstantiateMsg; +use valence_remote_chain_splitter::msg::InstantiateMsg as SplitterInstantiateMsg; +use valence_single_party_pol_holder::msg::InstantiateMsg as HolderInstantiateMsg; +use valence_stride_liquid_staker::msg::InstantiateMsg as LiquidStakerInstantiateMsg; + +use crate::msg::LiquidPoolerMigrateMsg; +use crate::{ + error::ContractError, + msg::{CovenantPartyConfig, InstantiateMsg, MigrateMsg, QueryMsg}, + state::{ + CONTRACT_CODES, COVENANT_CLOCK_ADDR, HOLDER_ADDR, LIQUID_POOLER_ADDR, LIQUID_STAKER_ADDR, + LP_FORWARDER_ADDR, LS_FORWARDER_ADDR, ROUTER_ADDR, SPLITTER_ADDR, + }, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub(crate) const CLOCK_SALT: &[u8] = b"clock"; +pub(crate) const HOLDER_SALT: &[u8] = b"pol_holder"; +pub(crate) const REMOTE_CHAIN_SPLITTER_SALT: &[u8] = b"remote_chain_splitter"; +pub(crate) const LS_FORWARDER_SALT: &[u8] = b"ls_forwarder"; +pub(crate) const LP_FORWARDER_SALT: &[u8] = b"lp_forwarder"; +pub(crate) const LIQUID_POOLER_SALT: &[u8] = b"liquid_pooler"; +pub(crate) const LIQUID_STAKER_SALT: &[u8] = b"liquid_staker"; +pub(crate) const ROUTER_SALT: &[u8] = b"router"; + +#[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)?; + + let creator_address = deps.api.addr_canonicalize(env.contract.address.as_str())?; + let clock_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + CLOCK_SALT, + &creator_address, + msg.contract_codes.clock_code, + )?; + let splitter_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + REMOTE_CHAIN_SPLITTER_SALT, + &creator_address, + msg.contract_codes.remote_chain_splitter_code, + )?; + let ls_forwarder_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + LS_FORWARDER_SALT, + &creator_address, + msg.contract_codes.ibc_forwarder_code, + )?; + let lp_forwarder_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + LP_FORWARDER_SALT, + &creator_address, + msg.contract_codes.ibc_forwarder_code, + )?; + let liquid_staker_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + LIQUID_STAKER_SALT, + &creator_address, + msg.contract_codes.liquid_staker_code, + )?; + let liquid_pooler_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + LIQUID_POOLER_SALT, + &creator_address, + msg.contract_codes.liquid_pooler_code, + )?; + let holder_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + HOLDER_SALT, + &creator_address, + msg.contract_codes.holder_code, + )?; + let router_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + ROUTER_SALT, + &creator_address, + msg.contract_codes.interchain_router_code, + )?; + + let mut clock_whitelist = Vec::with_capacity(7); + clock_whitelist.push(splitter_instantiate2_config.addr.to_string()); + clock_whitelist.push(liquid_pooler_instantiate2_config.addr.to_string()); + clock_whitelist.push(liquid_staker_instantiate2_config.addr.to_string()); + clock_whitelist.push(holder_instantiate2_config.addr.to_string()); + clock_whitelist.push(router_instantiate2_config.addr.to_string()); + + let mut denoms: BTreeSet = BTreeSet::new(); + denoms.insert(msg.ls_info.ls_denom_on_neutron.to_string()); + denoms.insert(msg.covenant_party_config.native_denom.to_string()); + + let router_instantiate2_msg = RouterInstantiateMsg { + clock_address: clock_instantiate2_config.addr.to_string(), + destination_config: DestinationConfig { + local_to_destination_chain_channel_id: msg + .covenant_party_config + .host_to_party_chain_channel_id + .to_string(), + destination_receiver_addr: msg.covenant_party_config.party_receiver_addr.to_string(), + ibc_transfer_timeout: msg.covenant_party_config.ibc_transfer_timeout, + denom_to_pfm_map: msg.covenant_party_config.denom_to_pfm_map, + }, + denoms, + } + .to_instantiate2_msg( + &router_instantiate2_config, + env.contract.address.to_string(), + format!("{}_interchain_router", msg.label), + )?; + + let holder_instantiate2_msg = HolderInstantiateMsg { + withdrawer: msg.covenant_party_config.addr.to_string(), + withdraw_to: router_instantiate2_config.addr.to_string(), + emergency_committee_addr: msg.emergency_committee.clone(), + lockup_period: msg.lockup_period, + pooler_address: liquid_pooler_instantiate2_config.addr.to_string(), + } + .to_instantiate2_msg( + &holder_instantiate2_config, + env.contract.address.to_string(), + format!("{}_holder", msg.label), + )?; + + let liquid_staker_instantiate2_msg = LiquidStakerInstantiateMsg { + ls_denom: msg.ls_info.ls_denom.to_string(), + stride_neutron_ibc_transfer_channel_id: msg + .ls_info + .ls_chain_to_neutron_channel_id + .to_string(), + neutron_stride_ibc_connection_id: msg.ls_info.ls_neutron_connection_id.to_string(), + ica_timeout: msg.timeouts.ica_timeout, + ibc_transfer_timeout: msg.timeouts.ibc_transfer_timeout, + clock_address: clock_instantiate2_config.addr.to_string(), + next_contract: liquid_pooler_instantiate2_config.addr.to_string(), + } + .to_instantiate2_msg( + &liquid_staker_instantiate2_config, + env.contract.address.to_string(), + format!("{}_liquid_staker", msg.label), + )?; + + let liquid_pooler_instantiate2_msg = msg.liquid_pooler_config.to_instantiate2_msg( + &liquid_pooler_instantiate2_config, + env.contract.address.to_string(), + format!("{}_liquid_pooler", msg.label), + clock_instantiate2_config.addr.to_string(), + holder_instantiate2_config.addr.to_string(), + msg.pool_price_config, + )?; + + let mut split_config_map: BTreeMap = BTreeMap::new(); + split_config_map.insert( + ls_forwarder_instantiate2_config.addr.to_string(), + msg.remote_chain_splitter_config.ls_share, + ); + split_config_map.insert( + lp_forwarder_instantiate2_config.addr.to_string(), + msg.remote_chain_splitter_config.native_share, + ); + + let mut splits: BTreeMap = BTreeMap::new(); + splits.insert( + msg.remote_chain_splitter_config.denom.to_string(), + SplitConfig { + receivers: split_config_map, + }, + ); + + let splitter_instantiate2_msg = SplitterInstantiateMsg { + clock_address: clock_instantiate2_config.addr.to_string(), + remote_chain_channel_id: msg.remote_chain_splitter_config.channel_id, + remote_chain_connection_id: msg.remote_chain_splitter_config.connection_id, + denom: msg.remote_chain_splitter_config.denom.to_string(), + amount: msg.remote_chain_splitter_config.amount, + ica_timeout: msg.timeouts.ica_timeout, + ibc_transfer_timeout: msg.timeouts.ibc_transfer_timeout, + splits, + fallback_address: msg.remote_chain_splitter_config.fallback_address, + } + .to_instantiate2_msg( + &splitter_instantiate2_config, + env.contract.address.to_string(), + format!("{}_remote_chain_splitter", msg.label), + )?; + + let mut messages = vec![ + liquid_staker_instantiate2_msg, + holder_instantiate2_msg, + liquid_pooler_instantiate2_msg, + splitter_instantiate2_msg, + router_instantiate2_msg, + ]; + + if let CovenantPartyConfig::Interchain(config) = msg.ls_forwarder_config { + LS_FORWARDER_ADDR.save(deps.storage, &ls_forwarder_instantiate2_config.addr)?; + clock_whitelist.insert(0, ls_forwarder_instantiate2_config.addr.to_string()); + let instantiate_msg = IbcForwarderInstantiateMsg { + clock_address: clock_instantiate2_config.addr.to_string(), + next_contract: liquid_staker_instantiate2_config.addr.to_string(), + remote_chain_connection_id: config.party_chain_connection_id, + remote_chain_channel_id: config.party_to_host_chain_channel_id, + denom: config.remote_chain_denom, + amount: config.contribution.amount, + ibc_transfer_timeout: msg.timeouts.ibc_transfer_timeout, + ica_timeout: msg.timeouts.ica_timeout, + fallback_address: config.fallback_address, + }; + messages.push(instantiate_msg.to_instantiate2_msg( + &ls_forwarder_instantiate2_config, + env.contract.address.to_string(), + format!("{}_ls_ibc_forwarder", msg.label), + )?); + } + + if let CovenantPartyConfig::Interchain(config) = msg.lp_forwarder_config { + LP_FORWARDER_ADDR.save(deps.storage, &lp_forwarder_instantiate2_config.addr)?; + clock_whitelist.insert(0, lp_forwarder_instantiate2_config.addr.to_string()); + let instantiate_msg = IbcForwarderInstantiateMsg { + clock_address: clock_instantiate2_config.addr.to_string(), + next_contract: liquid_pooler_instantiate2_config.addr.to_string(), + remote_chain_connection_id: config.party_chain_connection_id, + remote_chain_channel_id: config.party_to_host_chain_channel_id, + denom: config.remote_chain_denom, + amount: config.contribution.amount, + ibc_transfer_timeout: msg.timeouts.ibc_transfer_timeout, + ica_timeout: msg.timeouts.ica_timeout, + fallback_address: config.fallback_address, + }; + messages.push(instantiate_msg.to_instantiate2_msg( + &lp_forwarder_instantiate2_config, + env.contract.address.to_string(), + format!("{}_lp_ibc_forwarder", msg.label), + )?); + }; + + let clock_instantiate2_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: msg.clock_tick_max_gas, + whitelist: clock_whitelist, + } + .to_instantiate2_msg( + clock_instantiate2_config.code, + clock_instantiate2_config.salt, + env.contract.address.to_string(), + format!("{}-clock", msg.label), + )?; + messages.insert(0, clock_instantiate2_msg); + + HOLDER_ADDR.save(deps.storage, &holder_instantiate2_config.addr)?; + LIQUID_POOLER_ADDR.save(deps.storage, &liquid_pooler_instantiate2_config.addr)?; + LIQUID_STAKER_ADDR.save(deps.storage, &liquid_staker_instantiate2_config.addr)?; + COVENANT_CLOCK_ADDR.save(deps.storage, &clock_instantiate2_config.addr)?; + SPLITTER_ADDR.save(deps.storage, &splitter_instantiate2_config.addr)?; + ROUTER_ADDR.save(deps.storage, &router_instantiate2_config.addr)?; + LS_FORWARDER_ADDR.save(deps.storage, &ls_forwarder_instantiate2_config.addr)?; + LP_FORWARDER_ADDR.save(deps.storage, &lp_forwarder_instantiate2_config.addr)?; + CONTRACT_CODES.save(deps.storage, &msg.contract_codes)?; + + Ok(Response::default() + .add_attribute("method", "instantiate") + .add_attribute("clock_addr", clock_instantiate2_config.addr) + .add_attribute("ls_forwarder_addr", ls_forwarder_instantiate2_config.addr) + .add_attribute("lp_forwarder_addr", lp_forwarder_instantiate2_config.addr) + .add_attribute("holder_addr", holder_instantiate2_config.addr) + .add_attribute("splitter_addr", splitter_instantiate2_config.addr) + .add_attribute("liquid_staker_addr", liquid_staker_instantiate2_config.addr) + .add_attribute("liquid_pooler_addr", liquid_pooler_instantiate2_config.addr) + .add_attribute("router_addr", router_instantiate2_config.addr) + .add_messages(messages)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ClockAddress {} => Ok(to_json_binary( + &COVENANT_CLOCK_ADDR.may_load(deps.storage)?, + )?), + QueryMsg::HolderAddress {} => Ok(to_json_binary(&HOLDER_ADDR.may_load(deps.storage)?)?), + QueryMsg::IbcForwarderAddress { ty } => { + let resp = if ty == "lp" { + LP_FORWARDER_ADDR.may_load(deps.storage)? + } else if ty == "ls" { + LS_FORWARDER_ADDR.may_load(deps.storage)? + } else { + return Err(cosmwasm_std::StdError::not_found( + "unknown type".to_string(), + )); + }; + Ok(to_json_binary(&resp)?) + } + QueryMsg::LiquidStakerAddress {} => { + Ok(to_json_binary(&LIQUID_STAKER_ADDR.may_load(deps.storage)?)?) + } + QueryMsg::LiquidPoolerAddress {} => { + Ok(to_json_binary(&LIQUID_POOLER_ADDR.may_load(deps.storage)?)?) + } + QueryMsg::InterchainRouterAddress {} => { + Ok(to_json_binary(&ROUTER_ADDR.may_load(deps.storage)?)?) + } + QueryMsg::SplitterAddress {} => Ok(to_json_binary(&SPLITTER_ADDR.load(deps.storage)?)?), + QueryMsg::PartyDepositAddress {} => { + let splitter_address = SPLITTER_ADDR.load(deps.storage)?; + let ica: Option = deps.querier.query_wasm_smart( + splitter_address, + &covenant_utils::neutron::CovenantQueryMsg::DepositAddress {}, + )?; + + Ok(to_json_binary(&ica)?) + } + QueryMsg::ContractCodes {} => Ok(to_json_binary(&CONTRACT_CODES.load(deps.storage)?)?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult { + match msg { + MigrateMsg::MigrateContracts { + codes, + clock, + ls_forwarder, + lp_forwarder, + holder, + liquid_pooler, + liquid_staker, + splitter, + router, + } => { + let mut migrate_msgs = vec![]; + let mut resp = Response::default().add_attribute("method", "migrate_contracts"); + + if let Some(new_codes) = codes { + CONTRACT_CODES.save(deps.storage, &new_codes)?; + let code_binary = to_json_binary(&new_codes)?; + resp = resp.add_attribute("contract_codes_migrate", code_binary.to_base64()); + } + + let contract_codes = CONTRACT_CODES.load(deps.storage)?; + + if let Some(clock) = clock { + let msg = to_json_binary(&clock)?; + resp = resp.add_attribute("clock_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: COVENANT_CLOCK_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.clock_code, + msg, + }); + } + + if let Some(forwarder) = ls_forwarder { + let msg: Binary = to_json_binary(&forwarder)?; + resp = resp.add_attribute("ls_forwarder_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: LS_FORWARDER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.ibc_forwarder_code, + msg, + }); + } + + if let Some(forwarder) = lp_forwarder { + let msg: Binary = to_json_binary(&forwarder)?; + resp = resp.add_attribute("lp_forwarder_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: LP_FORWARDER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.ibc_forwarder_code, + msg, + }); + } + + if let Some(liquid_pooler_migrate_msg) = liquid_pooler { + let msg: Binary = match liquid_pooler_migrate_msg { + LiquidPoolerMigrateMsg::Astroport(msg) => to_json_binary(&msg)?, + LiquidPoolerMigrateMsg::Osmosis(msg) => to_json_binary(&msg)?, + }; + resp = resp.add_attribute("liquid_pooler_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: LIQUID_POOLER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.liquid_pooler_code, + msg, + }); + } + + if let Some(liquid_staker) = liquid_staker { + let msg: Binary = to_json_binary(&liquid_staker)?; + resp = resp.add_attribute("liquid_staker_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: LIQUID_STAKER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.liquid_staker_code, + msg, + }); + } + + if let Some(splitter) = splitter { + let msg: Binary = to_json_binary(&splitter)?; + resp = resp.add_attribute("splitter_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: SPLITTER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.remote_chain_splitter_code, + msg, + }); + } + + if let Some(holder) = holder { + let msg: Binary = to_json_binary(&holder)?; + resp = resp.add_attribute("holder_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: HOLDER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.holder_code, + msg, + }); + } + + if let Some(router) = router { + let msg: Binary = to_json_binary(&router)?; + resp = resp.add_attribute("router_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: ROUTER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.interchain_router_code, + msg, + }); + } + + Ok(resp.add_messages(migrate_msgs)) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} diff --git a/contracts/single-party-pol-covenant/src/error.rs b/contracts/single-party-pol-covenant/src/error.rs new file mode 100644 index 00000000..2b7b2d21 --- /dev/null +++ b/contracts/single-party-pol-covenant/src/error.rs @@ -0,0 +1,27 @@ +use cosmwasm_std::{Instantiate2AddressError, StdError}; +use cw_utils::ParseReplyError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Unknown reply id")] + UnknownReplyId {}, + + #[error("SubMsg reply error")] + ReplyError { err: String }, + + #[error("Failed to instantiate {contract:?} contract")] + ContractInstantiationError { + contract: String, + err: ParseReplyError, + }, + + #[error("{0}")] + InstantiationError(#[from] Instantiate2AddressError), +} diff --git a/contracts/single-party-pol-covenant/src/lib.rs b/contracts/single-party-pol-covenant/src/lib.rs new file mode 100644 index 00000000..47bc573c --- /dev/null +++ b/contracts/single-party-pol-covenant/src/lib.rs @@ -0,0 +1,6 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/single-party-pol-covenant/src/msg.rs b/contracts/single-party-pol-covenant/src/msg.rs new file mode 100644 index 00000000..f55451a9 --- /dev/null +++ b/contracts/single-party-pol-covenant/src/msg.rs @@ -0,0 +1,221 @@ +use std::collections::BTreeMap; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Binary, Decimal, StdResult, Uint128, Uint64, WasmMsg}; +use covenant_utils::{ + instantiate2_helper::Instantiate2HelperConfig, CovenantParty, DestinationConfig, + InterchainCovenantParty, NativeCovenantParty, PacketForwardMiddlewareConfig, PoolPriceConfig, + ReceiverConfig, +}; +use cw_utils::Expiration; +use valence_astroport_liquid_pooler::msg::AstroportLiquidPoolerConfig; +use valence_osmo_liquid_pooler::msg::OsmosisLiquidPoolerConfig; + +pub const DEFAULT_TIMEOUT: u64 = 60 * 60 * 5; // 5 hours + +#[cw_serde] +pub struct InstantiateMsg { + pub label: String, + pub timeouts: Timeouts, + pub contract_codes: CovenantContractCodeIds, + pub clock_tick_max_gas: Option, + pub lockup_period: Expiration, + pub ls_info: LsInfo, + pub ls_forwarder_config: CovenantPartyConfig, + pub lp_forwarder_config: CovenantPartyConfig, + pub pool_price_config: PoolPriceConfig, + pub remote_chain_splitter_config: RemoteChainSplitterConfig, + pub emergency_committee: Option, + pub covenant_party_config: InterchainCovenantParty, + pub liquid_pooler_config: LiquidPoolerConfig, +} + +#[cw_serde] +pub enum LiquidPoolerConfig { + Osmosis(Box), + Astroport(AstroportLiquidPoolerConfig), +} + +impl LiquidPoolerConfig { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + clock_addr: String, + holder_addr: String, + pool_price_config: PoolPriceConfig, + ) -> StdResult { + match self { + LiquidPoolerConfig::Osmosis(config) => Ok(config + .to_instantiate_msg( + clock_addr.to_string(), + holder_addr.to_string(), + pool_price_config, + ) + .to_instantiate2_msg(instantiate2_helper, admin, label)?), + LiquidPoolerConfig::Astroport(config) => Ok(config + .to_instantiate_msg( + clock_addr.to_string(), + holder_addr.to_string(), + pool_price_config, + ) + .to_instantiate2_msg(instantiate2_helper, admin, label)?), + } + } +} + +#[cw_serde] +pub struct SinglePartyPfmUnwindingConfig { + // keys: relevant denoms IBC'd to neutron + // values: channel ids to facilitate ibc unwinding to party chain + pub party_pfm_map: BTreeMap, +} + +#[cw_serde] +pub struct RemoteChainSplitterConfig { + pub channel_id: String, + pub connection_id: String, + pub denom: String, + pub amount: Uint128, + pub ls_share: Decimal, + pub native_share: Decimal, + pub fallback_address: Option, +} + +#[cw_serde] +pub struct LsInfo { + pub ls_denom: String, + pub ls_denom_on_neutron: String, + pub ls_chain_to_neutron_channel_id: String, + pub ls_neutron_connection_id: String, +} + +impl CovenantPartyConfig { + pub fn to_receiver_config(&self) -> ReceiverConfig { + match self { + CovenantPartyConfig::Interchain(config) => ReceiverConfig::Ibc(DestinationConfig { + local_to_destination_chain_channel_id: config + .host_to_party_chain_channel_id + .to_string(), + destination_receiver_addr: config.party_receiver_addr.to_string(), + ibc_transfer_timeout: config.ibc_transfer_timeout, + denom_to_pfm_map: BTreeMap::new(), + }), + CovenantPartyConfig::Native(config) => { + ReceiverConfig::Native(config.party_receiver_addr.to_string()) + } + } + } + + pub fn get_final_receiver_address(&self) -> String { + match self { + CovenantPartyConfig::Interchain(config) => config.party_receiver_addr.to_string(), + CovenantPartyConfig::Native(config) => config.party_receiver_addr.to_string(), + } + } + + pub fn to_covenant_party(&self) -> CovenantParty { + match self { + CovenantPartyConfig::Interchain(config) => CovenantParty { + addr: config.addr.to_string(), + native_denom: config.native_denom.to_string(), + receiver_config: self.to_receiver_config(), + }, + CovenantPartyConfig::Native(config) => CovenantParty { + addr: config.addr.to_string(), + native_denom: config.native_denom.to_string(), + receiver_config: self.to_receiver_config(), + }, + } + } + + pub fn get_native_denom(&self) -> String { + match self { + CovenantPartyConfig::Interchain(config) => config.native_denom.to_string(), + CovenantPartyConfig::Native(config) => config.native_denom.to_string(), + } + } +} + +#[cw_serde] +pub enum CovenantPartyConfig { + Interchain(InterchainCovenantParty), + Native(NativeCovenantParty), +} + +#[cw_serde] +pub struct CovenantContractCodeIds { + pub ibc_forwarder_code: u64, + pub holder_code: u64, + pub clock_code: u64, + pub remote_chain_splitter_code: u64, + pub liquid_pooler_code: u64, + pub liquid_staker_code: u64, + pub interchain_router_code: u64, +} + +#[cw_serde] +pub struct Timeouts { + /// ica timeout in seconds + pub ica_timeout: Uint64, + /// ibc transfer timeout in seconds + pub ibc_transfer_timeout: Uint64, +} + +impl Default for Timeouts { + fn default() -> Self { + Self { + ica_timeout: Uint64::new(DEFAULT_TIMEOUT), + ibc_transfer_timeout: Uint64::new(DEFAULT_TIMEOUT), + } + } +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Addr)] + ClockAddress {}, + #[returns(Addr)] + HolderAddress {}, + #[returns(Addr)] + IbcForwarderAddress { ty: String }, + #[returns(Addr)] + LiquidPoolerAddress {}, + #[returns(Addr)] + LiquidStakerAddress {}, + #[returns(Addr)] + SplitterAddress {}, + #[returns(Addr)] + PartyDepositAddress {}, + #[returns(Addr)] + InterchainRouterAddress {}, + #[returns(CovenantContractCodeIds)] + ContractCodes {}, +} + +#[allow(clippy::large_enum_variant)] +#[cw_serde] +pub enum MigrateMsg { + MigrateContracts { + codes: Option, + clock: Option, + holder: Option, + ls_forwarder: Option, + lp_forwarder: Option, + splitter: Option, + liquid_pooler: Option, + liquid_staker: Option, + router: Option, + }, + UpdateCodeId { + data: Option, + }, +} + +#[cw_serde] +pub enum LiquidPoolerMigrateMsg { + Osmosis(valence_osmo_liquid_pooler::msg::MigrateMsg), + Astroport(valence_astroport_liquid_pooler::msg::MigrateMsg), +} diff --git a/contracts/single-party-pol-covenant/src/state.rs b/contracts/single-party-pol-covenant/src/state.rs new file mode 100644 index 00000000..17d8fb4c --- /dev/null +++ b/contracts/single-party-pol-covenant/src/state.rs @@ -0,0 +1,14 @@ +use crate::msg::CovenantContractCodeIds; +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +pub const COVENANT_CLOCK_ADDR: Item = Item::new("covenant_clock_addr"); +pub const HOLDER_ADDR: Item = Item::new("holder_addr"); +pub const SPLITTER_ADDR: Item = Item::new("remote_chain_splitter_addr"); +pub const LIQUID_POOLER_ADDR: Item = Item::new("liquid_pooler_addr"); +pub const LIQUID_STAKER_ADDR: Item = Item::new("liquid_staker_addr"); +pub const LS_FORWARDER_ADDR: Item = Item::new("ls_forwarder_addr"); +pub const LP_FORWARDER_ADDR: Item = Item::new("lp_forwarder_addr"); +pub const ROUTER_ADDR: Item = Item::new("router_addr"); + +pub const CONTRACT_CODES: Item = Item::new("contract_codes"); diff --git a/contracts/single-party-pol-holder/.cargo/config b/contracts/single-party-pol-holder/.cargo/config new file mode 100644 index 00000000..6fac702a --- /dev/null +++ b/contracts/single-party-pol-holder/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --target wasm32-unknown-unknown --release" +wasm-debug = "build --target wasm32-unknown-unknown" \ No newline at end of file diff --git a/contracts/single-party-pol-holder/Cargo.toml b/contracts/single-party-pol-holder/Cargo.toml new file mode 100644 index 00000000..7ad725b7 --- /dev/null +++ b/contracts/single-party-pol-holder/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "valence-single-party-pol-holder" +authors = ["udit "] +description = "A holder can hold funds in a covenant" +edition = { workspace = true } +license = { workspace = true } +# rust-version = { workspace = true } +version = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# disables #[entry_point] (i.e. instantiate/execute/query) export +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +cw-utils = { workspace = true } +covenant-macros = { workspace = true } +covenant-utils = { workspace = true } diff --git a/contracts/single-party-pol-holder/README.md b/contracts/single-party-pol-holder/README.md new file mode 100644 index 00000000..f51aeda7 --- /dev/null +++ b/contracts/single-party-pol-holder/README.md @@ -0,0 +1 @@ +A single party holder mainly exists to withdraw the funds from the liquid pooler, it holds the logic of the distribution of the funds, and who can call the withdraw function. diff --git a/contracts/single-party-pol-holder/schema/valence-single-party-pol-holder.json b/contracts/single-party-pol-holder/schema/valence-single-party-pol-holder.json new file mode 100644 index 00000000..5e16fed5 --- /dev/null +++ b/contracts/single-party-pol-holder/schema/valence-single-party-pol-holder.json @@ -0,0 +1,475 @@ +{ + "contract_name": "valence-single-party-pol-holder", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "lockup_period", + "pooler_address", + "withdraw_to", + "withdrawer" + ], + "properties": { + "emergency_committee_addr": { + "description": "The address that is allowed to do emergency pull out", + "type": [ + "string", + "null" + ] + }, + "lockup_period": { + "description": "The lockup period for the covenant", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "pooler_address": { + "description": "the neutron address of the liquid pooler", + "type": "string" + }, + "withdraw_to": { + "description": "Withdraw the funds to this address", + "type": "string" + }, + "withdrawer": { + "description": "A withdrawer is the only authorized address that can withdraw from the contract.", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "This is called by the withdrawer to start the withdraw process", + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "After LPer finished withdrawing from LP, it sends the funds to the holder and the holder distributes them based on its logic Should only be called by the LPer of the covenant", + "type": "object", + "required": [ + "distribute" + ], + "properties": { + "distribute": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "This message is sent in case we do an IBC withdraw The withdraw can fail in async way, in case that happens we want the holder to be notified on that. In case of astroport, the withdraww + distribution is atomic, so nothing to worry there But in case of osmosis, the withdraw is async, so the \"claim\" will successful happen, while the withdraw can fail, in case the withdraw fails here, we execute this message on the holder", + "type": "object", + "required": [ + "withdraw_failed" + ], + "properties": { + "withdraw_failed": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allows for the emergency committee to withdraw the funds on case of an emergency", + "type": "object", + "required": [ + "emergency_withdraw" + ], + "properties": { + "emergency_withdraw": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "withdrawer" + ], + "properties": { + "withdrawer": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_to" + ], + "properties": { + "withdraw_to": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pooler_address" + ], + "properties": { + "pooler_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "emergency_committee_addr" + ], + "properties": { + "emergency_committee_addr": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "lockup_config" + ], + "properties": { + "lockup_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "emergency_committee": { + "type": [ + "string", + "null" + ] + }, + "lockup_period": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "pooler_address": { + "type": [ + "string", + "null" + ] + }, + "withdraw_to": { + "type": [ + "string", + "null" + ] + }, + "withdrawer": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "sudo": null, + "responses": { + "emergency_committee_addr": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "lockup_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Expiration", + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "pooler_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "withdraw_to": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "withdrawer": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/single-party-pol-holder/src/bin/schema.rs b/contracts/single-party-pol-holder/src/bin/schema.rs new file mode 100644 index 00000000..3711dacd --- /dev/null +++ b/contracts/single-party-pol-holder/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use valence_single_party_pol_holder::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/single-party-pol-holder/src/contract.rs b/contracts/single-party-pol-holder/src/contract.rs new file mode 100644 index 00000000..d9999447 --- /dev/null +++ b/contracts/single-party-pol-holder/src/contract.rs @@ -0,0 +1,205 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, to_json_binary, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, +}; +use covenant_utils::withdraw_lp_helper::{generate_withdraw_msg, EMERGENCY_COMMITTEE_ADDR}; +use cw2::set_contract_version; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use crate::state::{LOCKUP_PERIOD, POOLER_ADDRESS, WITHDRAWER, WITHDRAW_STATE, WITHDRAW_TO}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let withdrawer = deps.api.addr_validate(&msg.withdrawer)?; + let withdraw_to = deps.api.addr_validate(&msg.withdraw_to)?; + let liquidity_pooler_address = deps.api.addr_validate(&msg.pooler_address)?; + + WITHDRAWER.save(deps.storage, &withdrawer)?; + WITHDRAW_TO.save(deps.storage, &withdraw_to)?; + POOLER_ADDRESS.save(deps.storage, &liquidity_pooler_address)?; + + ensure!( + !msg.lockup_period.is_expired(&_env.block), + ContractError::MustBeFutureLockupPeriod {} + ); + LOCKUP_PERIOD.save(deps.storage, &msg.lockup_period)?; + + let resp = Response::default() + .add_attribute("method", "instantiate") + .add_attribute("pool_address", liquidity_pooler_address) + .add_attribute("withdrawer", withdrawer) + .add_attribute("withdraw_to", withdraw_to); + + if let Some(addr) = msg.emergency_committee_addr { + EMERGENCY_COMMITTEE_ADDR.save(deps.storage, &deps.api.addr_validate(&addr)?)?; + Ok(resp.add_attribute("emergency_committee", addr)) + } else { + Ok(resp) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Withdrawer {} => Ok(to_json_binary(&WITHDRAWER.may_load(deps.storage)?)?), + QueryMsg::WithdrawTo {} => Ok(to_json_binary(&WITHDRAW_TO.may_load(deps.storage)?)?), + QueryMsg::PoolerAddress {} => Ok(to_json_binary(&POOLER_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::EmergencyCommitteeAddr {} => Ok(to_json_binary( + &EMERGENCY_COMMITTEE_ADDR.may_load(deps.storage)?, + )?), + QueryMsg::LockupConfig {} => Ok(to_json_binary(&LOCKUP_PERIOD.load(deps.storage)?)?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Claim {} => try_claim(deps, env, info), + ExecuteMsg::Distribute {} => try_distribute(deps, info), + ExecuteMsg::WithdrawFailed {} => try_withdraw_failed(deps, info), + ExecuteMsg::EmergencyWithdraw {} => try_emergency_withdraw(deps, info), + } +} + +fn try_claim(deps: DepsMut, env: Env, info: MessageInfo) -> Result { + if WITHDRAW_STATE.load(deps.storage).is_ok() { + return Err(ContractError::WithdrawAlreadyStarted {}); + } + + let lockup_period = LOCKUP_PERIOD.load(deps.storage)?; + ensure!( + lockup_period.is_expired(&env.block), + ContractError::LockupPeriodNotOver(lockup_period.to_string()) + ); + + let withdrawer = WITHDRAWER.load(deps.storage)?; + ensure!(info.sender == withdrawer, ContractError::Unauthorized {}); + + let pooler_address = POOLER_ADDRESS.load(deps.storage)?; + + let withdraw_msg = generate_withdraw_msg(pooler_address.to_string(), None)?; + + WITHDRAW_STATE.save(deps.storage, &true)?; + + Ok(Response::default().add_message(withdraw_msg)) +} + +fn try_emergency_withdraw(deps: DepsMut, info: MessageInfo) -> Result { + // Make sure we are not withdrawing already + if WITHDRAW_STATE.load(deps.storage).is_ok() { + return Err(ContractError::WithdrawAlreadyStarted {}); + } + + let committee_addr = EMERGENCY_COMMITTEE_ADDR.load(deps.storage)?; + ensure!( + info.sender == committee_addr, + ContractError::Unauthorized {} + ); + + let pooler_address = POOLER_ADDRESS.load(deps.storage)?; + let withdraw_msg = generate_withdraw_msg(pooler_address.to_string(), None)?; + + WITHDRAW_STATE.save(deps.storage, &true)?; + + Ok(Response::default().add_message(withdraw_msg)) +} + +fn try_distribute(deps: DepsMut, info: MessageInfo) -> Result { + let pooler_addr = POOLER_ADDRESS.load(deps.storage)?; + let withdraw_to_addr = WITHDRAW_TO.load(deps.storage)?; + + // only liquid pooler should call this method + ensure!(info.sender == pooler_addr, ContractError::Unauthorized {}); + ensure!(info.funds.len() == 2, ContractError::InvalidFunds {}); + + // clear the pending withdraw state + WITHDRAW_STATE.remove(deps.storage); + + let send_msg = BankMsg::Send { + to_address: withdraw_to_addr.to_string(), + amount: info.funds, + }; + + Ok(Response::default().add_message(send_msg)) +} + +/// We don't need to do much if the withdraw failed. +/// We just need to ensure the caller is the pooler, and remove the withdraw_state storage +fn try_withdraw_failed(deps: DepsMut, info: MessageInfo) -> Result { + let pooler_addr = POOLER_ADDRESS.load(deps.storage)?; + ensure!(info.sender == pooler_addr, ContractError::Unauthorized {}); + + WITHDRAW_STATE.remove(deps.storage); + + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { + match msg { + MigrateMsg::UpdateConfig { + withdrawer, + withdraw_to, + pooler_address, + lockup_period, + emergency_committee, + } => { + let mut response = Response::default().add_attribute("method", "update_config"); + + if let Some(addr) = withdrawer { + WITHDRAWER.save(deps.storage, &deps.api.addr_validate(&addr)?)?; + response = response.add_attribute("withdrawer", addr); + } + + if let Some(addr) = withdraw_to { + WITHDRAW_TO.save(deps.storage, &deps.api.addr_validate(&addr)?)?; + response = response.add_attribute("withdraw_to", addr); + } + + if let Some(addr) = emergency_committee { + EMERGENCY_COMMITTEE_ADDR.save(deps.storage, &deps.api.addr_validate(&addr)?)?; + response = response.add_attribute("emergency_committee", addr); + } + + if let Some(addr) = pooler_address { + POOLER_ADDRESS.save(deps.storage, &deps.api.addr_validate(&addr)?)?; + response = response.add_attribute("pool_address", addr); + } + + if let Some(expires) = lockup_period { + // validate that the new lockup period is in the future + ensure!( + !expires.is_expired(&env.block), + ContractError::MustBeFutureLockupPeriod {} + ); + LOCKUP_PERIOD.save(deps.storage, &expires)?; + response = response.add_attribute("lockup_period", expires.to_string()); + } + + Ok(response) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} diff --git a/contracts/single-party-pol-holder/src/error.rs b/contracts/single-party-pol-holder/src/error.rs new file mode 100644 index 00000000..8a995ce3 --- /dev/null +++ b/contracts/single-party-pol-holder/src/error.rs @@ -0,0 +1,26 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("A withdraw process already started")] + WithdrawAlreadyStarted {}, + + #[error("The position is still locked, unlock at: {0}")] + LockupPeriodNotOver(String), + + #[error("The lockup period is already expired")] + LockupPeriodIsExpired, + + #[error("The lockup period must be in the future")] + MustBeFutureLockupPeriod, + + #[error("We expect 2 denoms to be received from the liquidity pooler")] + InvalidFunds, +} diff --git a/contracts/single-party-pol-holder/src/lib.rs b/contracts/single-party-pol-holder/src/lib.rs new file mode 100644 index 00000000..a5abdbb0 --- /dev/null +++ b/contracts/single-party-pol-holder/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/single-party-pol-holder/src/msg.rs b/contracts/single-party-pol-holder/src/msg.rs new file mode 100644 index 00000000..ad9d8dc0 --- /dev/null +++ b/contracts/single-party-pol-holder/src/msg.rs @@ -0,0 +1,77 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{to_json_binary, Addr, Binary, StdResult, WasmMsg}; +use covenant_macros::{covenant_holder_distribute, covenant_holder_emergency_withdraw}; +use covenant_utils::instantiate2_helper::Instantiate2HelperConfig; +use cw_utils::Expiration; + +#[cw_serde] +pub struct InstantiateMsg { + /// A withdrawer is the only authorized address that can withdraw + /// from the contract. + pub withdrawer: String, + /// Withdraw the funds to this address + pub withdraw_to: String, + /// The address that is allowed to do emergency pull out + pub emergency_committee_addr: Option, + /// the neutron address of the liquid pooler + pub pooler_address: String, + /// The lockup period for the covenant + pub lockup_period: Expiration, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +#[covenant_holder_distribute] +#[covenant_holder_emergency_withdraw] +#[cw_serde] +pub enum ExecuteMsg { + /// This is called by the withdrawer to start the withdraw process + Claim {}, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + // Queries the withdrawer address + #[returns(Addr)] + Withdrawer {}, + #[returns(Addr)] + WithdrawTo {}, + // Queries the pooler address + #[returns(Addr)] + PoolerAddress {}, + #[returns(Addr)] + EmergencyCommitteeAddr {}, + #[returns(Expiration)] + LockupConfig {}, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + withdrawer: Option, + withdraw_to: Option, + emergency_committee: Option, + pooler_address: Option, + lockup_period: Option, + }, + UpdateCodeId { + data: Option, + }, +} diff --git a/contracts/single-party-pol-holder/src/state.rs b/contracts/single-party-pol-holder/src/state.rs new file mode 100644 index 00000000..edc43d34 --- /dev/null +++ b/contracts/single-party-pol-holder/src/state.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; +use cw_utils::Expiration; + +/// address authorized to withdraw liquidity and the underlying assets +pub const WITHDRAWER: Item = Item::new("withdrawer"); +/// Addr that we withdraw the liquidity to +pub const WITHDRAW_TO: Item = Item::new("withdraw_to"); +/// address of the pool we expect to withdraw assets from +pub const POOLER_ADDRESS: Item = Item::new("pool_address"); +/// The lockup period of the LP tokens +pub const LOCKUP_PERIOD: Item = Item::new("lockup_period"); +/// The state of the withdraw process +pub const WITHDRAW_STATE: Item = Item::new("withdraw_state"); diff --git a/contracts/stride-liquid-staker/.cargo/config b/contracts/stride-liquid-staker/.cargo/config new file mode 100644 index 00000000..5f6aa466 --- /dev/null +++ b/contracts/stride-liquid-staker/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" diff --git a/contracts/stride-liquid-staker/Cargo.toml b/contracts/stride-liquid-staker/Cargo.toml new file mode 100644 index 00000000..2d8cc41f --- /dev/null +++ b/contracts/stride-liquid-staker/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "valence-stride-liquid-staker" +authors = ["benskey bekauz@protonmail.com", "Art3mix "] +description = "Liquid Staker module for stride covenant" +edition = { workspace = true } +license = { workspace = true } +# rust-version = { workspace = true } +version = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# disables #[entry_point] (i.e. instantiate/execute/query) export +library = [] + +[dependencies] +covenant-macros = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +covenant-utils = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +thiserror = { workspace = true } +schemars = { workspace = true } +serde-json-wasm = { workspace = true } +serde = { workspace = true } +neutron-sdk = { workspace = true } +cosmos-sdk-proto = { workspace = true } diff --git a/contracts/stride-liquid-staker/README.md b/contracts/stride-liquid-staker/README.md new file mode 100644 index 00000000..09fc3347 --- /dev/null +++ b/contracts/stride-liquid-staker/README.md @@ -0,0 +1,4 @@ +The Ls creates an Interchain Account on a host chain that temporarily holds funds. It allows anyone to permissionlessly forward funds denominated in a preset token to a preset destination address with an IBC transfer. See `src/msg.rs` for the API. + +## Example usecase +The current intended usecase is to create a covenant controlled Interchain Account on Stride. The covenant plans to liquid stake Atom using Stride's Autopilot 1-click liquid stake feature. Stride's Autopilot feature enables IBC transfers to a receiving address on Stride to be automatically liquid staked and also for these liquid staked vouchers to optionally be forwarded over IBC to a destination address. The current use of the contract is to register the receiving address as an ICA on Stride and allow anybody to forward liquid staked Atom from that ICA to the LPer contract. The benefit here is that if Stride's Autopilot IBC forwarding is disabled or otherwise fails, any user can recover the funds by forwarding them to the LPer. diff --git a/contracts/stride-liquid-staker/json.json b/contracts/stride-liquid-staker/json.json new file mode 100644 index 00000000..683b2fbc --- /dev/null +++ b/contracts/stride-liquid-staker/json.json @@ -0,0 +1,10 @@ +{ + "autopilot": { + "receiver": "stride123", + "stakeibc": { + "action": "LiquidStake", + "ibc_receiver": "neutron123", + "transfer_channel": "STRIDE_NEUTRON_CHANNEL" + } + } +} diff --git a/contracts/stride-liquid-staker/schema/valence-stride-liquid-staker.json b/contracts/stride-liquid-staker/schema/valence-stride-liquid-staker.json new file mode 100644 index 00000000..59b600f9 --- /dev/null +++ b/contracts/stride-liquid-staker/schema/valence-stride-liquid-staker.json @@ -0,0 +1,383 @@ +{ + "contract_name": "valence-stride-liquid-staker", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "clock_address", + "ibc_transfer_timeout", + "ica_timeout", + "ls_denom", + "neutron_stride_ibc_connection_id", + "next_contract", + "stride_neutron_ibc_transfer_channel_id" + ], + "properties": { + "clock_address": { + "description": "Address for the clock. This contract verifies that only the clock can execute Ticks", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "Timeout in seconds. This is used to craft a timeout timestamp that will be attached to the IBC transfer message from the ICA on the host chain (Stride) to its destination. Typically this timeout should be greater than the ICA timeout, otherwise if the ICA times out, the destination chain receiving the funds will also receive the IBC packet with an expired timestamp.", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "ica_timeout": { + "description": "Time in seconds for ICA SubmitTX messages from Neutron Note that ICA uses ordered channels, a timeout implies channel closed. We can reopen the channel by reregistering the ICA with the same port id and connection id", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "ls_denom": { + "description": "The liquid staked denom (e.g., stuatom). This is required because we only allow transfers of this denom out of the LSer", + "type": "string" + }, + "neutron_stride_ibc_connection_id": { + "description": "IBC connection ID on Neutron for Stride We make an Interchain Account over this connection", + "type": "string" + }, + "next_contract": { + "description": "Address of the next contract to query for the deposit address", + "type": "string" + }, + "stride_neutron_ibc_transfer_channel_id": { + "description": "IBC transfer channel on Stride for Neutron This is used to IBC transfer stuatom on Stride to the LP contract", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "The transfer message allows anybody to permissionlessly transfer a specified amount of tokens of the preset ls_denom from the ICA of the host chain to the preset lp_address", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Wakes the state machine up. The caller should check the sender of the tick is the clock if they'd like to pause when the clock does.", + "type": "object", + "required": [ + "tick" + ], + "properties": { + "tick": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "contract_state" + ], + "properties": { + "contract_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "next_memo" + ], + "properties": { + "next_memo": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated clock address authorized to submit ticks", + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated remote chain information", + "type": "object", + "required": [ + "remote_chain_info" + ], + "properties": { + "remote_chain_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the address a contract expects to receive funds to", + "type": "object", + "required": [ + "deposit_address" + ], + "properties": { + "deposit_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated remote chain information", + "type": "object", + "required": [ + "ica_address" + ], + "properties": { + "ica_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "next_contract": { + "type": [ + "string", + "null" + ] + }, + "remote_chain_info": { + "anyOf": [ + { + "$ref": "#/definitions/RemoteChainInfo" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "RemoteChainInfo": { + "type": "object", + "required": [ + "channel_id", + "connection_id", + "denom", + "ibc_transfer_timeout", + "ica_timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "connection_id": { + "description": "connection id from neutron to the remote chain on which we wish to open an ICA", + "type": "string" + }, + "denom": { + "type": "string" + }, + "ibc_transfer_timeout": { + "$ref": "#/definitions/Uint64" + }, + "ica_timeout": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "sudo": null, + "responses": { + "clock_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "contract_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractState", + "type": "string", + "enum": [ + "instantiated", + "ica_created" + ] + }, + "deposit_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "ica_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "next_memo": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" + }, + "remote_chain_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RemoteChainInfo", + "type": "object", + "required": [ + "channel_id", + "connection_id", + "denom", + "ibc_transfer_timeout", + "ica_timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "connection_id": { + "description": "connection id from neutron to the remote chain on which we wish to open an ICA", + "type": "string" + }, + "denom": { + "type": "string" + }, + "ibc_transfer_timeout": { + "$ref": "#/definitions/Uint64" + }, + "ica_timeout": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false, + "definitions": { + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/contracts/stride-liquid-staker/src/bin/schema.rs b/contracts/stride-liquid-staker/src/bin/schema.rs new file mode 100644 index 00000000..0de5e9db --- /dev/null +++ b/contracts/stride-liquid-staker/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use valence_stride_liquid_staker::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/stride-liquid-staker/src/contract.rs b/contracts/stride-liquid-staker/src/contract.rs new file mode 100644 index 00000000..5c6f7ba1 --- /dev/null +++ b/contracts/stride-liquid-staker/src/contract.rs @@ -0,0 +1,362 @@ +use cosmos_sdk_proto::ibc::applications::transfer::v1::MsgTransfer; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, to_json_string, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, + StdError, StdResult, Uint128, +}; +use covenant_utils::ica::{ + get_ica, msg_with_sudo_callback, prepare_sudo_payload, query_ica_registration_fee, sudo_error, + sudo_open_ack, sudo_response, sudo_timeout, INTERCHAIN_ACCOUNT_ID, +}; +use covenant_utils::neutron::{self, get_proto_coin, RemoteChainInfo, SudoPayload}; +use cw2::set_contract_version; +use neutron_sdk::query::min_ibc_fee::MinIbcFeeResponse; +use valence_clock::helpers::{enqueue_msg, verify_clock}; + +use crate::helpers::{Autopilot, AutopilotConfig}; +use crate::msg::{ContractState, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use crate::state::{ + LiquidStakerIcaStateHelper, CLOCK_ADDRESS, CONTRACT_STATE, INTERCHAIN_ACCOUNTS, NEXT_CONTRACT, + REMOTE_CHAIN_INFO, +}; +pub const SUDO_PAYLOAD_REPLY_ID: u64 = 1u64; +use neutron_sdk::{ + bindings::{msg::NeutronMsg, query::NeutronQuery}, + interchain_txs::helpers::get_port_id, + sudo::msg::SudoMsg, + NeutronError, NeutronResult, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +type QueryDeps<'a> = Deps<'a, NeutronQuery>; +type ExecuteDeps<'a> = DepsMut<'a, NeutronQuery>; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: ExecuteDeps, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> NeutronResult> { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + // validate the addresses + let clock_addr = deps.api.addr_validate(&msg.clock_address)?; + let next_contract = deps.api.addr_validate(&msg.next_contract)?; + + CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; + NEXT_CONTRACT.save(deps.storage, &next_contract)?; + let remote_chain_info = RemoteChainInfo { + connection_id: msg.neutron_stride_ibc_connection_id, + channel_id: msg.stride_neutron_ibc_transfer_channel_id, + denom: msg.ls_denom, + ibc_transfer_timeout: msg.ibc_transfer_timeout, + ica_timeout: msg.ica_timeout, + }; + REMOTE_CHAIN_INFO.save(deps.storage, &remote_chain_info)?; + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + + Ok(Response::default() + .add_message(enqueue_msg(clock_addr.as_str())?) + .add_attribute("method", "ls_instantiate") + .add_attribute("clock_address", clock_addr) + .add_attribute("next_contract", next_contract) + .add_attributes(remote_chain_info.get_response_attributes())) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: ExecuteDeps, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> NeutronResult> { + match msg { + ExecuteMsg::Tick {} => try_tick(deps, env, info), + ExecuteMsg::Transfer { amount } => { + let ica_address = get_ica( + &LiquidStakerIcaStateHelper, + deps.storage, + env.contract.address.as_ref(), + INTERCHAIN_ACCOUNT_ID, + ); + match ica_address { + Ok(_) => try_execute_transfer(deps, env, info, amount), + Err(_) => Ok(Response::default() + .add_attribute("method", "try_permisionless_transfer") + .add_attribute("ica_status", "not_created")), + } + } + } +} + +/// attempts to advance the state machine. performs `info.sender` validation +fn try_tick(deps: ExecuteDeps, env: Env, info: MessageInfo) -> NeutronResult> { + // Verify caller is the clock + verify_clock(&info.sender, &CLOCK_ADDRESS.load(deps.storage)?)?; + + let current_state = CONTRACT_STATE.load(deps.storage)?; + match current_state { + ContractState::Instantiated => try_register_stride_ica(deps, env), + ContractState::IcaCreated => Ok(Response::default()), + } +} + +/// registers an interchain account on stride with port_id associated with `INTERCHAIN_ACCOUNT_ID` +fn try_register_stride_ica(deps: ExecuteDeps, env: Env) -> NeutronResult> { + let remote_chain_info = REMOTE_CHAIN_INFO.load(deps.storage)?; + let ica_registration_fee = query_ica_registration_fee(deps.querier)?; + let register: NeutronMsg = NeutronMsg::register_interchain_account( + remote_chain_info.connection_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + Some(ica_registration_fee), + ); + let key = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + + // we are saving empty data here because we handle response of registering ICA in sudo_open_ack method + INTERCHAIN_ACCOUNTS.save(deps.storage, key, &None)?; + + Ok(Response::new() + .add_attribute("method", "try_register_stride_ica") + .add_message(register)) +} + +/// this is a permisionless transfer method. once liquid staked funds are in this +/// contract, anyone can call this method by passing an amount (`Uint128`) to transfer +/// the funds (with `ls_denom`) to the liquid pooler module. +fn try_execute_transfer( + deps: ExecuteDeps, + env: Env, + _info: MessageInfo, + amount: Uint128, +) -> NeutronResult> { + // first we verify whether the next contract is ready for receiving the funds + let next_contract = NEXT_CONTRACT.load(deps.storage)?; + let deposit_address_query = deps + .querier + .query_wasm_smart(next_contract, &neutron::QueryMsg::DepositAddress {})?; + + // if query returns None, then we error and wait + let Some(deposit_address) = deposit_address_query else { + return Err(NeutronError::Std(StdError::not_found( + "Next contract is not ready for receiving the funds yet", + ))); + }; + + let port_id = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + let interchain_account = INTERCHAIN_ACCOUNTS.load(deps.storage, port_id.clone())?; + let min_fee_query_response: MinIbcFeeResponse = + deps.querier.query(&NeutronQuery::MinIbcFee {}.into())?; + + match interchain_account { + Some((address, controller_conn_id)) => { + let remote_chain_info = REMOTE_CHAIN_INFO.load(deps.storage)?; + + // inner MsgTransfer that will be sent from stride to neutron. + // because of this message delivery depending on the ica wrapper below, + // timeout_timestamp = current block + ica timeout + ibc_transfer_timeout + let msg = MsgTransfer { + source_port: "transfer".to_string(), + source_channel: remote_chain_info.channel_id, + token: Some(get_proto_coin(remote_chain_info.denom, amount)), + sender: address, + receiver: deposit_address, + timeout_height: None, + timeout_timestamp: env + .block + .time + .plus_seconds(remote_chain_info.ica_timeout.u64()) + .plus_seconds(remote_chain_info.ibc_transfer_timeout.u64()) + .nanos(), + }; + + let protobuf = neutron::to_proto_msg_transfer(msg)?; + + // wrap the protobuf of MsgTransfer into a message to be executed + // by our interchain account + let submit_msg = NeutronMsg::submit_tx( + controller_conn_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + vec![protobuf], + "".to_string(), + remote_chain_info.ica_timeout.u64(), + min_fee_query_response.min_fee, + ); + let state_helper = LiquidStakerIcaStateHelper; + let sudo_msg = msg_with_sudo_callback( + &state_helper, + deps, + submit_msg, + SudoPayload { + port_id, + message: "permisionless_transfer".to_string(), + }, + SUDO_PAYLOAD_REPLY_ID, + )?; + Ok(Response::default() + .add_submessage(sudo_msg) + .add_attribute("method", "try_execute_transfer")) + } + None => { + // I can't think of a case of how we could end up here as `sudo_open_ack` + // callback advances the state to `ICACreated` and stores the ICA. + // just in case, we revert the state to `Instantiated` to restart the flow. + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + Ok(Response::default() + .add_attribute("method", "try_execute_transfer") + .add_attribute("error", "no_ica_found")) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: QueryDeps, env: Env, msg: QueryMsg) -> NeutronResult { + match msg { + QueryMsg::ClockAddress {} => Ok(to_json_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::IcaAddress {} => Ok(to_json_binary( + &get_ica( + &LiquidStakerIcaStateHelper, + deps.storage, + env.contract.address.as_str(), + INTERCHAIN_ACCOUNT_ID, + )? + .0, + )?), + QueryMsg::ContractState {} => Ok(to_json_binary(&CONTRACT_STATE.may_load(deps.storage)?)?), + QueryMsg::DepositAddress {} => { + let ica = get_ica( + &LiquidStakerIcaStateHelper, + deps.storage, + env.contract.address.as_str(), + INTERCHAIN_ACCOUNT_ID, + )? + .0; + + let autopilot = Autopilot { + autopilot: AutopilotConfig { + receiver: ica.to_string(), + stakeibc: crate::helpers::Stakeibc { + action: "LiquidStake".to_string(), + stride_address: ica, + }, + }, + }; + + let autopilot_str = to_json_string(&autopilot)?; + + Ok(to_json_binary(&autopilot_str)?) + } + QueryMsg::RemoteChainInfo {} => { + Ok(to_json_binary(&REMOTE_CHAIN_INFO.may_load(deps.storage)?)?) + } + QueryMsg::NextMemo {} => { + // 1. receiver = query ICA + let ica = get_ica( + &LiquidStakerIcaStateHelper, + deps.storage, + env.contract.address.as_str(), + INTERCHAIN_ACCOUNT_ID, + )? + .0; + + let autopilot = Autopilot { + autopilot: AutopilotConfig { + receiver: ica.to_string(), + stakeibc: crate::helpers::Stakeibc { + action: "LiquidStake".to_string(), + stride_address: ica, + }, + }, + }; + + let autopilot_str = to_json_string(&autopilot)?; + + Ok(to_json_binary(&autopilot_str)?) + } + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: ExecuteDeps, env: Env, msg: SudoMsg) -> Result, StdError> { + match msg { + // For handling successful (non-error) acknowledgements. + SudoMsg::Response { request, data } => sudo_response(request, data), + + // For handling error acknowledgements. + SudoMsg::Error { request, details } => sudo_error(request, details), + + // For handling error timeouts. + SudoMsg::Timeout { request } => { + sudo_timeout(&LiquidStakerIcaStateHelper, deps, env, request) + } + + // For handling successful registering of ICA + SudoMsg::OpenAck { + port_id, + channel_id, + counterparty_channel_id, + counterparty_version, + } => sudo_open_ack( + &LiquidStakerIcaStateHelper, + deps, + env, + port_id, + channel_id, + counterparty_channel_id, + counterparty_version, + ), + _ => Ok(Response::default()), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: ExecuteDeps, env: Env, msg: Reply) -> StdResult> { + match msg.id { + SUDO_PAYLOAD_REPLY_ID => prepare_sudo_payload(&LiquidStakerIcaStateHelper, deps, env, msg), + _ => Err(StdError::generic_err(format!( + "unsupported reply message id {}", + msg.id + ))), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: ExecuteDeps, _env: Env, msg: MigrateMsg) -> StdResult> { + match msg { + MigrateMsg::UpdateConfig { + clock_addr, + next_contract, + remote_chain_info, + } => { + let mut resp = Response::default().add_attribute("method", "update_config"); + + if let Some(addr) = clock_addr { + let addr = deps.api.addr_validate(&addr)?; + CLOCK_ADDRESS.save(deps.storage, &addr)?; + resp = resp.add_attribute("clock_addr", addr.to_string()); + } + + if let Some(addr) = next_contract { + let addr = deps.api.addr_validate(&addr)?; + resp = resp.add_attribute("next_contract", addr.to_string()); + NEXT_CONTRACT.save(deps.storage, &addr)?; + } + + if let Some(rci) = remote_chain_info { + REMOTE_CHAIN_INFO.save(deps.storage, &rci)?; + resp = resp.add_attributes(rci.get_response_attributes()); + } + + Ok(resp) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} diff --git a/contracts/ls/src/error.rs b/contracts/stride-liquid-staker/src/error.rs similarity index 100% rename from contracts/ls/src/error.rs rename to contracts/stride-liquid-staker/src/error.rs diff --git a/contracts/stride-liquid-staker/src/helpers.rs b/contracts/stride-liquid-staker/src/helpers.rs new file mode 100644 index 00000000..829f792d --- /dev/null +++ b/contracts/stride-liquid-staker/src/helpers.rs @@ -0,0 +1,20 @@ +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct AutopilotConfig { + pub receiver: String, + pub stakeibc: Stakeibc, +} + +#[cw_serde] +pub struct Autopilot { + pub autopilot: AutopilotConfig, +} + +#[cw_serde] +pub struct Stakeibc { + pub action: String, + pub stride_address: String, + // pub ibc_receiver: String, + // pub transfer_channel: String, +} diff --git a/contracts/stride-liquid-staker/src/lib.rs b/contracts/stride-liquid-staker/src/lib.rs new file mode 100644 index 00000000..3da36ff7 --- /dev/null +++ b/contracts/stride-liquid-staker/src/lib.rs @@ -0,0 +1,7 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod helpers; +pub mod msg; +pub mod state; diff --git a/contracts/stride-liquid-staker/src/msg.rs b/contracts/stride-liquid-staker/src/msg.rs new file mode 100644 index 00000000..aa2fab6f --- /dev/null +++ b/contracts/stride-liquid-staker/src/msg.rs @@ -0,0 +1,97 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{to_json_binary, Addr, Binary, StdResult, Uint128, Uint64, WasmMsg}; +use covenant_macros::{ + clocked, covenant_clock_address, covenant_deposit_address, covenant_ica_address, + covenant_remote_chain, +}; +use covenant_utils::{instantiate2_helper::Instantiate2HelperConfig, neutron::RemoteChainInfo}; + +#[cw_serde] +pub struct InstantiateMsg { + /// Address for the clock. This contract verifies + /// that only the clock can execute Ticks + pub clock_address: String, + /// IBC transfer channel on Stride for Neutron + /// This is used to IBC transfer stuatom on Stride + /// to the LP contract + pub stride_neutron_ibc_transfer_channel_id: String, + /// IBC connection ID on Neutron for Stride + /// We make an Interchain Account over this connection + pub neutron_stride_ibc_connection_id: String, + /// Address of the next contract to query for the deposit address + pub next_contract: String, + /// The liquid staked denom (e.g., stuatom). This is + /// required because we only allow transfers of this denom + /// out of the LSer + pub ls_denom: String, + /// Time in seconds for ICA SubmitTX messages from Neutron + /// Note that ICA uses ordered channels, a timeout implies + /// channel closed. We can reopen the channel by reregistering + /// the ICA with the same port id and connection id + pub ica_timeout: Uint64, + /// Timeout in seconds. This is used to craft a timeout timestamp + /// that will be attached to the IBC transfer message from the ICA + /// on the host chain (Stride) to its destination. Typically + /// this timeout should be greater than the ICA timeout, otherwise + /// if the ICA times out, the destination chain receiving the funds + /// will also receive the IBC packet with an expired timestamp. + pub ibc_transfer_timeout: Uint64, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +#[clocked] +#[cw_serde] +pub enum ExecuteMsg { + /// The transfer message allows anybody to permissionlessly + /// transfer a specified amount of tokens of the preset ls_denom + /// from the ICA of the host chain to the preset lp_address + Transfer { amount: Uint128 }, +} + +#[covenant_clock_address] +#[covenant_remote_chain] +#[covenant_deposit_address] +#[covenant_ica_address] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(ContractState)] + ContractState {}, + #[returns(String)] + NextMemo {}, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + next_contract: Option, + remote_chain_info: Option, + }, + UpdateCodeId { + data: Option, + }, +} + +#[cw_serde] +pub enum ContractState { + Instantiated, + IcaCreated, +} diff --git a/contracts/stride-liquid-staker/src/state.rs b/contracts/stride-liquid-staker/src/state.rs new file mode 100644 index 00000000..2bd4a5a4 --- /dev/null +++ b/contracts/stride-liquid-staker/src/state.rs @@ -0,0 +1,83 @@ +use cosmwasm_std::{from_json, to_json_vec, Addr, Binary, StdError, StdResult, Storage}; +use covenant_utils::{ + ica::IcaStateHelper, + neutron::{RemoteChainInfo, SudoPayload}, +}; +use cw_storage_plus::{Item, Map}; + +use crate::msg::ContractState; + +/// tracks the current state of state machine +pub const CONTRACT_STATE: Item = Item::new("contract_state"); + +/// clock module address to verify the sender of incoming ticks +pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); +/// next contract address to forward the liquid staked funds to +pub const NEXT_CONTRACT: Item = Item::new("next_contract"); + +/// information needed for an ibc transfer to the remote chain +pub const REMOTE_CHAIN_INFO: Item = Item::new("r_c_info"); + +/// interchain accounts storage in form of (port_id) -> (address, controller_connection_id) +pub const INTERCHAIN_ACCOUNTS: Map> = + Map::new("interchain_accounts"); + +/// interchain transaction responses - ack/err/timeout state to query later +pub const REPLY_ID_STORAGE: Item> = Item::new("reply_queue_id"); +pub const SUDO_PAYLOAD: Map<(String, u64), Vec> = Map::new("sudo_payload"); + +pub(crate) struct LiquidStakerIcaStateHelper; + +impl IcaStateHelper for LiquidStakerIcaStateHelper { + fn reset_state(&self, storage: &mut dyn Storage) -> StdResult<()> { + CONTRACT_STATE.save(storage, &ContractState::Instantiated)?; + Ok(()) + } + + fn clear_ica(&self, storage: &mut dyn Storage) -> StdResult<()> { + INTERCHAIN_ACCOUNTS.clear(storage); + Ok(()) + } + + fn save_ica( + &self, + storage: &mut dyn Storage, + port_id: String, + address: String, + controller_connection_id: String, + ) -> StdResult<()> { + INTERCHAIN_ACCOUNTS.save(storage, port_id, &Some((address, controller_connection_id)))?; + Ok(()) + } + + fn save_state_ica_created(&self, storage: &mut dyn Storage) -> StdResult<()> { + CONTRACT_STATE.save(storage, &ContractState::IcaCreated)?; + Ok(()) + } + + fn save_reply_payload(&self, storage: &mut dyn Storage, payload: SudoPayload) -> StdResult<()> { + REPLY_ID_STORAGE.save(storage, &to_json_vec(&payload)?)?; + Ok(()) + } + + fn read_reply_payload(&self, storage: &mut dyn Storage) -> StdResult { + let data = REPLY_ID_STORAGE.load(storage)?; + from_json(Binary(data)) + } + + fn save_sudo_payload( + &self, + storage: &mut dyn Storage, + channel_id: String, + seq_id: u64, + payload: SudoPayload, + ) -> StdResult<()> { + SUDO_PAYLOAD.save(storage, (channel_id, seq_id), &to_json_vec(&payload)?) + } + + fn get_ica(&self, storage: &dyn Storage, key: String) -> StdResult<(String, String)> { + INTERCHAIN_ACCOUNTS + .load(storage, key)? + .ok_or_else(|| StdError::generic_err("Interchain account is not created yet")) + } +} diff --git a/contracts/swap-covenant/.cargo/config b/contracts/swap-covenant/.cargo/config new file mode 100644 index 00000000..5f6aa466 --- /dev/null +++ b/contracts/swap-covenant/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" diff --git a/contracts/swap-covenant/Cargo.toml b/contracts/swap-covenant/Cargo.toml new file mode 100644 index 00000000..eefb2349 --- /dev/null +++ b/contracts/swap-covenant/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "valence-covenant-swap" +edition = { workspace = true } +authors = ["benskey bekauz@protonmail.com"] +description = "Swap covenant contract" +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } + +exclude = ["contract.wasm", "hash.txt"] + + +[lib] +crate-type = ["cdylib", "rlib"] + + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +sha2 = { workspace = true } +neutron-sdk = { workspace = true } +cosmos-sdk-proto = { workspace = true } +schemars = { workspace = true } +serde-json-wasm = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +bech32 = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +valence-swap-holder = { workspace = true, features = ["library"] } +covenant-utils = { workspace = true } +valence-native-splitter = { workspace = true, features = ["library"] } +valence-ibc-forwarder = { workspace = true, features = ["library"] } +valence-interchain-router = { workspace = true, features = ["library"] } +valence-native-router = { workspace = true, features = ["library"] } diff --git a/contracts/swap-covenant/README.md b/contracts/swap-covenant/README.md new file mode 100644 index 00000000..bcf0c226 --- /dev/null +++ b/contracts/swap-covenant/README.md @@ -0,0 +1,15 @@ +# swap covenant + +Contract responsible for orchestrating flow for a tokenswap between two parties. + +## instantiation chain flow + +Because of inter-contract dependencies, contracts in the covenant are instantiated in a specific order: +1. clock +1. party A router +1. party B router +1. splitter +1. holder +1. party A forwarder +1. party B forwarder +1. (clock whitelisting) \ No newline at end of file diff --git a/contracts/swap-covenant/schema/valence-covenant-swap.json b/contracts/swap-covenant/schema/valence-covenant-swap.json new file mode 100644 index 00000000..a5df1dde --- /dev/null +++ b/contracts/swap-covenant/schema/valence-covenant-swap.json @@ -0,0 +1,1640 @@ +{ + "contract_name": "valence-covenant-swap", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "contract_codes", + "label", + "lockup_config", + "party_a_config", + "party_b_config", + "splits", + "timeouts" + ], + "properties": { + "clock_tick_max_gas": { + "anyOf": [ + { + "$ref": "#/definitions/Uint64" + }, + { + "type": "null" + } + ] + }, + "contract_codes": { + "$ref": "#/definitions/SwapCovenantContractCodeIds" + }, + "fallback_address": { + "type": [ + "string", + "null" + ] + }, + "fallback_split": { + "anyOf": [ + { + "$ref": "#/definitions/SplitConfig" + }, + { + "type": "null" + } + ] + }, + "label": { + "type": "string" + }, + "lockup_config": { + "$ref": "#/definitions/Expiration" + }, + "party_a_config": { + "$ref": "#/definitions/CovenantPartyConfig" + }, + "party_b_config": { + "$ref": "#/definitions/CovenantPartyConfig" + }, + "splits": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } + }, + "timeouts": { + "$ref": "#/definitions/Timeouts" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CovenantPartyConfig": { + "oneOf": [ + { + "type": "object", + "required": [ + "interchain" + ], + "properties": { + "interchain": { + "$ref": "#/definitions/InterchainCovenantParty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "$ref": "#/definitions/NativeCovenantParty" + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "InterchainCovenantParty": { + "type": "object", + "required": [ + "addr", + "contribution", + "denom_to_pfm_map", + "host_to_party_chain_channel_id", + "ibc_transfer_timeout", + "native_denom", + "party_chain_connection_id", + "party_receiver_addr", + "party_to_host_chain_channel_id", + "remote_chain_denom" + ], + "properties": { + "addr": { + "description": "authorized address of the party on neutron", + "type": "string" + }, + "contribution": { + "description": "coin provided by the party on its native chain", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "denom_to_pfm_map": { + "description": "configuration for unwinding the denoms via pfm", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "fallback_address": { + "description": "fallback refund address on the remote chain", + "type": [ + "string", + "null" + ] + }, + "host_to_party_chain_channel_id": { + "description": "channel id from host chain to the party chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "native_denom": { + "description": "denom provided by the party on neutron", + "type": "string" + }, + "party_chain_connection_id": { + "description": "connection id to the party chain", + "type": "string" + }, + "party_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "party_to_host_chain_channel_id": { + "description": "channel id from party to host chain", + "type": "string" + }, + "remote_chain_denom": { + "description": "denom provided by the party on its native chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "NativeCovenantParty": { + "type": "object", + "required": [ + "addr", + "contribution", + "native_denom", + "party_receiver_addr" + ], + "properties": { + "addr": { + "description": "authorized address of the party on neutron", + "type": "string" + }, + "contribution": { + "description": "coin provided by the party on its native chain", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "native_denom": { + "description": "denom provided by the party on neutron", + "type": "string" + }, + "party_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + }, + "SwapCovenantContractCodeIds": { + "type": "object", + "required": [ + "clock_code", + "holder_code", + "ibc_forwarder_code", + "interchain_router_code", + "native_router_code", + "splitter_code" + ], + "properties": { + "clock_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "holder_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "ibc_forwarder_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interchain_router_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "native_router_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "splitter_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Timeouts": { + "type": "object", + "required": [ + "ibc_transfer_timeout", + "ica_timeout" + ], + "properties": { + "ibc_transfer_timeout": { + "description": "ibc transfer timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "ica_timeout": { + "description": "ica timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "type": "string", + "enum": [] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "holder_address" + ], + "properties": { + "holder_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "splitter_address" + ], + "properties": { + "splitter_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "interchain_router_address" + ], + "properties": { + "interchain_router_address": { + "type": "object", + "required": [ + "party" + ], + "properties": { + "party": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc_forwarder_address" + ], + "properties": { + "ibc_forwarder_address": { + "type": "object", + "required": [ + "party" + ], + "properties": { + "party": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "party_deposit_address" + ], + "properties": { + "party_deposit_address": { + "type": "object", + "required": [ + "party" + ], + "properties": { + "party": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "contract_codes" + ], + "properties": { + "contract_codes": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_covenant" + ], + "properties": { + "update_covenant": { + "type": "object", + "properties": { + "clock": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg" + }, + { + "type": "null" + } + ] + }, + "codes": { + "anyOf": [ + { + "$ref": "#/definitions/CovenantContractCodes" + }, + { + "type": "null" + } + ] + }, + "holder": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg2" + }, + { + "type": "null" + } + ] + }, + "party_a_forwarder": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg6" + }, + { + "type": "null" + } + ] + }, + "party_a_router": { + "anyOf": [ + { + "$ref": "#/definitions/RouterMigrateMsg" + }, + { + "type": "null" + } + ] + }, + "party_b_forwarder": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg6" + }, + { + "type": "null" + } + ] + }, + "party_b_router": { + "anyOf": [ + { + "$ref": "#/definitions/RouterMigrateMsg" + }, + { + "type": "null" + } + ] + }, + "splitter": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg3" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "CovenantContractCodes": { + "type": "object", + "required": [ + "clock", + "holder", + "party_a_forwarder", + "party_a_router", + "party_b_forwarder", + "party_b_router", + "splitter" + ], + "properties": { + "clock": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "holder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_a_forwarder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_a_router": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_b_forwarder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_b_router": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "splitter": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "CovenantPartiesConfig": { + "type": "object", + "required": [ + "party_a", + "party_b" + ], + "properties": { + "party_a": { + "$ref": "#/definitions/CovenantParty" + }, + "party_b": { + "$ref": "#/definitions/CovenantParty" + } + }, + "additionalProperties": false + }, + "CovenantParty": { + "type": "object", + "required": [ + "addr", + "native_denom", + "receiver_config" + ], + "properties": { + "addr": { + "description": "authorized address of the party", + "type": "string" + }, + "native_denom": { + "description": "denom provided by the party", + "type": "string" + }, + "receiver_config": { + "description": "information about receiver address", + "allOf": [ + { + "$ref": "#/definitions/ReceiverConfig" + } + ] + } + }, + "additionalProperties": false + }, + "CovenantTerms": { + "oneOf": [ + { + "type": "object", + "required": [ + "token_swap" + ], + "properties": { + "token_swap": { + "$ref": "#/definitions/SwapCovenantTerms" + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "DestinationConfig": { + "type": "object", + "required": [ + "denom_to_pfm_map", + "destination_receiver_addr", + "ibc_transfer_timeout", + "local_to_destination_chain_channel_id" + ], + "properties": { + "denom_to_pfm_map": { + "description": "pfm configurations for denoms", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "destination_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "local_to_destination_chain_channel_id": { + "description": "channel id of the destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "FallbackAddressUpdateConfig": { + "oneOf": [ + { + "type": "object", + "required": [ + "explicit_address" + ], + "properties": { + "explicit_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "disable" + ], + "properties": { + "disable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg": { + "oneOf": [ + { + "description": "Pauses the clock. No `ExecuteMsg` messages will be executable until the clock is unpaused. Callable only if the clock is unpaused.", + "type": "object", + "required": [ + "pause" + ], + "properties": { + "pause": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Unpauses the clock. Callable only if the clock is paused.", + "type": "object", + "required": [ + "unpause" + ], + "properties": { + "unpause": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Updates the max gas allowed to be consumed by a tick. This should be no larger than 100_000 less the block max gas so as to save enough gas to process the tick's error.", + "type": "object", + "required": [ + "update_tick_max_gas" + ], + "properties": { + "update_tick_max_gas": { + "type": "object", + "required": [ + "new_value" + ], + "properties": { + "new_value": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "manage_whitelist" + ], + "properties": { + "manage_whitelist": { + "type": "object", + "properties": { + "add": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "remove": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg2": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "covenant_terms": { + "anyOf": [ + { + "$ref": "#/definitions/CovenantTerms" + }, + { + "type": "null" + } + ] + }, + "lockup_config": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "next_contract": { + "type": [ + "string", + "null" + ] + }, + "parites_config": { + "anyOf": [ + { + "$ref": "#/definitions/CovenantPartiesConfig" + }, + { + "type": "null" + } + ] + }, + "refund_config": { + "anyOf": [ + { + "$ref": "#/definitions/RefundConfig" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg3": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "fallback_split": { + "anyOf": [ + { + "$ref": "#/definitions/SplitConfig" + }, + { + "type": "null" + } + ] + }, + "splits": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg4": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "destination_config": { + "anyOf": [ + { + "$ref": "#/definitions/DestinationConfig" + }, + { + "type": "null" + } + ] + }, + "target_denoms": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg5": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "receiver_address": { + "type": [ + "string", + "null" + ] + }, + "target_denoms": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg6": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "fallback_address": { + "anyOf": [ + { + "$ref": "#/definitions/FallbackAddressUpdateConfig" + }, + { + "type": "null" + } + ] + }, + "next_contract": { + "type": [ + "string", + "null" + ] + }, + "remote_chain_info": { + "anyOf": [ + { + "$ref": "#/definitions/RemoteChainInfo" + }, + { + "type": "null" + } + ] + }, + "transfer_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ReceiverConfig": { + "oneOf": [ + { + "description": "party expects to receive funds on the same chain", + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "party expects to receive funds on a remote chain", + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/DestinationConfig" + } + }, + "additionalProperties": false + } + ] + }, + "RefundConfig": { + "type": "object", + "required": [ + "party_a_refund_address", + "party_b_refund_address" + ], + "properties": { + "party_a_refund_address": { + "type": "string" + }, + "party_b_refund_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "RemoteChainInfo": { + "type": "object", + "required": [ + "channel_id", + "connection_id", + "denom", + "ibc_transfer_timeout", + "ica_timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "connection_id": { + "description": "connection id from neutron to the remote chain on which we wish to open an ICA", + "type": "string" + }, + "denom": { + "type": "string" + }, + "ibc_transfer_timeout": { + "$ref": "#/definitions/Uint64" + }, + "ica_timeout": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + }, + "RouterMigrateMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "interchain" + ], + "properties": { + "interchain": { + "$ref": "#/definitions/MigrateMsg4" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "$ref": "#/definitions/MigrateMsg5" + } + }, + "additionalProperties": false + } + ] + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + }, + "SwapCovenantTerms": { + "type": "object", + "required": [ + "party_a_amount", + "party_b_amount" + ], + "properties": { + "party_a_amount": { + "$ref": "#/definitions/Uint128" + }, + "party_b_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "sudo": null, + "responses": { + "clock_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "contract_codes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CovenantContractCodes", + "type": "object", + "required": [ + "clock", + "holder", + "party_a_forwarder", + "party_a_router", + "party_b_forwarder", + "party_b_router", + "splitter" + ], + "properties": { + "clock": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "holder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_a_forwarder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_a_router": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_b_forwarder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_b_router": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "splitter": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "holder_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ibc_forwarder_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "interchain_router_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "party_deposit_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "splitter_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/swap-covenant/src/bin/schema.rs b/contracts/swap-covenant/src/bin/schema.rs new file mode 100644 index 00000000..1995c1e1 --- /dev/null +++ b/contracts/swap-covenant/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use valence_covenant_swap::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/swap-covenant/src/contract.rs b/contracts/swap-covenant/src/contract.rs new file mode 100644 index 00000000..6df7a80d --- /dev/null +++ b/contracts/swap-covenant/src/contract.rs @@ -0,0 +1,490 @@ +use std::collections::BTreeSet; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, to_json_binary, to_json_string, Binary, Deps, DepsMut, Env, MessageInfo, Response, + StdError, StdResult, WasmMsg, +}; +use covenant_utils::{ + instantiate2_helper::get_instantiate2_salt_and_address, split::remap_splits, + CovenantPartiesConfig, CovenantTerms, SwapCovenantTerms, +}; +use cw2::set_contract_version; +use valence_swap_holder::msg::RefundConfig; + +use crate::{ + error::ContractError, + msg::{CovenantPartyConfig, InstantiateMsg, MigrateMsg, QueryMsg, RouterMigrateMsg}, + state::{ + CONTRACT_CODES, COVENANT_CLOCK_ADDR, COVENANT_INTERCHAIN_SPLITTER_ADDR, + COVENANT_SWAP_HOLDER_ADDR, PARTY_A_IBC_FORWARDER_ADDR, PARTY_A_ROUTER_ADDR, + PARTY_B_IBC_FORWARDER_ADDR, PARTY_B_ROUTER_ADDR, + }, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub(crate) const CLOCK_SALT: &[u8] = b"clock"; +pub(crate) const PARTY_A_ROUTER_SALT: &[u8] = b"party_a_router"; +pub(crate) const PARTY_B_ROUTER_SALT: &[u8] = b"party_b_router"; +pub(crate) const SPLITTER_SALT: &[u8] = b"splitter"; +pub(crate) const HOLDER_SALT: &[u8] = b"holder"; +pub(crate) const PARTY_A_FORWARDER_SALT: &[u8] = b"party_a_ibc_forwarder"; +pub(crate) const PARTY_B_FORWARDER_SALT: &[u8] = b"party_b_ibc_forwarder"; + +#[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)?; + let mut resp = Response::default().add_attribute("method", "instantiate_swap_covenant"); + + let creator_address = deps.api.addr_canonicalize(env.contract.address.as_str())?; + let covenant_denoms: BTreeSet = msg.splits.keys().map(|k| k.to_string()).collect(); + + // first we generate the instantiate2 addresses for each contract + let party_a_router_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + PARTY_A_ROUTER_SALT, + &creator_address, + msg.party_a_config.get_router_code_id(&msg.contract_codes), + )?; + let party_b_router_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + PARTY_B_ROUTER_SALT, + &creator_address, + msg.party_b_config.get_router_code_id(&msg.contract_codes), + )?; + let clock_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + CLOCK_SALT, + &creator_address, + msg.contract_codes.clock_code, + )?; + let holder_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + HOLDER_SALT, + &creator_address, + msg.contract_codes.holder_code, + )?; + let splitter_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + SPLITTER_SALT, + &creator_address, + msg.contract_codes.splitter_code, + )?; + + CONTRACT_CODES.save( + deps.storage, + &msg.contract_codes.to_covenant_codes_config( + party_a_router_instantiate2_config.code, + party_b_router_instantiate2_config.code, + ), + )?; + + let mut clock_whitelist = vec![ + holder_instantiate2_config.addr.to_string(), + party_a_router_instantiate2_config.addr.to_string(), + party_b_router_instantiate2_config.addr.to_string(), + splitter_instantiate2_config.addr.to_string(), + ]; + + let party_a_router_instantiate2_msg = msg.party_a_config.get_router_instantiate2_wasm_msg( + format!("{}_party_a_router", msg.label), + env.contract.address.to_string(), + clock_instantiate2_config.addr.clone(), + covenant_denoms.clone(), + party_a_router_instantiate2_config.clone(), + )?; + let party_b_router_instantiate2_msg = msg.party_b_config.get_router_instantiate2_wasm_msg( + format!("{}_party_b_router", msg.label), + env.contract.address.to_string(), + clock_instantiate2_config.addr.clone(), + covenant_denoms.clone(), + party_b_router_instantiate2_config.clone(), + )?; + + // we validate that denoms explicitly defined in splits are the + // same denoms that parties are expected to contribute + ensure!( + msg.splits + .contains_key(&msg.party_a_config.get_native_denom()), + ContractError::DenomMisconfigurationError( + msg.party_a_config.get_native_denom(), + format!("{:?}", covenant_denoms) + ) + ); + ensure!( + msg.splits + .contains_key(&msg.party_b_config.get_native_denom()), + ContractError::DenomMisconfigurationError( + msg.party_b_config.get_native_denom(), + format!("{:?}", covenant_denoms) + ) + ); + + let splitter_instantiate2_msg = valence_native_splitter::msg::InstantiateMsg { + clock_address: clock_instantiate2_config.addr.to_string(), + splits: remap_splits( + msg.splits.clone(), + ( + msg.party_a_config.get_final_receiver_address(), + party_a_router_instantiate2_config.addr.to_string(), + ), + ( + msg.party_b_config.get_final_receiver_address(), + party_b_router_instantiate2_config.addr.to_string(), + ), + )?, + fallback_split: match msg.fallback_split.clone() { + Some(config) => Some(config.remap_receivers_to_routers( + msg.party_a_config.get_final_receiver_address(), + party_a_router_instantiate2_config.addr.to_string(), + msg.party_b_config.get_final_receiver_address(), + party_b_router_instantiate2_config.addr.to_string(), + )?), + None => None, + }, + } + .to_instantiate2_msg( + &splitter_instantiate2_config, + env.contract.address.to_string(), + format!("{}_interchain_splitter", msg.label), + )?; + + let holder_instantiate2_msg = valence_swap_holder::msg::InstantiateMsg { + lockup_config: msg.lockup_config, + parties_config: CovenantPartiesConfig { + party_a: msg.party_a_config.to_covenant_party(), + party_b: msg.party_b_config.to_covenant_party(), + }, + covenant_terms: CovenantTerms::TokenSwap(SwapCovenantTerms { + party_a_amount: msg.party_a_config.get_contribution().amount, + party_b_amount: msg.party_b_config.get_contribution().amount, + }), + clock_address: clock_instantiate2_config.addr.to_string(), + next_contract: splitter_instantiate2_config.addr.to_string(), + refund_config: RefundConfig { + party_a_refund_address: party_a_router_instantiate2_config.addr.to_string(), + party_b_refund_address: party_b_router_instantiate2_config.addr.to_string(), + }, + } + .to_instantiate2_msg( + &holder_instantiate2_config, + env.contract.address.to_string(), + format!("{}_swap_holder", msg.label), + )?; + + let mut messages = vec![ + holder_instantiate2_msg, + party_a_router_instantiate2_msg, + party_b_router_instantiate2_msg, + splitter_instantiate2_msg, + ]; + + // if party A is an interchain party, we include it in the + // covenant flow. otherwise party is native, meaning that + // its deposit address will be the holder contract. no + // extra actions are neeed for that. + if let CovenantPartyConfig::Interchain(config) = &msg.party_a_config { + let party_a_forwarder_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + PARTY_A_FORWARDER_SALT, + &creator_address, + msg.contract_codes.ibc_forwarder_code, + )?; + // store its forwarder contract address + PARTY_A_IBC_FORWARDER_ADDR + .save(deps.storage, &party_a_forwarder_instantiate2_config.addr)?; + // whitelist that address on the clock + clock_whitelist.push(party_a_forwarder_instantiate2_config.addr.to_string()); + // generate its instantiate2 message and add it to the list + // of instantiation messages + let instantiate_msg = valence_ibc_forwarder::msg::InstantiateMsg { + remote_chain_connection_id: config.party_chain_connection_id.to_string(), + remote_chain_channel_id: config.party_to_host_chain_channel_id.to_string(), + denom: config.remote_chain_denom.to_string(), + amount: msg.party_a_config.get_contribution().amount, + ica_timeout: msg.timeouts.ica_timeout, + ibc_transfer_timeout: msg.timeouts.ibc_transfer_timeout, + clock_address: clock_instantiate2_config.addr.to_string(), + next_contract: holder_instantiate2_config.addr.to_string(), + fallback_address: msg.fallback_address.clone(), + } + .to_instantiate2_msg( + &party_a_forwarder_instantiate2_config, + env.contract.address.to_string(), + format!("{}_party_a_ibc_forwarder", msg.label), + )?; + messages.push(instantiate_msg); + resp = resp.add_attribute( + "party_a_ibc_forwarder_address", + party_a_forwarder_instantiate2_config.addr.to_string(), + ); + } + + // if party B is an interchain party, we include it in the + // covenant flow. otherwise party is native, meaning that + // its deposit address will be the holder contract. no + // extra actions are neeed for that. + if let CovenantPartyConfig::Interchain(config) = &msg.party_b_config { + let party_b_forwarder_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + PARTY_B_FORWARDER_SALT, + &creator_address, + msg.contract_codes.ibc_forwarder_code, + )?; + // store its forwarder contract address + PARTY_B_IBC_FORWARDER_ADDR + .save(deps.storage, &party_b_forwarder_instantiate2_config.addr)?; + // whitelist that address on the clock + clock_whitelist.push(party_b_forwarder_instantiate2_config.addr.to_string()); + // generate its instantiate2 message and add it to the list + // of instantiation messages + let instantiate_msg = valence_ibc_forwarder::msg::InstantiateMsg { + remote_chain_connection_id: config.party_chain_connection_id.to_string(), + remote_chain_channel_id: config.party_to_host_chain_channel_id.to_string(), + denom: config.remote_chain_denom.to_string(), + amount: msg.party_b_config.get_contribution().amount, + ica_timeout: msg.timeouts.ica_timeout, + ibc_transfer_timeout: msg.timeouts.ibc_transfer_timeout, + clock_address: clock_instantiate2_config.addr.to_string(), + next_contract: holder_instantiate2_config.addr.to_string(), + fallback_address: msg.fallback_address, + } + .to_instantiate2_msg( + &party_b_forwarder_instantiate2_config, + env.contract.address.to_string(), + format!("{}_party_b_ibc_forwarder", msg.label), + )?; + messages.push(instantiate_msg); + resp = resp.add_attribute( + "party_b_ibc_forwarder_address", + party_b_forwarder_instantiate2_config.addr.to_string(), + ); + } + + // include the clock in instantiation flow + messages.insert( + 0, + valence_clock::msg::InstantiateMsg { + tick_max_gas: msg.clock_tick_max_gas, + whitelist: clock_whitelist, + } + .to_instantiate2_msg( + clock_instantiate2_config.code, + clock_instantiate2_config.salt, + env.contract.address.to_string(), + format!("{}-clock", msg.label), + )?, + ); + + // save the contract addresses + COVENANT_CLOCK_ADDR.save(deps.storage, &clock_instantiate2_config.addr)?; + PARTY_A_ROUTER_ADDR.save(deps.storage, &party_a_router_instantiate2_config.addr)?; + PARTY_B_ROUTER_ADDR.save(deps.storage, &party_b_router_instantiate2_config.addr)?; + COVENANT_INTERCHAIN_SPLITTER_ADDR.save(deps.storage, &splitter_instantiate2_config.addr)?; + COVENANT_SWAP_HOLDER_ADDR.save(deps.storage, &holder_instantiate2_config.addr)?; + + Ok(resp + .add_attribute("clock_address", clock_instantiate2_config.addr.to_string()) + .add_attribute( + "party_a_router_address", + party_a_router_instantiate2_config.addr.to_string(), + ) + .add_attribute( + "party_b_router_address", + party_b_router_instantiate2_config.addr.to_string(), + ) + .add_attribute( + "holder_address", + holder_instantiate2_config.addr.to_string(), + ) + .add_attribute( + "splitter_address", + splitter_instantiate2_config.addr.to_string(), + ) + .add_attribute("instantiation_messages", to_json_string(&messages)?) + .add_messages(messages)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ClockAddress {} => Ok(to_json_binary( + &COVENANT_CLOCK_ADDR.may_load(deps.storage)?, + )?), + QueryMsg::HolderAddress {} => Ok(to_json_binary( + &COVENANT_SWAP_HOLDER_ADDR.may_load(deps.storage)?, + )?), + QueryMsg::SplitterAddress {} => Ok(to_json_binary( + &COVENANT_INTERCHAIN_SPLITTER_ADDR.may_load(deps.storage)?, + )?), + QueryMsg::InterchainRouterAddress { party } => { + let resp = if party == "party_a" { + PARTY_A_ROUTER_ADDR.may_load(deps.storage)? + } else if party == "party_b" { + PARTY_B_ROUTER_ADDR.may_load(deps.storage)? + } else { + return Err(StdError::not_found("unknown party")); + }; + Ok(to_json_binary(&resp)?) + } + QueryMsg::IbcForwarderAddress { party } => { + let resp = if party == "party_a" { + PARTY_A_IBC_FORWARDER_ADDR.may_load(deps.storage)? + } else if party == "party_b" { + PARTY_B_IBC_FORWARDER_ADDR.may_load(deps.storage)? + } else { + return Err(StdError::not_found("unknown party")); + }; + Ok(to_json_binary(&resp)?) + } + QueryMsg::PartyDepositAddress { party } => { + // here depending on the party we query their ibc forwarder. + // if it's present, we then query it for a deposit address + // which should return the address of ICA on a remote chain. + // if no ibc forwarder is saved, we return the holder. + let resp = if party == "party_a" { + match PARTY_A_IBC_FORWARDER_ADDR.may_load(deps.storage)? { + Some(addr) => deps.querier.query_wasm_smart( + addr, + &covenant_utils::neutron::QueryMsg::DepositAddress {}, + )?, + None => COVENANT_SWAP_HOLDER_ADDR.may_load(deps.storage)?, + } + } else if party == "party_b" { + match PARTY_B_IBC_FORWARDER_ADDR.may_load(deps.storage)? { + Some(addr) => deps.querier.query_wasm_smart( + addr, + &covenant_utils::neutron::QueryMsg::DepositAddress {}, + )?, + None => COVENANT_SWAP_HOLDER_ADDR.may_load(deps.storage)?, + } + } else { + return Err(StdError::not_found("unknown party")); + }; + Ok(to_json_binary(&resp)?) + } + QueryMsg::ContractCodes {} => Ok(to_json_binary(&CONTRACT_CODES.load(deps.storage)?)?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult { + match msg { + MigrateMsg::UpdateCovenant { + codes, + clock, + holder, + splitter, + party_a_router, + party_b_router, + party_a_forwarder, + party_b_forwarder, + } => { + let mut migrate_msgs = vec![]; + let mut resp = Response::default().add_attribute("method", "migrate_contracts"); + + if let Some(new_codes) = codes { + CONTRACT_CODES.save(deps.storage, &new_codes)?; + let code_binary = to_json_binary(&new_codes)?; + resp = resp.add_attribute("contract_codes_migrate", code_binary.to_base64()); + } + + let contract_codes = CONTRACT_CODES.load(deps.storage)?; + + if let Some(clock) = clock { + let msg = to_json_binary(&clock)?; + resp = resp.add_attribute("clock_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: COVENANT_CLOCK_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.clock, + msg, + }); + } + + if let Some(router_migrate_msg) = party_a_router { + let msg: Binary = match router_migrate_msg { + RouterMigrateMsg::Interchain(msg) => to_json_binary(&msg)?, + RouterMigrateMsg::Native(msg) => to_json_binary(&msg)?, + }; + + resp = resp.add_attribute("party_a_router_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: PARTY_A_ROUTER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.party_a_router, + msg, + }); + } + + if let Some(router_migrate_msg) = party_b_router { + let msg: Binary = match router_migrate_msg { + RouterMigrateMsg::Interchain(msg) => to_json_binary(&msg)?, + RouterMigrateMsg::Native(msg) => to_json_binary(&msg)?, + }; + resp = resp.add_attribute("party_b_router_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: PARTY_B_ROUTER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.party_b_router, + msg, + }); + } + + if let Some(forwarder) = *party_a_forwarder { + let msg: Binary = to_json_binary(&forwarder)?; + resp = resp.add_attribute("party_a_forwarder_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: PARTY_A_IBC_FORWARDER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.party_a_forwarder, + msg, + }); + } + + if let Some(forwarder) = *party_b_forwarder { + let msg: Binary = to_json_binary(&forwarder)?; + resp = resp.add_attribute("party_b_forwarder_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: PARTY_B_IBC_FORWARDER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.party_b_forwarder, + msg, + }); + } + + if let Some(holder) = holder { + let msg: Binary = to_json_binary(&holder)?; + resp = resp.add_attribute("holder_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: COVENANT_SWAP_HOLDER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.holder, + msg, + }); + } + + if let Some(splitter) = splitter { + let msg = to_json_binary(&splitter)?; + resp = resp.add_attribute("splitter_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: COVENANT_INTERCHAIN_SPLITTER_ADDR + .load(deps.storage)? + .to_string(), + new_code_id: contract_codes.splitter, + msg, + }); + } + + Ok(resp.add_messages(migrate_msgs)) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} diff --git a/contracts/swap-covenant/src/error.rs b/contracts/swap-covenant/src/error.rs new file mode 100644 index 00000000..a897fa4f --- /dev/null +++ b/contracts/swap-covenant/src/error.rs @@ -0,0 +1,33 @@ +use cosmwasm_std::{Instantiate2AddressError, StdError}; +use cw_utils::ParseReplyError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Attempt to deposit zero")] + ZeroDeposit {}, + + #[error("Unknown reply id")] + UnknownReplyId {}, + + #[error("SubMsg reply error")] + ReplyError { err: String }, + + #[error("Failed to instantiate {contract:?} contract")] + ContractInstantiationError { + contract: String, + err: ParseReplyError, + }, + + #[error("{0}")] + InstantiationError(#[from] Instantiate2AddressError), + + #[error("{0} contribution missing an explicit split configuration (got {1})")] + DenomMisconfigurationError(String, String), +} diff --git a/contracts/swap-covenant/src/lib.rs b/contracts/swap-covenant/src/lib.rs new file mode 100644 index 00000000..47bc573c --- /dev/null +++ b/contracts/swap-covenant/src/lib.rs @@ -0,0 +1,6 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/swap-covenant/src/msg.rs b/contracts/swap-covenant/src/msg.rs new file mode 100644 index 00000000..02e5a744 --- /dev/null +++ b/contracts/swap-covenant/src/msg.rs @@ -0,0 +1,229 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Binary, Coin, StdResult, Uint64, WasmMsg}; +use covenant_utils::{ + instantiate2_helper::Instantiate2HelperConfig, split::SplitConfig, CovenantParty, + DestinationConfig, InterchainCovenantParty, NativeCovenantParty, ReceiverConfig, +}; +use cw_utils::Expiration; + +pub const DEFAULT_TIMEOUT: u64 = 60 * 60 * 5; // 5 hours + +#[cw_serde] +pub struct InstantiateMsg { + pub label: String, + pub timeouts: Timeouts, + pub contract_codes: SwapCovenantContractCodeIds, + pub clock_tick_max_gas: Option, + pub lockup_config: Expiration, + pub party_a_config: CovenantPartyConfig, + pub party_b_config: CovenantPartyConfig, + pub splits: BTreeMap, + pub fallback_split: Option, + pub fallback_address: Option, +} + +#[cw_serde] +pub enum CovenantPartyConfig { + Interchain(InterchainCovenantParty), + Native(NativeCovenantParty), +} + +impl CovenantPartyConfig { + pub fn to_receiver_config(&self) -> ReceiverConfig { + match self { + CovenantPartyConfig::Interchain(config) => ReceiverConfig::Ibc(DestinationConfig { + local_to_destination_chain_channel_id: config + .host_to_party_chain_channel_id + .to_string(), + destination_receiver_addr: config.party_receiver_addr.to_string(), + ibc_transfer_timeout: config.ibc_transfer_timeout, + denom_to_pfm_map: config.denom_to_pfm_map.clone(), + }), + CovenantPartyConfig::Native(config) => { + ReceiverConfig::Native(config.party_receiver_addr.to_string()) + } + } + } + + pub fn get_final_receiver_address(&self) -> String { + match self { + CovenantPartyConfig::Interchain(config) => config.party_receiver_addr.to_string(), + CovenantPartyConfig::Native(config) => config.party_receiver_addr.to_string(), + } + } + + pub fn to_covenant_party(&self) -> CovenantParty { + match self { + CovenantPartyConfig::Interchain(config) => CovenantParty { + addr: config.addr.to_string(), + native_denom: config.native_denom.to_string(), + receiver_config: self.to_receiver_config(), + }, + CovenantPartyConfig::Native(config) => CovenantParty { + addr: config.addr.to_string(), + native_denom: config.native_denom.to_string(), + receiver_config: self.to_receiver_config(), + }, + } + } + + pub fn get_router_code_id(&self, contract_codes: &SwapCovenantContractCodeIds) -> u64 { + match self { + CovenantPartyConfig::Interchain(_) => contract_codes.interchain_router_code, + CovenantPartyConfig::Native(_) => contract_codes.native_router_code, + } + } + + pub fn get_native_denom(&self) -> String { + match self { + CovenantPartyConfig::Interchain(config) => config.native_denom.to_string(), + CovenantPartyConfig::Native(config) => config.native_denom.to_string(), + } + } + + pub fn get_contribution(&self) -> Coin { + match self { + CovenantPartyConfig::Interchain(config) => config.contribution.clone(), + CovenantPartyConfig::Native(config) => config.contribution.clone(), + } + } + + pub fn get_router_instantiate2_wasm_msg( + &self, + label: String, + admin: String, + clock_addr: Addr, + covenant_denoms: BTreeSet, + instantiate2_helper: Instantiate2HelperConfig, + ) -> StdResult { + match self { + CovenantPartyConfig::Interchain(party) => { + let destination_config = DestinationConfig { + local_to_destination_chain_channel_id: party + .host_to_party_chain_channel_id + .to_string(), + destination_receiver_addr: party.party_receiver_addr.to_string(), + ibc_transfer_timeout: party.ibc_transfer_timeout, + denom_to_pfm_map: party.denom_to_pfm_map.clone(), + }; + let instantiate_msg = valence_interchain_router::msg::InstantiateMsg { + clock_address: clock_addr.to_string(), + destination_config, + denoms: covenant_denoms, + }; + Ok(instantiate_msg.to_instantiate2_msg(&instantiate2_helper, admin, label)?) + } + CovenantPartyConfig::Native(party) => { + let instantiate_msg = valence_native_router::msg::InstantiateMsg { + clock_address: clock_addr.to_string(), + receiver_address: party.party_receiver_addr.to_string(), + denoms: covenant_denoms, + }; + Ok(instantiate_msg.to_instantiate2_msg(&instantiate2_helper, admin, label)?) + } + } + } +} + +#[cw_serde] +pub struct SwapCovenantContractCodeIds { + pub ibc_forwarder_code: u64, + pub interchain_router_code: u64, + pub native_router_code: u64, + pub splitter_code: u64, + pub holder_code: u64, + pub clock_code: u64, +} + +impl SwapCovenantContractCodeIds { + pub(crate) fn to_covenant_codes_config( + &self, + party_a_router_code: u64, + party_b_router_code: u64, + ) -> CovenantContractCodes { + CovenantContractCodes { + clock: self.clock_code, + holder: self.holder_code, + splitter: self.splitter_code, + party_a_router: party_a_router_code, + party_b_router: party_b_router_code, + party_a_forwarder: self.ibc_forwarder_code, + party_b_forwarder: self.ibc_forwarder_code, + } + } +} + +#[cw_serde] +pub struct Timeouts { + /// ica timeout in seconds + pub ica_timeout: Uint64, + /// ibc transfer timeout in seconds + pub ibc_transfer_timeout: Uint64, +} + +impl Default for Timeouts { + fn default() -> Self { + Self { + ica_timeout: Uint64::new(DEFAULT_TIMEOUT), + ibc_transfer_timeout: Uint64::new(DEFAULT_TIMEOUT), + } + } +} + +#[cw_serde] +pub enum ExecuteMsg {} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Addr)] + ClockAddress {}, + #[returns(Addr)] + HolderAddress {}, + #[returns(Addr)] + SplitterAddress {}, + #[returns(Addr)] + InterchainRouterAddress { party: String }, + #[returns(Addr)] + IbcForwarderAddress { party: String }, + #[returns(Addr)] + PartyDepositAddress { party: String }, + #[returns(CovenantContractCodes)] + ContractCodes {}, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateCovenant { + codes: Option, + clock: Option, + holder: Option, + splitter: Option, + party_a_router: Option, + party_b_router: Option, + party_a_forwarder: Box>, + party_b_forwarder: Box>, + }, + UpdateCodeId { + data: Option, + }, +} + +#[cw_serde] +pub enum RouterMigrateMsg { + Interchain(valence_interchain_router::msg::MigrateMsg), + Native(valence_native_router::msg::MigrateMsg), +} + +#[cw_serde] +pub struct CovenantContractCodes { + pub clock: u64, + pub holder: u64, + pub party_a_router: u64, + pub party_b_router: u64, + pub party_a_forwarder: u64, + pub party_b_forwarder: u64, + pub splitter: u64, +} diff --git a/contracts/swap-covenant/src/state.rs b/contracts/swap-covenant/src/state.rs new file mode 100644 index 00000000..59a6aa9c --- /dev/null +++ b/contracts/swap-covenant/src/state.rs @@ -0,0 +1,15 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +use crate::msg::CovenantContractCodes; + +pub const COVENANT_CLOCK_ADDR: Item = Item::new("covenant_clock_addr"); +pub const COVENANT_INTERCHAIN_SPLITTER_ADDR: Item = + Item::new("covenant_interchain_splitter_addr"); +pub const COVENANT_SWAP_HOLDER_ADDR: Item = Item::new("covenant_swap_holder_addr"); +pub const PARTY_A_IBC_FORWARDER_ADDR: Item = Item::new("party_a_ibc_forwarder_addr"); +pub const PARTY_B_IBC_FORWARDER_ADDR: Item = Item::new("party_b_ibc_forwarder_addr"); +pub const PARTY_A_ROUTER_ADDR: Item = Item::new("party_a_router_addr"); +pub const PARTY_B_ROUTER_ADDR: Item = Item::new("party_b_router_addr"); + +pub(crate) const CONTRACT_CODES: Item = Item::new("contract_codes"); diff --git a/contracts/swap-holder/.cargo/config b/contracts/swap-holder/.cargo/config new file mode 100644 index 00000000..6a6f2852 --- /dev/null +++ b/contracts/swap-holder/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" \ No newline at end of file diff --git a/contracts/swap-holder/Cargo.toml b/contracts/swap-holder/Cargo.toml new file mode 100644 index 00000000..c90fd26f --- /dev/null +++ b/contracts/swap-holder/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "valence-swap-holder" +authors = ["benskey bekauz@protonmail.com"] +description = "covenant contract to facilitate a tokenswap" +edition = { workspace = true } +license = { workspace = true } +# rust-version = { workspace = true } +version = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# disables #[entry_point] (i.e. instantiate/execute/query) export +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +covenant-macros = { workspace = true } +covenant-utils = { workspace = true } +cosmos-sdk-proto = { workspace = true } +neutron-sdk = { workspace = true } diff --git a/contracts/swap-holder/README.md b/contracts/swap-holder/README.md new file mode 100644 index 00000000..ade22b83 --- /dev/null +++ b/contracts/swap-holder/README.md @@ -0,0 +1,12 @@ +# Swap Holder + +Swap Holder is a contract meant to facilitate a tokenswap covenant between two parties. + +It holds a list of parties participating in the swap with amount and denom theyre expected to provide. + +If holder receives all expected tokens before the deposit deadline expires, +it forwards them to the splitter module, dequeues from the clock, and completes. + +If either/both party contributions fail to reach this contract before the expiration deadline, +holder completes without dequeuing itself from the clock. This enables any late deposits +to be refunded to the parties. diff --git a/contracts/swap-holder/schema/valence-swap-holder.json b/contracts/swap-holder/schema/valence-swap-holder.json new file mode 100644 index 00000000..aa478d6d --- /dev/null +++ b/contracts/swap-holder/schema/valence-swap-holder.json @@ -0,0 +1,1082 @@ +{ + "contract_name": "valence-swap-holder", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "clock_address", + "covenant_terms", + "lockup_config", + "next_contract", + "parties_config", + "refund_config" + ], + "properties": { + "clock_address": { + "description": "Address for the clock. This contract verifies that only the clock can execute Ticks", + "type": "string" + }, + "covenant_terms": { + "description": "terms of the covenant", + "allOf": [ + { + "$ref": "#/definitions/CovenantTerms" + } + ] + }, + "lockup_config": { + "description": "block height of covenant expiration. Position is exited automatically upon reaching that height.", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "next_contract": { + "description": "address of the next contract to forward the funds to. usually expected to be the splitter.", + "type": "string" + }, + "parties_config": { + "description": "parties engaged in the POL.", + "allOf": [ + { + "$ref": "#/definitions/CovenantPartiesConfig" + } + ] + }, + "refund_config": { + "description": "refund configuration containing party router adresses", + "allOf": [ + { + "$ref": "#/definitions/RefundConfig" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "CovenantPartiesConfig": { + "type": "object", + "required": [ + "party_a", + "party_b" + ], + "properties": { + "party_a": { + "$ref": "#/definitions/CovenantParty" + }, + "party_b": { + "$ref": "#/definitions/CovenantParty" + } + }, + "additionalProperties": false + }, + "CovenantParty": { + "type": "object", + "required": [ + "addr", + "native_denom", + "receiver_config" + ], + "properties": { + "addr": { + "description": "authorized address of the party", + "type": "string" + }, + "native_denom": { + "description": "denom provided by the party", + "type": "string" + }, + "receiver_config": { + "description": "information about receiver address", + "allOf": [ + { + "$ref": "#/definitions/ReceiverConfig" + } + ] + } + }, + "additionalProperties": false + }, + "CovenantTerms": { + "oneOf": [ + { + "type": "object", + "required": [ + "token_swap" + ], + "properties": { + "token_swap": { + "$ref": "#/definitions/SwapCovenantTerms" + } + }, + "additionalProperties": false + } + ] + }, + "DestinationConfig": { + "type": "object", + "required": [ + "denom_to_pfm_map", + "destination_receiver_addr", + "ibc_transfer_timeout", + "local_to_destination_chain_channel_id" + ], + "properties": { + "denom_to_pfm_map": { + "description": "pfm configurations for denoms", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "destination_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "local_to_destination_chain_channel_id": { + "description": "channel id of the destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ReceiverConfig": { + "oneOf": [ + { + "description": "party expects to receive funds on the same chain", + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "party expects to receive funds on a remote chain", + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/DestinationConfig" + } + }, + "additionalProperties": false + } + ] + }, + "RefundConfig": { + "type": "object", + "required": [ + "party_a_refund_address", + "party_b_refund_address" + ], + "properties": { + "party_a_refund_address": { + "type": "string" + }, + "party_b_refund_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "SwapCovenantTerms": { + "type": "object", + "required": [ + "party_a_amount", + "party_b_amount" + ], + "properties": { + "party_a_amount": { + "$ref": "#/definitions/Uint128" + }, + "party_b_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Wakes the state machine up. The caller should check the sender of the tick is the clock if they'd like to pause when the clock does.", + "type": "object", + "required": [ + "tick" + ], + "properties": { + "tick": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "next_contract" + ], + "properties": { + "next_contract": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "lockup_config" + ], + "properties": { + "lockup_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "covenant_parties" + ], + "properties": { + "covenant_parties": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "covenant_terms" + ], + "properties": { + "covenant_terms": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "contract_state" + ], + "properties": { + "contract_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "refund_config" + ], + "properties": { + "refund_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated clock address authorized to submit ticks", + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the address a contract expects to receive funds to", + "type": "object", + "required": [ + "deposit_address" + ], + "properties": { + "deposit_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "covenant_terms": { + "anyOf": [ + { + "$ref": "#/definitions/CovenantTerms" + }, + { + "type": "null" + } + ] + }, + "lockup_config": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "next_contract": { + "type": [ + "string", + "null" + ] + }, + "parites_config": { + "anyOf": [ + { + "$ref": "#/definitions/CovenantPartiesConfig" + }, + { + "type": "null" + } + ] + }, + "refund_config": { + "anyOf": [ + { + "$ref": "#/definitions/RefundConfig" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "CovenantPartiesConfig": { + "type": "object", + "required": [ + "party_a", + "party_b" + ], + "properties": { + "party_a": { + "$ref": "#/definitions/CovenantParty" + }, + "party_b": { + "$ref": "#/definitions/CovenantParty" + } + }, + "additionalProperties": false + }, + "CovenantParty": { + "type": "object", + "required": [ + "addr", + "native_denom", + "receiver_config" + ], + "properties": { + "addr": { + "description": "authorized address of the party", + "type": "string" + }, + "native_denom": { + "description": "denom provided by the party", + "type": "string" + }, + "receiver_config": { + "description": "information about receiver address", + "allOf": [ + { + "$ref": "#/definitions/ReceiverConfig" + } + ] + } + }, + "additionalProperties": false + }, + "CovenantTerms": { + "oneOf": [ + { + "type": "object", + "required": [ + "token_swap" + ], + "properties": { + "token_swap": { + "$ref": "#/definitions/SwapCovenantTerms" + } + }, + "additionalProperties": false + } + ] + }, + "DestinationConfig": { + "type": "object", + "required": [ + "denom_to_pfm_map", + "destination_receiver_addr", + "ibc_transfer_timeout", + "local_to_destination_chain_channel_id" + ], + "properties": { + "denom_to_pfm_map": { + "description": "pfm configurations for denoms", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "destination_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "local_to_destination_chain_channel_id": { + "description": "channel id of the destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ReceiverConfig": { + "oneOf": [ + { + "description": "party expects to receive funds on the same chain", + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "party expects to receive funds on a remote chain", + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/DestinationConfig" + } + }, + "additionalProperties": false + } + ] + }, + "RefundConfig": { + "type": "object", + "required": [ + "party_a_refund_address", + "party_b_refund_address" + ], + "properties": { + "party_a_refund_address": { + "type": "string" + }, + "party_b_refund_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + "SwapCovenantTerms": { + "type": "object", + "required": [ + "party_a_amount", + "party_b_amount" + ], + "properties": { + "party_a_amount": { + "$ref": "#/definitions/Uint128" + }, + "party_b_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "sudo": null, + "responses": { + "clock_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "contract_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractState", + "oneOf": [ + { + "type": "string", + "enum": [ + "instantiated" + ] + }, + { + "description": "covenant has reached its expiration date.", + "type": "string", + "enum": [ + "expired" + ] + }, + { + "description": "underlying funds have been withdrawn.", + "type": "string", + "enum": [ + "complete" + ] + } + ] + }, + "covenant_parties": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CovenantPartiesConfig", + "type": "object", + "required": [ + "party_a", + "party_b" + ], + "properties": { + "party_a": { + "$ref": "#/definitions/CovenantParty" + }, + "party_b": { + "$ref": "#/definitions/CovenantParty" + } + }, + "additionalProperties": false, + "definitions": { + "CovenantParty": { + "type": "object", + "required": [ + "addr", + "native_denom", + "receiver_config" + ], + "properties": { + "addr": { + "description": "authorized address of the party", + "type": "string" + }, + "native_denom": { + "description": "denom provided by the party", + "type": "string" + }, + "receiver_config": { + "description": "information about receiver address", + "allOf": [ + { + "$ref": "#/definitions/ReceiverConfig" + } + ] + } + }, + "additionalProperties": false + }, + "DestinationConfig": { + "type": "object", + "required": [ + "denom_to_pfm_map", + "destination_receiver_addr", + "ibc_transfer_timeout", + "local_to_destination_chain_channel_id" + ], + "properties": { + "denom_to_pfm_map": { + "description": "pfm configurations for denoms", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "destination_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "local_to_destination_chain_channel_id": { + "description": "channel id of the destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ReceiverConfig": { + "oneOf": [ + { + "description": "party expects to receive funds on the same chain", + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "party expects to receive funds on a remote chain", + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/DestinationConfig" + } + }, + "additionalProperties": false + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "covenant_terms": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CovenantTerms", + "oneOf": [ + { + "type": "object", + "required": [ + "token_swap" + ], + "properties": { + "token_swap": { + "$ref": "#/definitions/SwapCovenantTerms" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "SwapCovenantTerms": { + "type": "object", + "required": [ + "party_a_amount", + "party_b_amount" + ], + "properties": { + "party_a_amount": { + "$ref": "#/definitions/Uint128" + }, + "party_b_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "deposit_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "lockup_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Expiration", + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "next_contract": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" + }, + "refund_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RefundConfig", + "type": "object", + "required": [ + "party_a_refund_address", + "party_b_refund_address" + ], + "properties": { + "party_a_refund_address": { + "type": "string" + }, + "party_b_refund_address": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/clock-tester/examples/schema.rs b/contracts/swap-holder/src/bin/schema.rs similarity index 59% rename from contracts/clock-tester/examples/schema.rs rename to contracts/swap-holder/src/bin/schema.rs index 6b26d02e..ec686565 100644 --- a/contracts/clock-tester/examples/schema.rs +++ b/contracts/swap-holder/src/bin/schema.rs @@ -1,11 +1,11 @@ use cosmwasm_schema::write_api; - -use covenant_clock_tester::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use valence_swap_holder::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { instantiate: InstantiateMsg, execute: ExecuteMsg, query: QueryMsg, + migrate: MigrateMsg, } } diff --git a/contracts/swap-holder/src/contract.rs b/contracts/swap-holder/src/contract.rs new file mode 100644 index 00000000..df25bf14 --- /dev/null +++ b/contracts/swap-holder/src/contract.rs @@ -0,0 +1,276 @@ +use cosmwasm_std::{ + to_json_binary, Addr, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, + StdError, StdResult, Uint128, +}; +use covenant_utils::CovenantTerms; + +use crate::{ + error::ContractError, + msg::{ContractState, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + state::{ + CLOCK_ADDRESS, CONTRACT_STATE, COVENANT_TERMS, LOCKUP_CONFIG, NEXT_CONTRACT, + PARTIES_CONFIG, REFUND_CONFIG, + }, +}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cw2::set_contract_version; +use valence_clock::helpers::{enqueue_msg, verify_clock}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let next_contract = deps.api.addr_validate(&msg.next_contract)?; + let clock_addr = deps.api.addr_validate(&msg.clock_address)?; + msg.parties_config.validate_party_addresses(deps.api)?; + if msg.lockup_config.is_expired(&env.block) { + return Err(ContractError::Std(StdError::generic_err( + "past lockup config", + ))); + } + deps.api + .addr_validate(&msg.refund_config.party_a_refund_address)?; + deps.api + .addr_validate(&msg.refund_config.party_b_refund_address)?; + + NEXT_CONTRACT.save(deps.storage, &next_contract)?; + CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; + LOCKUP_CONFIG.save(deps.storage, &msg.lockup_config)?; + PARTIES_CONFIG.save(deps.storage, &msg.parties_config)?; + COVENANT_TERMS.save(deps.storage, &msg.covenant_terms)?; + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + REFUND_CONFIG.save(deps.storage, &msg.refund_config)?; + + Ok(Response::default() + .add_message(enqueue_msg(clock_addr.as_str())?) + .add_attribute("method", "swap_holder_instantiate") + .add_attributes(msg.get_response_attributes())) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Tick {} => try_tick(deps, env, info), + } +} + +/// attempts to advance the state machine. performs `info.sender` validation +fn try_tick(deps: DepsMut, env: Env, info: MessageInfo) -> Result { + // Verify caller is the clock + verify_clock(&info.sender, &CLOCK_ADDRESS.load(deps.storage)?)?; + + let current_state = CONTRACT_STATE.load(deps.storage)?; + match current_state { + ContractState::Instantiated => try_forward(deps, env, info.sender), + ContractState::Expired => try_refund(deps, env), + ContractState::Complete => Ok(Response::default() + .add_attribute("contract_state", "complete") + .add_attribute("method", "try_tick")), + } +} + +/// attempts to route any available covenant party contribution denoms to +/// the parties that were responsible for contributing that denom. +fn try_refund(deps: DepsMut, env: Env) -> Result { + let parties = PARTIES_CONFIG.load(deps.storage)?; + let refund_config = REFUND_CONFIG.load(deps.storage)?; + + // query holder balances + let party_a_bal = deps.querier.query_balance( + env.contract.address.to_string(), + parties.party_a.native_denom, + )?; + let party_b_bal = deps.querier.query_balance( + env.contract.address.to_string(), + parties.party_b.native_denom, + )?; + + let refund_messages: Vec = + match (party_a_bal.amount.is_zero(), party_b_bal.amount.is_zero()) { + // both balances empty, nothing to refund + (true, true) => vec![], + // party A failed to deposit. refund party B + (true, false) => vec![CosmosMsg::Bank(BankMsg::Send { + to_address: refund_config.party_b_refund_address, + amount: vec![party_b_bal], + })], + // party B failed to deposit. refund party A + (false, true) => vec![CosmosMsg::Bank(BankMsg::Send { + to_address: refund_config.party_a_refund_address, + amount: vec![party_a_bal], + })], + // not enough balances to perform the covenant swap. + // refund denoms to both parties. + (false, false) => vec![ + CosmosMsg::Bank(BankMsg::Send { + to_address: refund_config.party_a_refund_address, + amount: vec![party_a_bal], + }), + CosmosMsg::Bank(BankMsg::Send { + to_address: refund_config.party_b_refund_address, + amount: vec![party_b_bal], + }), + ], + }; + + Ok(Response::default() + .add_attribute("contract_state", "expired") + .add_attribute("method", "try_refund") + .add_messages(refund_messages)) +} + +fn try_forward(mut deps: DepsMut, env: Env, clock_addr: Addr) -> Result { + let lockup_config = LOCKUP_CONFIG.load(deps.storage)?; + // check if covenant is expired + if lockup_config.is_expired(&env.block) { + CONTRACT_STATE.save(deps.storage, &ContractState::Expired)?; + return Ok(Response::default() + .add_attribute("method", "try_forward") + .add_attribute("result", "covenant_expired") + .add_attribute("contract_state", "expired")); + } + + let parties = PARTIES_CONFIG.load(deps.storage)?; + let CovenantTerms::TokenSwap(covenant_terms) = COVENANT_TERMS.load(deps.storage)?; + + let mut party_a_coin = deps + .querier + .query_balance(env.contract.address.clone(), parties.party_a.native_denom)?; + let mut party_b_coin = deps + .querier + .query_balance(env.contract.address, parties.party_b.native_denom)?; + + if party_a_coin.amount < covenant_terms.party_a_amount { + party_a_coin.amount = Uint128::zero(); + } else if party_b_coin.amount < covenant_terms.party_b_amount { + party_b_coin.amount = Uint128::zero(); + } + + // if either of the coin amounts did not get updated to non-zero, + // we are not ready for the swap yet + if party_a_coin.amount.is_zero() || party_b_coin.amount.is_zero() { + return Err(ContractError::InsufficientFunds {}); + } + + // otherwise we are ready to forward the funds to the next module + let amount = vec![party_a_coin, party_b_coin]; + + // first we query the deposit address of next module + let next_contract = NEXT_CONTRACT.load(deps.storage)?; + let deposit_address_query = deps.querier.query_wasm_smart( + next_contract, + &covenant_utils::neutron::QueryMsg::DepositAddress {}, + )?; + + // if query returns None, then we error and wait + let Some(deposit_address) = deposit_address_query else { + return Err(ContractError::Std(StdError::not_found( + "Next contract is not ready for receiving the funds yet", + ))); + }; + + let bank_msg = BankMsg::Send { + to_address: deposit_address, + amount, + }; + + // given that we successfully forward the expected funds, + // we can now dequeue from the clock and complete + let dequeue_msg = ContractState::complete_and_dequeue(deps.branch(), clock_addr.as_str())?; + + Ok(Response::default() + .add_message(bank_msg) + .add_message(dequeue_msg)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::NextContract {} => Ok(to_json_binary(&NEXT_CONTRACT.may_load(deps.storage)?)?), + QueryMsg::LockupConfig {} => Ok(to_json_binary(&LOCKUP_CONFIG.may_load(deps.storage)?)?), + QueryMsg::CovenantParties {} => { + Ok(to_json_binary(&PARTIES_CONFIG.may_load(deps.storage)?)?) + } + QueryMsg::CovenantTerms {} => Ok(to_json_binary(&COVENANT_TERMS.may_load(deps.storage)?)?), + QueryMsg::ClockAddress {} => Ok(to_json_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), + QueryMsg::ContractState {} => Ok(to_json_binary(&CONTRACT_STATE.may_load(deps.storage)?)?), + // the deposit address for swap-holder is the contract itself + QueryMsg::DepositAddress {} => Ok(to_json_binary(&Some(env.contract.address))?), + QueryMsg::RefundConfig {} => Ok(to_json_binary(&REFUND_CONFIG.may_load(deps.storage)?)?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult { + match msg { + MigrateMsg::UpdateConfig { + clock_addr, + next_contract, + lockup_config, + parites_config, + covenant_terms, + refund_config, + } => { + let mut resp = Response::default().add_attribute("method", "update_config"); + + if let Some(addr) = clock_addr { + let clock_address = deps.api.addr_validate(&addr)?; + CLOCK_ADDRESS.save(deps.storage, &clock_address)?; + resp = resp.add_attribute("clock_addr", addr); + } + + if let Some(addr) = next_contract { + let next_contract_addr = deps.api.addr_validate(&addr)?; + NEXT_CONTRACT.save(deps.storage, &next_contract_addr)?; + resp = resp.add_attribute("next_contract", addr); + } + + if let Some(expiry_config) = lockup_config { + if expiry_config.is_expired(&env.block) { + return Err(StdError::generic_err("lockup config is already past")); + } + LOCKUP_CONFIG.save(deps.storage, &expiry_config)?; + resp = resp.add_attribute("lockup_config", expiry_config.to_string()); + } + + if let Some(parites_config) = *parites_config { + PARTIES_CONFIG.save(deps.storage, &parites_config)?; + resp = resp.add_attribute("parites_config", format!("{parites_config:?}")); + } + + if let Some(covenant_terms) = covenant_terms { + COVENANT_TERMS.save(deps.storage, &covenant_terms)?; + resp = resp.add_attribute("covenant_terms", format!("{covenant_terms:?}")); + } + + if let Some(config) = refund_config { + deps.api.addr_validate(&config.party_a_refund_address)?; + deps.api.addr_validate(&config.party_b_refund_address)?; + REFUND_CONFIG.save(deps.storage, &config)?; + resp = resp.add_attribute("refund_config", format!("{config:?}")); + } + + Ok(resp) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} diff --git a/contracts/swap-holder/src/error.rs b/contracts/swap-holder/src/error.rs new file mode 100644 index 00000000..899a4ae5 --- /dev/null +++ b/contracts/swap-holder/src/error.rs @@ -0,0 +1,24 @@ +use cosmwasm_std::StdError; +use neutron_sdk::NeutronError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error(transparent)] + NeutronError(#[from] NeutronError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("No withdrawer address configured")] + NoWithdrawerError {}, + + #[error("Insufficient funds to forward")] + InsufficientFunds {}, + + #[error("unexpected reply id")] + UnexpectedReplyId {}, +} diff --git a/contracts/swap-holder/src/lib.rs b/contracts/swap-holder/src/lib.rs new file mode 100644 index 00000000..a5abdbb0 --- /dev/null +++ b/contracts/swap-holder/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/swap-holder/src/msg.rs b/contracts/swap-holder/src/msg.rs new file mode 100644 index 00000000..7ad2313d --- /dev/null +++ b/contracts/swap-holder/src/msg.rs @@ -0,0 +1,122 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{ + to_json_binary, Addr, Attribute, Binary, DepsMut, StdError, StdResult, WasmMsg, +}; +use covenant_macros::{clocked, covenant_clock_address, covenant_deposit_address}; +use covenant_utils::{ + instantiate2_helper::Instantiate2HelperConfig, CovenantPartiesConfig, CovenantTerms, +}; +use cw_utils::Expiration; +use valence_clock::helpers::dequeue_msg; + +use crate::state::CONTRACT_STATE; + +#[cw_serde] +pub struct InstantiateMsg { + /// Address for the clock. This contract verifies + /// that only the clock can execute Ticks + pub clock_address: String, + /// address of the next contract to forward the funds to. + /// usually expected to be the splitter. + pub next_contract: String, + /// block height of covenant expiration. Position is exited + /// automatically upon reaching that height. + pub lockup_config: Expiration, + /// parties engaged in the POL. + pub parties_config: CovenantPartiesConfig, + /// terms of the covenant + pub covenant_terms: CovenantTerms, + /// refund configuration containing party router adresses + pub refund_config: RefundConfig, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +impl InstantiateMsg { + pub fn get_response_attributes(self) -> Vec { + let mut attrs = vec![ + Attribute::new("clock_addr", self.clock_address), + Attribute::new("next_contract", self.next_contract), + Attribute::new("lockup_config", self.lockup_config.to_string()), + ]; + attrs.extend(self.parties_config.get_response_attributes()); + attrs.extend(self.covenant_terms.get_response_attributes()); + attrs + } +} + +#[cw_serde] +pub struct RefundConfig { + pub party_a_refund_address: String, + pub party_b_refund_address: String, +} + +#[clocked] +#[cw_serde] +pub enum ExecuteMsg {} + +#[covenant_clock_address] +#[covenant_deposit_address] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(String)] + NextContract {}, + #[returns(Expiration)] + LockupConfig {}, + #[returns(CovenantPartiesConfig)] + CovenantParties {}, + #[returns(CovenantTerms)] + CovenantTerms {}, + #[returns(ContractState)] + ContractState {}, + #[returns(RefundConfig)] + RefundConfig {}, +} + +#[cw_serde] +pub enum ContractState { + Instantiated, + /// covenant has reached its expiration date. + Expired, + /// underlying funds have been withdrawn. + Complete, +} + +impl ContractState { + pub fn complete_and_dequeue(deps: DepsMut, clock_addr: &str) -> Result { + CONTRACT_STATE.save(deps.storage, &ContractState::Complete)?; + dequeue_msg(clock_addr) + } +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + next_contract: Option, + lockup_config: Option, + parites_config: Box>, + covenant_terms: Option, + refund_config: Option, + }, + UpdateCodeId { + data: Option, + }, +} diff --git a/contracts/swap-holder/src/state.rs b/contracts/swap-holder/src/state.rs new file mode 100644 index 00000000..9b889c9a --- /dev/null +++ b/contracts/swap-holder/src/state.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::Addr; +use covenant_utils::{CovenantPartiesConfig, CovenantTerms}; +use cw_storage_plus::Item; +use cw_utils::Expiration; + +use crate::msg::{ContractState, RefundConfig}; + +pub const CONTRACT_STATE: Item = Item::new("contract_state"); +pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); +pub const NEXT_CONTRACT: Item = Item::new("next_contract"); +pub const PARTIES_CONFIG: Item = Item::new("parties_config"); +pub const LOCKUP_CONFIG: Item = Item::new("lockup_config"); +pub const COVENANT_TERMS: Item = Item::new("covenant_terms"); +pub const REFUND_CONFIG: Item = Item::new("refund_config"); diff --git a/contracts/two-party-pol-covenant/.cargo/config b/contracts/two-party-pol-covenant/.cargo/config new file mode 100644 index 00000000..5f6aa466 --- /dev/null +++ b/contracts/two-party-pol-covenant/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" diff --git a/contracts/two-party-pol-covenant/Cargo.toml b/contracts/two-party-pol-covenant/Cargo.toml new file mode 100644 index 00000000..0e204d6b --- /dev/null +++ b/contracts/two-party-pol-covenant/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "valence-covenant-two-party-pol" +edition = { workspace = true } +authors = ["benskey bekauz@protonmail.com"] +description = "Two Party POL covenant" +license = { workspace = true } +repository = { workspace = true } +version = { workspace = true } + +exclude = ["contract.wasm", "hash.txt"] + + +[lib] +crate-type = ["cdylib", "rlib"] + + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +sha2 = { workspace = true } +neutron-sdk = { workspace = true } +cosmos-sdk-proto = { workspace = true } +schemars = { workspace = true } +serde-json-wasm = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +bech32 = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +covenant-utils = { workspace = true } +valence-ibc-forwarder = { workspace = true, features = ["library"] } +valence-interchain-router = { workspace = true, features = ["library"] } +valence-native-router = { workspace = true, features = ["library"] } +valence-two-party-pol-holder = { workspace = true, features = ["library"] } +valence-astroport-liquid-pooler = { workspace = true, features = ["library"] } +valence-osmo-liquid-pooler = { workspace = true, features = ["library"] } +astroport = { workspace = true } diff --git a/contracts/two-party-pol-covenant/README.md b/contracts/two-party-pol-covenant/README.md new file mode 100644 index 00000000..c8e0f548 --- /dev/null +++ b/contracts/two-party-pol-covenant/README.md @@ -0,0 +1,4 @@ +# two party POL covenant + +Contract responsible for orchestrating the flow for a two party POL. + diff --git a/contracts/two-party-pol-covenant/schema/valence-covenant-two-party-pol.json b/contracts/two-party-pol-covenant/schema/valence-covenant-two-party-pol.json new file mode 100644 index 00000000..d82d0a1e --- /dev/null +++ b/contracts/two-party-pol-covenant/schema/valence-covenant-two-party-pol.json @@ -0,0 +1,2658 @@ +{ + "contract_name": "valence-covenant-two-party-pol", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "contract_codes", + "covenant_type", + "deposit_deadline", + "label", + "liquid_pooler_config", + "lockup_config", + "party_a_config", + "party_a_share", + "party_b_config", + "party_b_share", + "pool_price_config", + "splits", + "timeouts" + ], + "properties": { + "clock_tick_max_gas": { + "anyOf": [ + { + "$ref": "#/definitions/Uint64" + }, + { + "type": "null" + } + ] + }, + "contract_codes": { + "$ref": "#/definitions/CovenantContractCodeIds" + }, + "covenant_type": { + "$ref": "#/definitions/CovenantType" + }, + "deposit_deadline": { + "$ref": "#/definitions/Expiration" + }, + "emergency_committee": { + "type": [ + "string", + "null" + ] + }, + "fallback_address": { + "type": [ + "string", + "null" + ] + }, + "fallback_split": { + "anyOf": [ + { + "$ref": "#/definitions/SplitConfig" + }, + { + "type": "null" + } + ] + }, + "label": { + "type": "string" + }, + "liquid_pooler_config": { + "$ref": "#/definitions/LiquidPoolerConfig" + }, + "lockup_config": { + "$ref": "#/definitions/Expiration" + }, + "party_a_config": { + "$ref": "#/definitions/CovenantPartyConfig" + }, + "party_a_share": { + "$ref": "#/definitions/Decimal" + }, + "party_b_config": { + "$ref": "#/definitions/CovenantPartyConfig" + }, + "party_b_share": { + "$ref": "#/definitions/Decimal" + }, + "pool_price_config": { + "$ref": "#/definitions/PoolPriceConfig" + }, + "ragequit_config": { + "anyOf": [ + { + "$ref": "#/definitions/RagequitConfig" + }, + { + "type": "null" + } + ] + }, + "splits": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } + }, + "timeouts": { + "$ref": "#/definitions/Timeouts" + } + }, + "additionalProperties": false, + "definitions": { + "AstroportLiquidPoolerConfig": { + "type": "object", + "required": [ + "asset_a_denom", + "asset_b_denom", + "pool_address", + "pool_pair_type", + "single_side_lp_limits" + ], + "properties": { + "asset_a_denom": { + "type": "string" + }, + "asset_b_denom": { + "type": "string" + }, + "pool_address": { + "type": "string" + }, + "pool_pair_type": { + "$ref": "#/definitions/PairType" + }, + "single_side_lp_limits": { + "$ref": "#/definitions/SingleSideLpLimits" + } + }, + "additionalProperties": false + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CovenantContractCodeIds": { + "type": "object", + "required": [ + "clock_code", + "holder_code", + "ibc_forwarder_code", + "interchain_router_code", + "liquid_pooler_code", + "native_router_code" + ], + "properties": { + "clock_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "holder_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "ibc_forwarder_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "interchain_router_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "liquid_pooler_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "native_router_code": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "CovenantPartyConfig": { + "oneOf": [ + { + "type": "object", + "required": [ + "interchain" + ], + "properties": { + "interchain": { + "$ref": "#/definitions/InterchainCovenantParty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "$ref": "#/definitions/NativeCovenantParty" + } + }, + "additionalProperties": false + } + ] + }, + "CovenantType": { + "type": "string", + "enum": [ + "share", + "side" + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "ForwardMetadata": { + "type": "object", + "required": [ + "channel", + "port", + "receiver" + ], + "properties": { + "channel": { + "type": "string" + }, + "port": { + "type": "string" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + }, + "InterchainCovenantParty": { + "type": "object", + "required": [ + "addr", + "contribution", + "denom_to_pfm_map", + "host_to_party_chain_channel_id", + "ibc_transfer_timeout", + "native_denom", + "party_chain_connection_id", + "party_receiver_addr", + "party_to_host_chain_channel_id", + "remote_chain_denom" + ], + "properties": { + "addr": { + "description": "authorized address of the party on neutron", + "type": "string" + }, + "contribution": { + "description": "coin provided by the party on its native chain", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "denom_to_pfm_map": { + "description": "configuration for unwinding the denoms via pfm", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "fallback_address": { + "description": "fallback refund address on the remote chain", + "type": [ + "string", + "null" + ] + }, + "host_to_party_chain_channel_id": { + "description": "channel id from host chain to the party chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "native_denom": { + "description": "denom provided by the party on neutron", + "type": "string" + }, + "party_chain_connection_id": { + "description": "connection id to the party chain", + "type": "string" + }, + "party_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "party_to_host_chain_channel_id": { + "description": "channel id from party to host chain", + "type": "string" + }, + "remote_chain_denom": { + "description": "denom provided by the party on its native chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "LiquidPoolerConfig": { + "oneOf": [ + { + "type": "object", + "required": [ + "osmosis" + ], + "properties": { + "osmosis": { + "$ref": "#/definitions/OsmosisLiquidPoolerConfig" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "astroport" + ], + "properties": { + "astroport": { + "$ref": "#/definitions/AstroportLiquidPoolerConfig" + } + }, + "additionalProperties": false + } + ] + }, + "NativeCovenantParty": { + "type": "object", + "required": [ + "addr", + "contribution", + "native_denom", + "party_receiver_addr" + ], + "properties": { + "addr": { + "description": "authorized address of the party on neutron", + "type": "string" + }, + "contribution": { + "description": "coin provided by the party on its native chain", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "native_denom": { + "description": "denom provided by the party on neutron", + "type": "string" + }, + "party_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "OsmosisLiquidPoolerConfig": { + "type": "object", + "required": [ + "funding_duration", + "lp_token_denom", + "note_address", + "osmo_ibc_timeout", + "osmo_outpost", + "osmo_to_neutron_channel_id", + "party_1_chain_info", + "party_1_denom_info", + "party_2_chain_info", + "party_2_denom_info", + "pool_id", + "single_side_lp_limits" + ], + "properties": { + "funding_duration": { + "$ref": "#/definitions/Duration" + }, + "lp_token_denom": { + "type": "string" + }, + "note_address": { + "type": "string" + }, + "osmo_ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "osmo_outpost": { + "type": "string" + }, + "osmo_to_neutron_channel_id": { + "type": "string" + }, + "party_1_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + }, + "party_1_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "party_2_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + }, + "party_2_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "pool_id": { + "$ref": "#/definitions/Uint64" + }, + "single_side_lp_limits": { + "$ref": "#/definitions/SingleSideLpLimits" + } + }, + "additionalProperties": false + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", + "type": "object", + "required": [ + "stable" + ], + "properties": { + "stable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "PartyChainInfo": { + "type": "object", + "required": [ + "ibc_timeout", + "neutron_to_party_chain_channel", + "party_chain_to_neutron_channel" + ], + "properties": { + "ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "inwards_pfm": { + "description": "pfm configuration used to route funds from osmosis to local chain via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "neutron_to_party_chain_channel": { + "description": "channel id to route funds from local chain to party chain", + "type": "string" + }, + "outwards_pfm": { + "description": "pfm configuration used to route funds from local chain to osmosis via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "party_chain_to_neutron_channel": { + "description": "channel id to route funds from party chain to local chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PartyDenomInfo": { + "type": "object", + "required": [ + "local_denom", + "osmosis_coin" + ], + "properties": { + "local_denom": { + "description": "ibc denom on liquid pooler chain", + "type": "string" + }, + "osmosis_coin": { + "description": "coin as denominated on osmosis", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "PoolPriceConfig": { + "description": "config for the pool price expectations upon covenant instantiation", + "type": "object", + "required": [ + "acceptable_price_spread", + "expected_spot_price" + ], + "properties": { + "acceptable_price_spread": { + "$ref": "#/definitions/Decimal" + }, + "expected_spot_price": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "RagequitConfig": { + "oneOf": [ + { + "description": "ragequit is disabled", + "type": "string", + "enum": [ + "disabled" + ] + }, + { + "description": "ragequit is enabled with `RagequitTerms`", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "$ref": "#/definitions/RagequitTerms" + } + }, + "additionalProperties": false + } + ] + }, + "RagequitState": { + "type": "object", + "required": [ + "coins", + "rq_party" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "rq_party": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + } + }, + "additionalProperties": false + }, + "RagequitTerms": { + "type": "object", + "required": [ + "penalty" + ], + "properties": { + "penalty": { + "description": "decimal based penalty to be applied on a party for initiating ragequit. Must be in the range of (0.00, 1.00). Also must not exceed either party allocations in raw values.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "state": { + "description": "optional rq state. none indicates no ragequit. some holds the ragequit related config", + "anyOf": [ + { + "$ref": "#/definitions/RagequitState" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "SingleSideLpLimits": { + "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", + "type": "object", + "required": [ + "asset_a_limit", + "asset_b_limit" + ], + "properties": { + "asset_a_limit": { + "$ref": "#/definitions/Uint128" + }, + "asset_b_limit": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + }, + "Timeouts": { + "type": "object", + "required": [ + "ibc_transfer_timeout", + "ica_timeout" + ], + "properties": { + "ibc_transfer_timeout": { + "description": "ibc transfer timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "ica_timeout": { + "description": "ica timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "TwoPartyPolCovenantParty": { + "type": "object", + "required": [ + "allocation", + "contribution", + "controller_addr", + "host_addr", + "router" + ], + "properties": { + "allocation": { + "description": "fraction of the entire LP position owned by the party. upon exiting it becomes 0.00. if counterparty exits, this would become 1.00, meaning that this party owns the entire position managed by the covenant.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "contribution": { + "description": "the `denom` and `amount` (`Uint128`) to be contributed by the party", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "controller_addr": { + "description": "address of the party on the controller chain (final receiver)", + "type": "string" + }, + "host_addr": { + "description": "neutron address authorized by the party to perform claims/ragequits", + "type": "string" + }, + "router": { + "description": "address of the interchain router associated with this party", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "type": "string", + "enum": [] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "holder_address" + ], + "properties": { + "holder_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc_forwarder_address" + ], + "properties": { + "ibc_forwarder_address": { + "type": "object", + "required": [ + "party" + ], + "properties": { + "party": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "interchain_router_address" + ], + "properties": { + "interchain_router_address": { + "type": "object", + "required": [ + "party" + ], + "properties": { + "party": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "liquid_pooler_address" + ], + "properties": { + "liquid_pooler_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "party_deposit_address" + ], + "properties": { + "party_deposit_address": { + "type": "object", + "required": [ + "party" + ], + "properties": { + "party": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "contract_codes" + ], + "properties": { + "contract_codes": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_covenant" + ], + "properties": { + "update_covenant": { + "type": "object", + "properties": { + "clock": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg" + }, + { + "type": "null" + } + ] + }, + "codes": { + "anyOf": [ + { + "$ref": "#/definitions/CovenantContractCodes" + }, + { + "type": "null" + } + ] + }, + "holder": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg2" + }, + { + "type": "null" + } + ] + }, + "liquid_pooler": { + "anyOf": [ + { + "$ref": "#/definitions/LiquidPoolerMigrateMsg" + }, + { + "type": "null" + } + ] + }, + "party_a_forwarder": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg7" + }, + { + "type": "null" + } + ] + }, + "party_a_router": { + "anyOf": [ + { + "$ref": "#/definitions/RouterMigrateMsg" + }, + { + "type": "null" + } + ] + }, + "party_b_forwarder": { + "anyOf": [ + { + "$ref": "#/definitions/MigrateMsg7" + }, + { + "type": "null" + } + ] + }, + "party_b_router": { + "anyOf": [ + { + "$ref": "#/definitions/RouterMigrateMsg" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AssetData": { + "description": "holds the both asset denoms relevant for providing liquidity", + "type": "object", + "required": [ + "asset_a_denom", + "asset_b_denom" + ], + "properties": { + "asset_a_denom": { + "type": "string" + }, + "asset_b_denom": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CovenantContractCodes": { + "type": "object", + "required": [ + "clock", + "holder", + "liquid_pooler", + "party_a_forwarder", + "party_a_router", + "party_b_forwarder", + "party_b_router" + ], + "properties": { + "clock": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "holder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "liquid_pooler": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_a_forwarder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_a_router": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_b_forwarder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_b_router": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "CovenantType": { + "type": "string", + "enum": [ + "share", + "side" + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "DecimalRange": { + "type": "object", + "required": [ + "max", + "min" + ], + "properties": { + "max": { + "$ref": "#/definitions/Decimal" + }, + "min": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "DestinationConfig": { + "type": "object", + "required": [ + "denom_to_pfm_map", + "destination_receiver_addr", + "ibc_transfer_timeout", + "local_to_destination_chain_channel_id" + ], + "properties": { + "denom_to_pfm_map": { + "description": "pfm configurations for denoms", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PacketForwardMiddlewareConfig" + } + }, + "destination_receiver_addr": { + "description": "address of the receiver on destination chain", + "type": "string" + }, + "ibc_transfer_timeout": { + "description": "timeout in seconds", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "local_to_destination_chain_channel_id": { + "description": "channel id of the destination chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "FallbackAddressUpdateConfig": { + "oneOf": [ + { + "type": "object", + "required": [ + "explicit_address" + ], + "properties": { + "explicit_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "disable" + ], + "properties": { + "disable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "ForwardMetadata": { + "type": "object", + "required": [ + "channel", + "port", + "receiver" + ], + "properties": { + "channel": { + "type": "string" + }, + "port": { + "type": "string" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + }, + "IbcConfig": { + "type": "object", + "required": [ + "osmo_ibc_timeout", + "osmo_to_neutron_channel_id", + "party_1_chain_info", + "party_2_chain_info" + ], + "properties": { + "osmo_ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "osmo_to_neutron_channel_id": { + "type": "string" + }, + "party_1_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + }, + "party_2_chain_info": { + "$ref": "#/definitions/PartyChainInfo" + } + }, + "additionalProperties": false + }, + "LiquidPoolerMigrateMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "osmosis" + ], + "properties": { + "osmosis": { + "$ref": "#/definitions/MigrateMsg3" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "astroport" + ], + "properties": { + "astroport": { + "$ref": "#/definitions/MigrateMsg4" + } + }, + "additionalProperties": false + } + ] + }, + "LiquidityProvisionConfig": { + "type": "object", + "required": [ + "funding_duration", + "latest_balances", + "lp_token_denom", + "outpost", + "party_1_denom_info", + "party_2_denom_info", + "pool_id", + "pool_price_config", + "single_side_lp_limits" + ], + "properties": { + "funding_duration": { + "$ref": "#/definitions/Duration" + }, + "latest_balances": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Coin" + } + }, + "lp_token_denom": { + "type": "string" + }, + "outpost": { + "type": "string" + }, + "party_1_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "party_2_denom_info": { + "$ref": "#/definitions/PartyDenomInfo" + }, + "pool_id": { + "$ref": "#/definitions/Uint64" + }, + "pool_price_config": { + "$ref": "#/definitions/PoolPriceConfig" + }, + "single_side_lp_limits": { + "$ref": "#/definitions/SingleSideLpLimits" + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "LpConfig": { + "type": "object", + "required": [ + "asset_data", + "expected_pool_ratio_range", + "pair_type", + "pool_address", + "single_side_lp_limits" + ], + "properties": { + "asset_data": { + "description": "denoms of both parties", + "allOf": [ + { + "$ref": "#/definitions/AssetData" + } + ] + }, + "expected_pool_ratio_range": { + "description": "expected price range", + "allOf": [ + { + "$ref": "#/definitions/DecimalRange" + } + ] + }, + "pair_type": { + "description": "pair type specified in the covenant", + "allOf": [ + { + "$ref": "#/definitions/PairType" + } + ] + }, + "pool_address": { + "description": "address of the liquidity pool we plan to enter", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "single_side_lp_limits": { + "description": "amounts of both tokens we consider ok to single-side lp", + "allOf": [ + { + "$ref": "#/definitions/SingleSideLpLimits" + } + ] + }, + "slippage_tolerance": { + "description": "slippage tolerance parameter for liquidity provisioning", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "MigrateMsg": { + "oneOf": [ + { + "description": "Pauses the clock. No `ExecuteMsg` messages will be executable until the clock is unpaused. Callable only if the clock is unpaused.", + "type": "object", + "required": [ + "pause" + ], + "properties": { + "pause": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Unpauses the clock. Callable only if the clock is paused.", + "type": "object", + "required": [ + "unpause" + ], + "properties": { + "unpause": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Updates the max gas allowed to be consumed by a tick. This should be no larger than 100_000 less the block max gas so as to save enough gas to process the tick's error.", + "type": "object", + "required": [ + "update_tick_max_gas" + ], + "properties": { + "update_tick_max_gas": { + "type": "object", + "required": [ + "new_value" + ], + "properties": { + "new_value": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "manage_whitelist" + ], + "properties": { + "manage_whitelist": { + "type": "object", + "properties": { + "add": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "remove": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg2": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "covenant_config": { + "anyOf": [ + { + "$ref": "#/definitions/TwoPartyPolCovenantConfig" + }, + { + "type": "null" + } + ] + }, + "denom_splits": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } + }, + "deposit_deadline": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "emergency_committee": { + "type": [ + "string", + "null" + ] + }, + "fallback_split": { + "anyOf": [ + { + "$ref": "#/definitions/SplitConfig" + }, + { + "type": "null" + } + ] + }, + "lockup_config": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "next_contract": { + "type": [ + "string", + "null" + ] + }, + "ragequit_config": { + "anyOf": [ + { + "$ref": "#/definitions/RagequitConfig" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg3": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "holder_address": { + "type": [ + "string", + "null" + ] + }, + "ibc_config": { + "anyOf": [ + { + "$ref": "#/definitions/IbcConfig" + }, + { + "type": "null" + } + ] + }, + "lp_config": { + "anyOf": [ + { + "$ref": "#/definitions/LiquidityProvisionConfig" + }, + { + "type": "null" + } + ] + }, + "note_address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg4": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "holder_address": { + "type": [ + "string", + "null" + ] + }, + "lp_config": { + "anyOf": [ + { + "$ref": "#/definitions/LpConfig" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg5": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "destination_config": { + "anyOf": [ + { + "$ref": "#/definitions/DestinationConfig" + }, + { + "type": "null" + } + ] + }, + "target_denoms": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg6": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "receiver_address": { + "type": [ + "string", + "null" + ] + }, + "target_denoms": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "MigrateMsg7": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "fallback_address": { + "anyOf": [ + { + "$ref": "#/definitions/FallbackAddressUpdateConfig" + }, + { + "type": "null" + } + ] + }, + "next_contract": { + "type": [ + "string", + "null" + ] + }, + "remote_chain_info": { + "anyOf": [ + { + "$ref": "#/definitions/RemoteChainInfo" + }, + { + "type": "null" + } + ] + }, + "transfer_amount": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "PacketForwardMiddlewareConfig": { + "type": "object", + "required": [ + "hop_chain_receiver_address", + "hop_to_destination_chain_channel_id", + "local_to_hop_chain_channel_id" + ], + "properties": { + "hop_chain_receiver_address": { + "type": "string" + }, + "hop_to_destination_chain_channel_id": { + "type": "string" + }, + "local_to_hop_chain_channel_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PairType": { + "description": "This enum describes available pair types. ## Available pool types ``` # use astroport::factory::PairType::{Custom, Stable, Xyk}; Xyk {}; Stable {}; Custom(String::from(\"Custom\")); ```", + "oneOf": [ + { + "description": "XYK pair type", + "type": "object", + "required": [ + "xyk" + ], + "properties": { + "xyk": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Stable pair type", + "type": "object", + "required": [ + "stable" + ], + "properties": { + "stable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Custom pair type", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "PartyChainInfo": { + "type": "object", + "required": [ + "ibc_timeout", + "neutron_to_party_chain_channel", + "party_chain_to_neutron_channel" + ], + "properties": { + "ibc_timeout": { + "$ref": "#/definitions/Uint64" + }, + "inwards_pfm": { + "description": "pfm configuration used to route funds from osmosis to local chain via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "neutron_to_party_chain_channel": { + "description": "channel id to route funds from local chain to party chain", + "type": "string" + }, + "outwards_pfm": { + "description": "pfm configuration used to route funds from local chain to osmosis via origin chain", + "anyOf": [ + { + "$ref": "#/definitions/ForwardMetadata" + }, + { + "type": "null" + } + ] + }, + "party_chain_to_neutron_channel": { + "description": "channel id to route funds from party chain to local chain", + "type": "string" + } + }, + "additionalProperties": false + }, + "PartyDenomInfo": { + "type": "object", + "required": [ + "local_denom", + "osmosis_coin" + ], + "properties": { + "local_denom": { + "description": "ibc denom on liquid pooler chain", + "type": "string" + }, + "osmosis_coin": { + "description": "coin as denominated on osmosis", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false + }, + "PoolPriceConfig": { + "description": "config for the pool price expectations upon covenant instantiation", + "type": "object", + "required": [ + "acceptable_price_spread", + "expected_spot_price" + ], + "properties": { + "acceptable_price_spread": { + "$ref": "#/definitions/Decimal" + }, + "expected_spot_price": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + }, + "RagequitConfig": { + "oneOf": [ + { + "description": "ragequit is disabled", + "type": "string", + "enum": [ + "disabled" + ] + }, + { + "description": "ragequit is enabled with `RagequitTerms`", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "$ref": "#/definitions/RagequitTerms" + } + }, + "additionalProperties": false + } + ] + }, + "RagequitState": { + "type": "object", + "required": [ + "coins", + "rq_party" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "rq_party": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + } + }, + "additionalProperties": false + }, + "RagequitTerms": { + "type": "object", + "required": [ + "penalty" + ], + "properties": { + "penalty": { + "description": "decimal based penalty to be applied on a party for initiating ragequit. Must be in the range of (0.00, 1.00). Also must not exceed either party allocations in raw values.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "state": { + "description": "optional rq state. none indicates no ragequit. some holds the ragequit related config", + "anyOf": [ + { + "$ref": "#/definitions/RagequitState" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "RemoteChainInfo": { + "type": "object", + "required": [ + "channel_id", + "connection_id", + "denom", + "ibc_transfer_timeout", + "ica_timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "connection_id": { + "description": "connection id from neutron to the remote chain on which we wish to open an ICA", + "type": "string" + }, + "denom": { + "type": "string" + }, + "ibc_transfer_timeout": { + "$ref": "#/definitions/Uint64" + }, + "ica_timeout": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + }, + "RouterMigrateMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "interchain" + ], + "properties": { + "interchain": { + "$ref": "#/definitions/MigrateMsg5" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "$ref": "#/definitions/MigrateMsg6" + } + }, + "additionalProperties": false + } + ] + }, + "SingleSideLpLimits": { + "description": "single side lp limits define the highest amount (in `Uint128`) that we consider acceptable to provide single-sided. if asset balance exceeds these limits, double-sided liquidity should be provided.", + "type": "object", + "required": [ + "asset_a_limit", + "asset_b_limit" + ], + "properties": { + "asset_a_limit": { + "$ref": "#/definitions/Uint128" + }, + "asset_b_limit": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "TwoPartyPolCovenantConfig": { + "type": "object", + "required": [ + "covenant_type", + "party_a", + "party_b" + ], + "properties": { + "covenant_type": { + "$ref": "#/definitions/CovenantType" + }, + "party_a": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + }, + "party_b": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + } + }, + "additionalProperties": false + }, + "TwoPartyPolCovenantParty": { + "type": "object", + "required": [ + "allocation", + "contribution", + "controller_addr", + "host_addr", + "router" + ], + "properties": { + "allocation": { + "description": "fraction of the entire LP position owned by the party. upon exiting it becomes 0.00. if counterparty exits, this would become 1.00, meaning that this party owns the entire position managed by the covenant.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "contribution": { + "description": "the `denom` and `amount` (`Uint128`) to be contributed by the party", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "controller_addr": { + "description": "address of the party on the controller chain (final receiver)", + "type": "string" + }, + "host_addr": { + "description": "neutron address authorized by the party to perform claims/ragequits", + "type": "string" + }, + "router": { + "description": "address of the interchain router associated with this party", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "sudo": null, + "responses": { + "clock_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "contract_codes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CovenantContractCodes", + "type": "object", + "required": [ + "clock", + "holder", + "liquid_pooler", + "party_a_forwarder", + "party_a_router", + "party_b_forwarder", + "party_b_router" + ], + "properties": { + "clock": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "holder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "liquid_pooler": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_a_forwarder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_a_router": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_b_forwarder": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "party_b_router": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "holder_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ibc_forwarder_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "interchain_router_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "liquid_pooler_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "party_deposit_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/two-party-pol-covenant/src/bin/schema.rs b/contracts/two-party-pol-covenant/src/bin/schema.rs new file mode 100644 index 00000000..855c3473 --- /dev/null +++ b/contracts/two-party-pol-covenant/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use valence_covenant_two_party_pol::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/two-party-pol-covenant/src/contract.rs b/contracts/two-party-pol-covenant/src/contract.rs new file mode 100644 index 00000000..ac0e024c --- /dev/null +++ b/contracts/two-party-pol-covenant/src/contract.rs @@ -0,0 +1,447 @@ +use std::collections::BTreeSet; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, Binary, CanonicalAddr, Deps, DepsMut, Env, MessageInfo, Response, StdError, + StdResult, WasmMsg, +}; +use covenant_utils::{instantiate2_helper::get_instantiate2_salt_and_address, split::remap_splits}; +use cw2::set_contract_version; +use valence_ibc_forwarder::msg::InstantiateMsg as IbcForwarderInstantiateMsg; +use valence_two_party_pol_holder::msg::{RagequitConfig, TwoPartyPolCovenantConfig}; + +use crate::{ + error::ContractError, + msg::{ + CovenantPartyConfig, InstantiateMsg, LiquidPoolerMigrateMsg, MigrateMsg, QueryMsg, + RouterMigrateMsg, + }, + state::{ + CONTRACT_CODES, COVENANT_CLOCK_ADDR, COVENANT_POL_HOLDER_ADDR, LIQUID_POOLER_ADDR, + PARTY_A_IBC_FORWARDER_ADDR, PARTY_A_ROUTER_ADDR, PARTY_B_IBC_FORWARDER_ADDR, + PARTY_B_ROUTER_ADDR, + }, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub const CLOCK_SALT: &[u8] = b"clock"; +pub const PARTY_A_ROUTER_SALT: &[u8] = b"router_a"; +pub const PARTY_B_ROUTER_SALT: &[u8] = b"router_b"; +pub const HOLDER_SALT: &[u8] = b"pol_holder"; +pub const PARTY_A_FORWARDER_SALT: &[u8] = b"forwarder_a"; +pub const PARTY_B_FORWARDER_SALT: &[u8] = b"forwarder_b"; +pub const LIQUID_POOLER_SALT: &[u8] = b"liquid_pooler"; + +#[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)?; + let mut resp = Response::default().add_attribute("method", "instantiate"); + let creator_address: CanonicalAddr = + deps.api.addr_canonicalize(env.contract.address.as_str())?; + + let covenant_denoms: BTreeSet = msg.splits.keys().map(|k| k.to_string()).collect(); + + let clock_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + CLOCK_SALT, + &creator_address, + msg.contract_codes.clock_code, + )?; + let party_a_router_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + PARTY_A_ROUTER_SALT, + &creator_address, + msg.party_a_config.get_router_code_id(&msg.contract_codes), + )?; + let party_b_router_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + PARTY_B_ROUTER_SALT, + &creator_address, + msg.party_b_config.get_router_code_id(&msg.contract_codes), + )?; + let holder_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + HOLDER_SALT, + &creator_address, + msg.contract_codes.holder_code, + )?; + let liquid_pooler_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + LIQUID_POOLER_SALT, + &creator_address, + msg.contract_codes.liquid_pooler_code, + )?; + + let mut clock_whitelist: Vec = Vec::with_capacity(6); + clock_whitelist.push(holder_instantiate2_config.addr.to_string()); + clock_whitelist.push(party_a_router_instantiate2_config.addr.to_string()); + clock_whitelist.push(party_b_router_instantiate2_config.addr.to_string()); + clock_whitelist.push(liquid_pooler_instantiate2_config.addr.to_string()); + + let holder_instantiate2_msg = valence_two_party_pol_holder::msg::InstantiateMsg { + clock_address: clock_instantiate2_config.addr.to_string(), + lockup_config: msg.lockup_config, + next_contract: liquid_pooler_instantiate2_config.addr.to_string(), + ragequit_config: msg.ragequit_config.unwrap_or(RagequitConfig::Disabled), + deposit_deadline: msg.deposit_deadline, + splits: remap_splits( + msg.splits, + ( + msg.party_a_config.get_final_receiver_address(), + party_a_router_instantiate2_config.addr.to_string(), + ), + ( + msg.party_b_config.get_final_receiver_address(), + party_b_router_instantiate2_config.addr.to_string(), + ), + )?, + fallback_split: match msg.fallback_split { + Some(config) => Some(config.remap_receivers_to_routers( + msg.party_a_config.get_final_receiver_address(), + party_a_router_instantiate2_config.addr.to_string(), + msg.party_b_config.get_final_receiver_address(), + party_b_router_instantiate2_config.addr.to_string(), + )?), + None => None, + }, + covenant_config: TwoPartyPolCovenantConfig { + party_a: msg.party_a_config.to_two_party_pol_party( + msg.party_a_share, + party_a_router_instantiate2_config.addr.to_string(), + ), + party_b: msg.party_b_config.to_two_party_pol_party( + msg.party_b_share, + party_b_router_instantiate2_config.addr.to_string(), + ), + covenant_type: msg.covenant_type.clone(), + }, + emergency_committee_addr: msg.emergency_committee, + } + .to_instantiate2_msg( + &holder_instantiate2_config, + env.contract.address.to_string(), + format!("{}_holder", msg.label), + )?; + + let party_a_router_instantiate2_msg = msg.party_a_config.to_router_instantiate2_msg( + env.contract.address.to_string(), + clock_instantiate2_config.addr.clone(), + format!("{}_party_a_router", msg.label), + covenant_denoms.clone(), + party_a_router_instantiate2_config.clone(), + )?; + + let party_b_router_instantiate2_msg = msg.party_b_config.to_router_instantiate2_msg( + env.contract.address.to_string(), + clock_instantiate2_config.addr.clone(), + format!("{}_party_b_router", msg.label), + covenant_denoms.clone(), + party_b_router_instantiate2_config.clone(), + )?; + + let liquid_pooler_instantiate2_msg = msg.liquid_pooler_config.to_instantiate2_msg( + &liquid_pooler_instantiate2_config, + env.contract.address.to_string(), + format!("{}_liquid_pooler", msg.label), + clock_instantiate2_config.addr.to_string(), + holder_instantiate2_config.addr.to_string(), + msg.pool_price_config, + )?; + + let mut messages = vec![ + holder_instantiate2_msg, + party_a_router_instantiate2_msg, + party_b_router_instantiate2_msg, + liquid_pooler_instantiate2_msg, + ]; + + if let CovenantPartyConfig::Interchain(config) = &msg.party_a_config { + let party_a_forwarder_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + PARTY_A_FORWARDER_SALT, + &creator_address, + msg.contract_codes.ibc_forwarder_code, + )?; + PARTY_A_IBC_FORWARDER_ADDR + .save(deps.storage, &party_a_forwarder_instantiate2_config.addr)?; + clock_whitelist.push(party_a_forwarder_instantiate2_config.addr.to_string()); + let instantiate_msg = IbcForwarderInstantiateMsg { + clock_address: clock_instantiate2_config.addr.to_string(), + next_contract: holder_instantiate2_config.addr.to_string(), + remote_chain_connection_id: config.party_chain_connection_id.to_string(), + remote_chain_channel_id: config.party_to_host_chain_channel_id.to_string(), + denom: config.remote_chain_denom.to_string(), + amount: config.contribution.amount, + ica_timeout: msg.timeouts.ica_timeout, + ibc_transfer_timeout: msg.timeouts.ibc_transfer_timeout, + fallback_address: msg.fallback_address.clone(), + }; + + messages.push(instantiate_msg.to_instantiate2_msg( + &party_a_forwarder_instantiate2_config, + env.contract.address.to_string(), + format!("{}_party_a_ibc_forwarder", msg.label), + )?); + resp = resp.add_attribute( + "party_a_forwarder_addr", + party_a_forwarder_instantiate2_config.addr, + ); + } + + if let CovenantPartyConfig::Interchain(config) = &msg.party_b_config { + let party_b_forwarder_instantiate2_config = get_instantiate2_salt_and_address( + deps.as_ref(), + PARTY_B_FORWARDER_SALT, + &creator_address, + msg.contract_codes.ibc_forwarder_code, + )?; + PARTY_B_IBC_FORWARDER_ADDR + .save(deps.storage, &party_b_forwarder_instantiate2_config.addr)?; + clock_whitelist.push(party_b_forwarder_instantiate2_config.addr.to_string()); + let instantiate_msg = IbcForwarderInstantiateMsg { + clock_address: clock_instantiate2_config.addr.to_string(), + next_contract: holder_instantiate2_config.addr.to_string(), + remote_chain_connection_id: config.party_chain_connection_id.to_string(), + remote_chain_channel_id: config.party_to_host_chain_channel_id.to_string(), + denom: config.remote_chain_denom.to_string(), + amount: config.contribution.amount, + ica_timeout: msg.timeouts.ica_timeout, + ibc_transfer_timeout: msg.timeouts.ibc_transfer_timeout, + fallback_address: msg.fallback_address, + }; + + messages.push(instantiate_msg.to_instantiate2_msg( + &party_b_forwarder_instantiate2_config, + env.contract.address.to_string(), + format!("{}_party_b_ibc_forwarder", msg.label), + )?); + resp = resp.add_attribute( + "party_b_forwarder_addr", + party_b_forwarder_instantiate2_config.addr, + ); + } + + let clock_instantiate2_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: msg.clock_tick_max_gas, + whitelist: clock_whitelist, + } + .to_instantiate2_msg( + clock_instantiate2_config.code, + clock_instantiate2_config.salt, + env.contract.address.to_string(), + format!("{}-clock", msg.label), + )?; + messages.insert(0, clock_instantiate2_msg); + + CONTRACT_CODES.save( + deps.storage, + &msg.contract_codes.to_covenant_codes_config( + party_a_router_instantiate2_config.code, + party_b_router_instantiate2_config.code, + ), + )?; + COVENANT_POL_HOLDER_ADDR.save(deps.storage, &holder_instantiate2_config.addr)?; + LIQUID_POOLER_ADDR.save(deps.storage, &liquid_pooler_instantiate2_config.addr)?; + PARTY_B_ROUTER_ADDR.save(deps.storage, &party_b_router_instantiate2_config.addr)?; + PARTY_A_ROUTER_ADDR.save(deps.storage, &party_a_router_instantiate2_config.addr)?; + COVENANT_CLOCK_ADDR.save(deps.storage, &clock_instantiate2_config.addr)?; + + Ok(resp + .add_attribute("clock_addr", clock_instantiate2_config.addr) + .add_attribute("liquid_pooler_addr", liquid_pooler_instantiate2_config.addr) + .add_attribute( + "party_a_router_addr", + party_a_router_instantiate2_config.addr, + ) + .add_attribute( + "party_b_router_addr", + party_b_router_instantiate2_config.addr, + ) + .add_attribute("holder_addr", holder_instantiate2_config.addr) + .add_messages(messages)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ClockAddress {} => Ok(to_json_binary( + &COVENANT_CLOCK_ADDR.may_load(deps.storage)?, + )?), + QueryMsg::HolderAddress {} => Ok(to_json_binary( + &COVENANT_POL_HOLDER_ADDR.may_load(deps.storage)?, + )?), + QueryMsg::IbcForwarderAddress { party } => { + let resp = if party == "party_a" { + PARTY_A_IBC_FORWARDER_ADDR.may_load(deps.storage)? + } else if party == "party_b" { + PARTY_B_IBC_FORWARDER_ADDR.may_load(deps.storage)? + } else { + return Err(StdError::not_found("not found")); + }; + Ok(to_json_binary(&resp)?) + } + QueryMsg::InterchainRouterAddress { party } => { + let resp = if party == "party_a" { + PARTY_A_ROUTER_ADDR.may_load(deps.storage)? + } else if party == "party_b" { + PARTY_B_ROUTER_ADDR.may_load(deps.storage)? + } else { + return Err(StdError::not_found("not found")); + }; + Ok(to_json_binary(&resp)?) + } + QueryMsg::LiquidPoolerAddress {} => { + Ok(to_json_binary(&LIQUID_POOLER_ADDR.may_load(deps.storage)?)?) + } + QueryMsg::PartyDepositAddress { party } => { + // here depending on the party we query their ibc forwarder. + // if it's present, we then query it for a deposit address + // which should return the address of ICA on a remote chain. + // if no ibc forwarder is saved, we return the holder. + let resp = if party == "party_a" { + match PARTY_A_IBC_FORWARDER_ADDR.may_load(deps.storage)? { + Some(addr) => deps.querier.query_wasm_smart( + addr, + &covenant_utils::neutron::QueryMsg::DepositAddress {}, + )?, + None => COVENANT_POL_HOLDER_ADDR.may_load(deps.storage)?, + } + } else if party == "party_b" { + match PARTY_B_IBC_FORWARDER_ADDR.may_load(deps.storage)? { + Some(addr) => deps.querier.query_wasm_smart( + addr, + &covenant_utils::neutron::QueryMsg::DepositAddress {}, + )?, + None => COVENANT_POL_HOLDER_ADDR.may_load(deps.storage)?, + } + } else { + return Err(StdError::not_found("not found")); + }; + Ok(to_json_binary(&resp)?) + } + QueryMsg::ContractCodes {} => Ok(to_json_binary(&CONTRACT_CODES.load(deps.storage)?)?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult { + match msg { + MigrateMsg::UpdateCovenant { + codes, + clock, + holder, + liquid_pooler, + party_a_router, + party_b_router, + party_a_forwarder, + party_b_forwarder, + } => { + let mut migrate_msgs = vec![]; + let mut resp = Response::default().add_attribute("method", "migrate_contracts"); + + if let Some(new_codes) = codes { + CONTRACT_CODES.save(deps.storage, &new_codes)?; + let code_binary = to_json_binary(&new_codes)?; + resp = resp.add_attribute("contract_codes_migrate", code_binary.to_base64()); + } + + let contract_codes = CONTRACT_CODES.load(deps.storage)?; + + if let Some(clock) = clock { + let msg = to_json_binary(&clock)?; + resp = resp.add_attribute("clock_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: COVENANT_CLOCK_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.clock, + msg, + }); + } + + if let Some(router_migrate_msg) = party_a_router { + let msg: Binary = match router_migrate_msg { + RouterMigrateMsg::Interchain(msg) => to_json_binary(&msg)?, + RouterMigrateMsg::Native(msg) => to_json_binary(&msg)?, + }; + resp = resp.add_attribute("party_a_router_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: PARTY_A_ROUTER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.party_a_router, + msg, + }); + } + + if let Some(router_migrate_msg) = party_b_router { + let msg: Binary = match router_migrate_msg { + RouterMigrateMsg::Interchain(msg) => to_json_binary(&msg)?, + RouterMigrateMsg::Native(msg) => to_json_binary(&msg)?, + }; + resp = resp.add_attribute("party_b_router_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: PARTY_B_ROUTER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.party_b_router, + msg, + }); + } + + if let Some(forwarder) = party_a_forwarder { + let msg: Binary = to_json_binary(&forwarder)?; + resp = resp.add_attribute("party_a_forwarder_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: PARTY_A_IBC_FORWARDER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.party_a_forwarder, + msg, + }); + } + + if let Some(forwarder) = party_b_forwarder { + let msg: Binary = to_json_binary(&forwarder)?; + resp = resp.add_attribute("party_b_forwarder_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: PARTY_B_IBC_FORWARDER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.party_b_forwarder, + msg, + }); + } + + if let Some(holder) = holder { + let msg: Binary = to_json_binary(&holder)?; + resp = resp.add_attribute("holder_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: COVENANT_POL_HOLDER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.holder, + msg, + }); + } + + if let Some(liquid_pooler_migrate_msg) = liquid_pooler { + let msg: Binary = match liquid_pooler_migrate_msg { + LiquidPoolerMigrateMsg::Astroport(msg) => to_json_binary(&msg)?, + LiquidPoolerMigrateMsg::Osmosis(msg) => to_json_binary(&msg)?, + }; + + resp = resp.add_attribute("liquid_pooler_migrate", msg.to_base64()); + migrate_msgs.push(WasmMsg::Migrate { + contract_addr: LIQUID_POOLER_ADDR.load(deps.storage)?.to_string(), + new_code_id: contract_codes.liquid_pooler, + msg, + }); + } + + Ok(resp.add_messages(migrate_msgs)) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} diff --git a/contracts/two-party-pol-covenant/src/error.rs b/contracts/two-party-pol-covenant/src/error.rs new file mode 100644 index 00000000..2b7b2d21 --- /dev/null +++ b/contracts/two-party-pol-covenant/src/error.rs @@ -0,0 +1,27 @@ +use cosmwasm_std::{Instantiate2AddressError, StdError}; +use cw_utils::ParseReplyError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Unknown reply id")] + UnknownReplyId {}, + + #[error("SubMsg reply error")] + ReplyError { err: String }, + + #[error("Failed to instantiate {contract:?} contract")] + ContractInstantiationError { + contract: String, + err: ParseReplyError, + }, + + #[error("{0}")] + InstantiationError(#[from] Instantiate2AddressError), +} diff --git a/contracts/two-party-pol-covenant/src/lib.rs b/contracts/two-party-pol-covenant/src/lib.rs new file mode 100644 index 00000000..47bc573c --- /dev/null +++ b/contracts/two-party-pol-covenant/src/lib.rs @@ -0,0 +1,6 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/two-party-pol-covenant/src/msg.rs b/contracts/two-party-pol-covenant/src/msg.rs new file mode 100644 index 00000000..e72d3269 --- /dev/null +++ b/contracts/two-party-pol-covenant/src/msg.rs @@ -0,0 +1,301 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{coin, Addr, Binary, Decimal, StdResult, Uint64, WasmMsg}; +use covenant_utils::{ + instantiate2_helper::Instantiate2HelperConfig, split::SplitConfig, CovenantParty, + DestinationConfig, InterchainCovenantParty, NativeCovenantParty, PoolPriceConfig, + ReceiverConfig, +}; +use cw_utils::Expiration; +use valence_astroport_liquid_pooler::msg::AstroportLiquidPoolerConfig; +use valence_osmo_liquid_pooler::msg::OsmosisLiquidPoolerConfig; +use valence_two_party_pol_holder::msg::{CovenantType, RagequitConfig, TwoPartyPolCovenantParty}; + +pub const DEFAULT_TIMEOUT: u64 = 60 * 60 * 5; // 5 hours + +#[cw_serde] +pub struct InstantiateMsg { + pub label: String, + pub timeouts: Timeouts, + pub contract_codes: CovenantContractCodeIds, + pub clock_tick_max_gas: Option, + pub lockup_config: Expiration, + pub party_a_config: CovenantPartyConfig, + pub party_b_config: CovenantPartyConfig, + pub covenant_type: CovenantType, + pub ragequit_config: Option, + pub deposit_deadline: Expiration, + pub party_a_share: Decimal, + pub party_b_share: Decimal, + pub pool_price_config: PoolPriceConfig, + pub splits: BTreeMap, + pub fallback_split: Option, + pub emergency_committee: Option, + pub liquid_pooler_config: LiquidPoolerConfig, + pub fallback_address: Option, +} + +#[cw_serde] +pub enum LiquidPoolerConfig { + Osmosis(Box), + Astroport(AstroportLiquidPoolerConfig), +} + +impl LiquidPoolerConfig { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + clock_addr: String, + holder_addr: String, + pool_price_config: PoolPriceConfig, + ) -> StdResult { + match self { + LiquidPoolerConfig::Osmosis(config) => Ok(config + .to_instantiate_msg( + clock_addr.to_string(), + holder_addr.to_string(), + pool_price_config, + ) + .to_instantiate2_msg(instantiate2_helper, admin, label)?), + LiquidPoolerConfig::Astroport(config) => Ok(config + .to_instantiate_msg( + clock_addr.to_string(), + holder_addr.to_string(), + pool_price_config, + ) + .to_instantiate2_msg(instantiate2_helper, admin, label)?), + } + } +} + +impl CovenantPartyConfig { + pub fn to_receiver_config(&self) -> ReceiverConfig { + match self { + CovenantPartyConfig::Interchain(config) => ReceiverConfig::Ibc(DestinationConfig { + local_to_destination_chain_channel_id: config + .host_to_party_chain_channel_id + .to_string(), + destination_receiver_addr: config.party_receiver_addr.to_string(), + ibc_transfer_timeout: config.ibc_transfer_timeout, + denom_to_pfm_map: config.denom_to_pfm_map.clone(), + }), + CovenantPartyConfig::Native(config) => { + ReceiverConfig::Native(config.party_receiver_addr.to_string()) + } + } + } + + pub fn get_final_receiver_address(&self) -> String { + match self { + CovenantPartyConfig::Interchain(config) => config.party_receiver_addr.to_string(), + CovenantPartyConfig::Native(config) => config.party_receiver_addr.to_string(), + } + } + + pub fn to_covenant_party(&self) -> CovenantParty { + match self { + CovenantPartyConfig::Interchain(config) => CovenantParty { + addr: config.addr.to_string(), + native_denom: config.native_denom.to_string(), + receiver_config: self.to_receiver_config(), + }, + CovenantPartyConfig::Native(config) => CovenantParty { + addr: config.addr.to_string(), + native_denom: config.native_denom.to_string(), + receiver_config: self.to_receiver_config(), + }, + } + } + + pub fn to_two_party_pol_party( + &self, + allocation: Decimal, + router: String, + ) -> TwoPartyPolCovenantParty { + match &self { + CovenantPartyConfig::Interchain(config) => TwoPartyPolCovenantParty { + contribution: coin( + config.contribution.amount.u128(), + config.native_denom.to_string(), + ), + host_addr: config.addr.to_string(), + controller_addr: config.party_receiver_addr.to_string(), + allocation, + router, + }, + CovenantPartyConfig::Native(config) => TwoPartyPolCovenantParty { + contribution: config.contribution.clone(), + host_addr: config.addr.to_string(), + controller_addr: config.party_receiver_addr.to_string(), + allocation, + router, + }, + } + } + + pub fn get_native_denom(&self) -> String { + match self { + CovenantPartyConfig::Interchain(config) => config.native_denom.to_string(), + CovenantPartyConfig::Native(config) => config.native_denom.to_string(), + } + } + + pub fn get_router_code_id(&self, contract_codes: &CovenantContractCodeIds) -> u64 { + match self { + CovenantPartyConfig::Native(_) => contract_codes.native_router_code, + CovenantPartyConfig::Interchain(_) => contract_codes.interchain_router_code, + } + } + + pub fn to_router_instantiate2_msg( + &self, + admin_addr: String, + clock_addr: Addr, + label: String, + denoms: BTreeSet, + instantiate2_helper: Instantiate2HelperConfig, + ) -> StdResult { + match self { + CovenantPartyConfig::Interchain(party) => { + let instantiate_msg = valence_interchain_router::msg::InstantiateMsg { + clock_address: clock_addr.to_string(), + destination_config: DestinationConfig { + local_to_destination_chain_channel_id: party + .host_to_party_chain_channel_id + .to_string(), + destination_receiver_addr: party.party_receiver_addr.to_string(), + ibc_transfer_timeout: party.ibc_transfer_timeout, + denom_to_pfm_map: party.denom_to_pfm_map.clone(), + }, + denoms, + }; + Ok(instantiate_msg.to_instantiate2_msg(&instantiate2_helper, admin_addr, label)?) + } + CovenantPartyConfig::Native(party) => { + let instantiate_msg = valence_native_router::msg::InstantiateMsg { + clock_address: clock_addr.to_string(), + receiver_address: party.party_receiver_addr.to_string(), + denoms, + }; + Ok(instantiate_msg.to_instantiate2_msg(&instantiate2_helper, admin_addr, label)?) + } + } + } +} + +#[cw_serde] +pub enum CovenantPartyConfig { + Interchain(InterchainCovenantParty), + Native(NativeCovenantParty), +} + +#[cw_serde] +pub struct CovenantContractCodeIds { + pub ibc_forwarder_code: u64, + pub holder_code: u64, + pub clock_code: u64, + pub interchain_router_code: u64, + pub native_router_code: u64, + pub liquid_pooler_code: u64, +} + +#[cw_serde] +pub struct CovenantContractCodes { + pub clock: u64, + pub holder: u64, + pub liquid_pooler: u64, + pub party_a_router: u64, + pub party_b_router: u64, + pub party_a_forwarder: u64, + pub party_b_forwarder: u64, +} + +impl CovenantContractCodeIds { + pub(crate) fn to_covenant_codes_config( + &self, + party_a_router_code: u64, + party_b_router_code: u64, + ) -> CovenantContractCodes { + CovenantContractCodes { + clock: self.clock_code, + holder: self.holder_code, + liquid_pooler: self.liquid_pooler_code, + party_a_router: party_a_router_code, + party_b_router: party_b_router_code, + party_a_forwarder: self.ibc_forwarder_code, + party_b_forwarder: self.ibc_forwarder_code, + } + } +} + +#[cw_serde] +pub struct Timeouts { + /// ica timeout in seconds + pub ica_timeout: Uint64, + /// ibc transfer timeout in seconds + pub ibc_transfer_timeout: Uint64, +} + +impl Default for Timeouts { + fn default() -> Self { + Self { + ica_timeout: Uint64::new(DEFAULT_TIMEOUT), + ibc_transfer_timeout: Uint64::new(DEFAULT_TIMEOUT), + } + } +} + +#[cw_serde] +pub enum ExecuteMsg {} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Addr)] + ClockAddress {}, + #[returns(Addr)] + HolderAddress {}, + #[returns(Addr)] + IbcForwarderAddress { party: String }, + #[returns(Addr)] + InterchainRouterAddress { party: String }, + #[returns(Addr)] + LiquidPoolerAddress {}, + #[returns(Addr)] + PartyDepositAddress { party: String }, + #[returns(CovenantContractCodes)] + ContractCodes {}, +} + +#[allow(clippy::large_enum_variant)] +#[cw_serde] +pub enum MigrateMsg { + UpdateCovenant { + codes: Option, + clock: Option, + holder: Option, + liquid_pooler: Option, + party_a_router: Option, + party_b_router: Option, + party_a_forwarder: Option, + party_b_forwarder: Option, + }, + UpdateCodeId { + data: Option, + }, +} + +#[cw_serde] +pub enum LiquidPoolerMigrateMsg { + Osmosis(valence_osmo_liquid_pooler::msg::MigrateMsg), + Astroport(valence_astroport_liquid_pooler::msg::MigrateMsg), +} + +#[cw_serde] +pub enum RouterMigrateMsg { + Interchain(valence_interchain_router::msg::MigrateMsg), + Native(valence_native_router::msg::MigrateMsg), +} diff --git a/contracts/two-party-pol-covenant/src/state.rs b/contracts/two-party-pol-covenant/src/state.rs new file mode 100644 index 00000000..b3cb0c9d --- /dev/null +++ b/contracts/two-party-pol-covenant/src/state.rs @@ -0,0 +1,13 @@ +use crate::msg::CovenantContractCodes; +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +pub const COVENANT_CLOCK_ADDR: Item = Item::new("covenant_clock_addr"); +pub const COVENANT_POL_HOLDER_ADDR: Item = Item::new("covenant_two_party_pol_holder_addr"); +pub const PARTY_A_IBC_FORWARDER_ADDR: Item = Item::new("party_a_ibc_forwarder_addr"); +pub const PARTY_B_IBC_FORWARDER_ADDR: Item = Item::new("party_b_ibc_forwarder_addr"); +pub const PARTY_A_ROUTER_ADDR: Item = Item::new("party_a_router_addr"); +pub const PARTY_B_ROUTER_ADDR: Item = Item::new("party_b_router_addr"); +pub const LIQUID_POOLER_ADDR: Item = Item::new("liquid_pooler_addr"); + +pub(crate) const CONTRACT_CODES: Item = Item::new("contract_codes"); diff --git a/contracts/two-party-pol-holder/.cargo/config b/contracts/two-party-pol-holder/.cargo/config new file mode 100644 index 00000000..6a6f2852 --- /dev/null +++ b/contracts/two-party-pol-holder/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" \ No newline at end of file diff --git a/contracts/two-party-pol-holder/Cargo.toml b/contracts/two-party-pol-holder/Cargo.toml new file mode 100644 index 00000000..3b608787 --- /dev/null +++ b/contracts/two-party-pol-holder/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "valence-two-party-pol-holder" +authors = ["benskey bekauz@protonmail.com"] +description = "Two party POL holder module for covenants" +edition = { workspace = true } +license = { workspace = true } +# rust-version = { workspace = true } +version = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# disables #[entry_point] (i.e. instantiate/execute/query) export +library = [] + +[dependencies] +covenant-macros = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +valence-clock = { workspace = true, features = ["library"] } +cw2 = { workspace = true } +thiserror = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +astroport = { workspace = true } +cw20 = { workspace = true } +cw-utils = { workspace = true } +covenant-utils = { workspace = true } diff --git a/contracts/two-party-pol-holder/README.md b/contracts/two-party-pol-holder/README.md new file mode 100644 index 00000000..ea5bb7f6 --- /dev/null +++ b/contracts/two-party-pol-holder/README.md @@ -0,0 +1,59 @@ +# Two party POL holder + +## Responsibilities + +### Multiple parties + +Multiple parties are going to be participating, so the holder should store a list of whitelisted addresses. + +### Lock Period + +A `Lock` duration should be stored to keep track of the covenant duration. + +After the `Lock` period expires, both parties are allowed to submit `Claim` messages. +A successful claim results in the claiming party's liquidity portion being withdrawn from the +pool, and forwarding the underlying assets to the respective router module. + +### Ragequit + +A ragequit functionality should be enabled for both parties that may wish to break their part of the covenant. +Ragequitting party is subject to a percentage based penalty agreed upon instantiation. + +Holder then withdraws the allocation of the ragequitting party (minus the penalty) and forwards the funds to the party. +Counterparty remains in an active position. + +Ragequit breaks the regular covenant flow in the following way: + +- covenant is no longer subject to expiration +- splitter module no longer gets instantiated, meaning that any pre-agreed upon token distribution split is void + - both parties receive a 50/50 split of the underlying denoms + +### Deposit funds to Liquid Pooler + +Both parties should deposit their funds to holder. After holder asserts the expected balances, it forwards +the funds to the Liquid Pooler which then in turn enters into a position. + +Deposit stage is subject to a deposit deadline (`Expiration`). +Once the deposit deadline expires, refunds are issued to parties that delivered their parts of the covenant. +This can happen if any of the counterparties do not deliver the funds before the deadline expires, as holder attempts to send all expected funds in a combined `BankSend`. + +## Flow + +After instantiation, holder sits in `Instantiated` state and awaits for both parties to deposit funds. + +- Once both deposits are received, holder forwards the funds to the next contract and advances the state to `Active`. +- If one of the parties do deposit their part of the funds, but their counterparty does not, refund is initiated. This happens by sending the deposited funds to the respective interchain-router which then takes care of the rest. + +`Active` state is a prerequisite for initiating a `Ragequit`. In case of a ragequit, usual covenant flow is broken: + +- The initiating party forfeits part of its funds to the other party. +- After withdrawing the ragequitting party funds, holder forwards them to the respective interchain-router contract. +- Other party is no longer subject to the notion of expiry date. + - It is free to submit a `Claim` which will remove the remaining liquidity and send the underlying funds to the interchain-router. + +After holder no longer manages any funds, it advances its state to `Complete`. + +Any ticks received while holder is `Active` will trigger a check for expiration. + +If covenant is expired, holder state is advanced to `Expired`. +Both parties are free to submit `Claim` messages to the holder. diff --git a/contracts/two-party-pol-holder/schema/valence-two-party-pol-holder.json b/contracts/two-party-pol-holder/schema/valence-two-party-pol-holder.json new file mode 100644 index 00000000..c35f9a13 --- /dev/null +++ b/contracts/two-party-pol-holder/schema/valence-two-party-pol-holder.json @@ -0,0 +1,1589 @@ +{ + "contract_name": "valence-two-party-pol-holder", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "clock_address", + "covenant_config", + "deposit_deadline", + "lockup_config", + "next_contract", + "ragequit_config", + "splits" + ], + "properties": { + "clock_address": { + "description": "address of authorized clock", + "type": "string" + }, + "covenant_config": { + "description": "config describing the covenant dynamics", + "allOf": [ + { + "$ref": "#/definitions/TwoPartyPolCovenantConfig" + } + ] + }, + "deposit_deadline": { + "description": "deadline for both parties to deposit their funds", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "emergency_committee_addr": { + "description": "address of the emergency committee", + "type": [ + "string", + "null" + ] + }, + "fallback_split": { + "description": "a split for all denoms that are not covered in the regular `splits` list", + "anyOf": [ + { + "$ref": "#/definitions/SplitConfig" + }, + { + "type": "null" + } + ] + }, + "lockup_config": { + "description": "config describing the agreed upon duration of POL", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "next_contract": { + "description": "liquid pooler address", + "type": "string" + }, + "ragequit_config": { + "description": "config describing early exit dynamics", + "allOf": [ + { + "$ref": "#/definitions/RagequitConfig" + } + ] + }, + "splits": { + "description": "mapping of denoms to their splits", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CovenantType": { + "type": "string", + "enum": [ + "share", + "side" + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "RagequitConfig": { + "oneOf": [ + { + "description": "ragequit is disabled", + "type": "string", + "enum": [ + "disabled" + ] + }, + { + "description": "ragequit is enabled with `RagequitTerms`", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "$ref": "#/definitions/RagequitTerms" + } + }, + "additionalProperties": false + } + ] + }, + "RagequitState": { + "type": "object", + "required": [ + "coins", + "rq_party" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "rq_party": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + } + }, + "additionalProperties": false + }, + "RagequitTerms": { + "type": "object", + "required": [ + "penalty" + ], + "properties": { + "penalty": { + "description": "decimal based penalty to be applied on a party for initiating ragequit. Must be in the range of (0.00, 1.00). Also must not exceed either party allocations in raw values.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "state": { + "description": "optional rq state. none indicates no ragequit. some holds the ragequit related config", + "anyOf": [ + { + "$ref": "#/definitions/RagequitState" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "TwoPartyPolCovenantConfig": { + "type": "object", + "required": [ + "covenant_type", + "party_a", + "party_b" + ], + "properties": { + "covenant_type": { + "$ref": "#/definitions/CovenantType" + }, + "party_a": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + }, + "party_b": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + } + }, + "additionalProperties": false + }, + "TwoPartyPolCovenantParty": { + "type": "object", + "required": [ + "allocation", + "contribution", + "controller_addr", + "host_addr", + "router" + ], + "properties": { + "allocation": { + "description": "fraction of the entire LP position owned by the party. upon exiting it becomes 0.00. if counterparty exits, this would become 1.00, meaning that this party owns the entire position managed by the covenant.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "contribution": { + "description": "the `denom` and `amount` (`Uint128`) to be contributed by the party", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "controller_addr": { + "description": "address of the party on the controller chain (final receiver)", + "type": "string" + }, + "host_addr": { + "description": "neutron address authorized by the party to perform claims/ragequits", + "type": "string" + }, + "router": { + "description": "address of the interchain router associated with this party", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "initiate the ragequit", + "type": "object", + "required": [ + "ragequit" + ], + "properties": { + "ragequit": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "withdraw the liquidity party is entitled to", + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "distribute any unspecified denoms", + "type": "object", + "required": [ + "distribute_fallback_split" + ], + "properties": { + "distribute_fallback_split": { + "type": "object", + "required": [ + "denoms" + ], + "properties": { + "denoms": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Wakes the state machine up. The caller should check the sender of the tick is the clock if they'd like to pause when the clock does.", + "type": "object", + "required": [ + "tick" + ], + "properties": { + "tick": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "After LPer finished withdrawing from LP, it sends the funds to the holder and the holder distributes them based on its logic Should only be called by the LPer of the covenant", + "type": "object", + "required": [ + "distribute" + ], + "properties": { + "distribute": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "This message is sent in case we do an IBC withdraw The withdraw can fail in async way, in case that happens we want the holder to be notified on that. In case of astroport, the withdraww + distribution is atomic, so nothing to worry there But in case of osmosis, the withdraw is async, so the \"claim\" will successful happen, while the withdraw can fail, in case the withdraw fails here, we execute this message on the holder", + "type": "object", + "required": [ + "withdraw_failed" + ], + "properties": { + "withdraw_failed": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allows for the emergency committee to withdraw the funds on case of an emergency", + "type": "object", + "required": [ + "emergency_withdraw" + ], + "properties": { + "emergency_withdraw": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "contract_state" + ], + "properties": { + "contract_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ragequit_config" + ], + "properties": { + "ragequit_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "lockup_config" + ], + "properties": { + "lockup_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config_party_a" + ], + "properties": { + "config_party_a": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config_party_b" + ], + "properties": { + "config_party_b": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "deposit_deadline" + ], + "properties": { + "deposit_deadline": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "denom_splits" + ], + "properties": { + "denom_splits": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "emergency_committee" + ], + "properties": { + "emergency_committee": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated clock address authorized to submit ticks", + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the associated remote chain information", + "type": "object", + "required": [ + "next_contract" + ], + "properties": { + "next_contract": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the address a contract expects to receive funds to", + "type": "object", + "required": [ + "deposit_address" + ], + "properties": { + "deposit_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "clock_addr": { + "type": [ + "string", + "null" + ] + }, + "covenant_config": { + "anyOf": [ + { + "$ref": "#/definitions/TwoPartyPolCovenantConfig" + }, + { + "type": "null" + } + ] + }, + "denom_splits": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } + }, + "deposit_deadline": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "emergency_committee": { + "type": [ + "string", + "null" + ] + }, + "fallback_split": { + "anyOf": [ + { + "$ref": "#/definitions/SplitConfig" + }, + { + "type": "null" + } + ] + }, + "lockup_config": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "next_contract": { + "type": [ + "string", + "null" + ] + }, + "ragequit_config": { + "anyOf": [ + { + "$ref": "#/definitions/RagequitConfig" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_code_id" + ], + "properties": { + "update_code_id": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CovenantType": { + "type": "string", + "enum": [ + "share", + "side" + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "RagequitConfig": { + "oneOf": [ + { + "description": "ragequit is disabled", + "type": "string", + "enum": [ + "disabled" + ] + }, + { + "description": "ragequit is enabled with `RagequitTerms`", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "$ref": "#/definitions/RagequitTerms" + } + }, + "additionalProperties": false + } + ] + }, + "RagequitState": { + "type": "object", + "required": [ + "coins", + "rq_party" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "rq_party": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + } + }, + "additionalProperties": false + }, + "RagequitTerms": { + "type": "object", + "required": [ + "penalty" + ], + "properties": { + "penalty": { + "description": "decimal based penalty to be applied on a party for initiating ragequit. Must be in the range of (0.00, 1.00). Also must not exceed either party allocations in raw values.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "state": { + "description": "optional rq state. none indicates no ragequit. some holds the ragequit related config", + "anyOf": [ + { + "$ref": "#/definitions/RagequitState" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "TwoPartyPolCovenantConfig": { + "type": "object", + "required": [ + "covenant_type", + "party_a", + "party_b" + ], + "properties": { + "covenant_type": { + "$ref": "#/definitions/CovenantType" + }, + "party_a": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + }, + "party_b": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + } + }, + "additionalProperties": false + }, + "TwoPartyPolCovenantParty": { + "type": "object", + "required": [ + "allocation", + "contribution", + "controller_addr", + "host_addr", + "router" + ], + "properties": { + "allocation": { + "description": "fraction of the entire LP position owned by the party. upon exiting it becomes 0.00. if counterparty exits, this would become 1.00, meaning that this party owns the entire position managed by the covenant.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "contribution": { + "description": "the `denom` and `amount` (`Uint128`) to be contributed by the party", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "controller_addr": { + "description": "address of the party on the controller chain (final receiver)", + "type": "string" + }, + "host_addr": { + "description": "neutron address authorized by the party to perform claims/ragequits", + "type": "string" + }, + "router": { + "description": "address of the interchain router associated with this party", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "sudo": null, + "responses": { + "clock_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TwoPartyPolCovenantConfig", + "type": "object", + "required": [ + "covenant_type", + "party_a", + "party_b" + ], + "properties": { + "covenant_type": { + "$ref": "#/definitions/CovenantType" + }, + "party_a": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + }, + "party_b": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CovenantType": { + "type": "string", + "enum": [ + "share", + "side" + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "TwoPartyPolCovenantParty": { + "type": "object", + "required": [ + "allocation", + "contribution", + "controller_addr", + "host_addr", + "router" + ], + "properties": { + "allocation": { + "description": "fraction of the entire LP position owned by the party. upon exiting it becomes 0.00. if counterparty exits, this would become 1.00, meaning that this party owns the entire position managed by the covenant.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "contribution": { + "description": "the `denom` and `amount` (`Uint128`) to be contributed by the party", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "controller_addr": { + "description": "address of the party on the controller chain (final receiver)", + "type": "string" + }, + "host_addr": { + "description": "neutron address authorized by the party to perform claims/ragequits", + "type": "string" + }, + "router": { + "description": "address of the interchain router associated with this party", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "config_party_a": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TwoPartyPolCovenantParty", + "type": "object", + "required": [ + "allocation", + "contribution", + "controller_addr", + "host_addr", + "router" + ], + "properties": { + "allocation": { + "description": "fraction of the entire LP position owned by the party. upon exiting it becomes 0.00. if counterparty exits, this would become 1.00, meaning that this party owns the entire position managed by the covenant.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "contribution": { + "description": "the `denom` and `amount` (`Uint128`) to be contributed by the party", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "controller_addr": { + "description": "address of the party on the controller chain (final receiver)", + "type": "string" + }, + "host_addr": { + "description": "neutron address authorized by the party to perform claims/ragequits", + "type": "string" + }, + "router": { + "description": "address of the interchain router associated with this party", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "config_party_b": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TwoPartyPolCovenantParty", + "type": "object", + "required": [ + "allocation", + "contribution", + "controller_addr", + "host_addr", + "router" + ], + "properties": { + "allocation": { + "description": "fraction of the entire LP position owned by the party. upon exiting it becomes 0.00. if counterparty exits, this would become 1.00, meaning that this party owns the entire position managed by the covenant.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "contribution": { + "description": "the `denom` and `amount` (`Uint128`) to be contributed by the party", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "controller_addr": { + "description": "address of the party on the controller chain (final receiver)", + "type": "string" + }, + "host_addr": { + "description": "neutron address authorized by the party to perform claims/ragequits", + "type": "string" + }, + "router": { + "description": "address of the interchain router associated with this party", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "contract_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractState", + "oneOf": [ + { + "description": "contract is instantiated and awaiting for deposits from both parties involved", + "type": "string", + "enum": [ + "instantiated" + ] + }, + { + "description": "funds have been forwarded to the LP module. from the perspective of this contract that indicates an active LP position.", + "type": "string", + "enum": [ + "active" + ] + }, + { + "description": "one of the parties have initiated ragequit. the remaining counterparty with an active position is free to exit at any time.", + "type": "string", + "enum": [ + "ragequit" + ] + }, + { + "description": "covenant has reached its expiration date.", + "type": "string", + "enum": [ + "expired" + ] + }, + { + "description": "underlying funds have been withdrawn.", + "type": "string", + "enum": [ + "complete" + ] + } + ] + }, + "denom_splits": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DenomSplits", + "type": "object", + "required": [ + "explicit_splits" + ], + "properties": { + "explicit_splits": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/SplitConfig" + } + }, + "fallback_split": { + "anyOf": [ + { + "$ref": "#/definitions/SplitConfig" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "SplitConfig": { + "type": "object", + "required": [ + "receivers" + ], + "properties": { + "receivers": { + "description": "map receiver address to its share of the split", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + } + }, + "additionalProperties": false + } + } + }, + "deposit_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "deposit_deadline": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Expiration", + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "emergency_committee": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "lockup_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Expiration", + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "next_contract": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_String", + "type": [ + "string", + "null" + ] + }, + "ragequit_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RagequitConfig", + "oneOf": [ + { + "description": "ragequit is disabled", + "type": "string", + "enum": [ + "disabled" + ] + }, + { + "description": "ragequit is enabled with `RagequitTerms`", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "$ref": "#/definitions/RagequitTerms" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "RagequitState": { + "type": "object", + "required": [ + "coins", + "rq_party" + ], + "properties": { + "coins": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "rq_party": { + "$ref": "#/definitions/TwoPartyPolCovenantParty" + } + }, + "additionalProperties": false + }, + "RagequitTerms": { + "type": "object", + "required": [ + "penalty" + ], + "properties": { + "penalty": { + "description": "decimal based penalty to be applied on a party for initiating ragequit. Must be in the range of (0.00, 1.00). Also must not exceed either party allocations in raw values.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "state": { + "description": "optional rq state. none indicates no ragequit. some holds the ragequit related config", + "anyOf": [ + { + "$ref": "#/definitions/RagequitState" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "TwoPartyPolCovenantParty": { + "type": "object", + "required": [ + "allocation", + "contribution", + "controller_addr", + "host_addr", + "router" + ], + "properties": { + "allocation": { + "description": "fraction of the entire LP position owned by the party. upon exiting it becomes 0.00. if counterparty exits, this would become 1.00, meaning that this party owns the entire position managed by the covenant.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "contribution": { + "description": "the `denom` and `amount` (`Uint128`) to be contributed by the party", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "controller_addr": { + "description": "address of the party on the controller chain (final receiver)", + "type": "string" + }, + "host_addr": { + "description": "neutron address authorized by the party to perform claims/ragequits", + "type": "string" + }, + "router": { + "description": "address of the interchain router associated with this party", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/contracts/two-party-pol-holder/src/bin/schema.rs b/contracts/two-party-pol-holder/src/bin/schema.rs new file mode 100644 index 00000000..4afeb275 --- /dev/null +++ b/contracts/two-party-pol-holder/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; +use valence_two_party_pol_holder::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + migrate: MigrateMsg, + } +} diff --git a/contracts/two-party-pol-holder/src/contract.rs b/contracts/two-party-pol-holder/src/contract.rs new file mode 100644 index 00000000..91ab2f28 --- /dev/null +++ b/contracts/two-party-pol-holder/src/contract.rs @@ -0,0 +1,696 @@ +use std::cmp::Ordering; +use std::collections::BTreeMap; + +use cosmwasm_std::{ + ensure, to_json_binary, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, + MessageInfo, Response, StdError, StdResult, +}; + +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; + +use covenant_utils::split::SplitConfig; +use covenant_utils::withdraw_lp_helper::{generate_withdraw_msg, EMERGENCY_COMMITTEE_ADDR}; +use cw2::set_contract_version; +use valence_clock::helpers::{enqueue_msg, verify_clock}; + +use crate::msg::CovenantType; +use crate::state::{WithdrawState, LIQUID_POOLER_ADDRESS, WITHDRAW_STATE}; +use crate::{ + error::ContractError, + msg::{ + ContractState, DenomSplits, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, + RagequitConfig, RagequitState, TwoPartyPolCovenantConfig, TwoPartyPolCovenantParty, + }, + state::{ + CLOCK_ADDRESS, CONTRACT_STATE, COVENANT_CONFIG, DENOM_SPLITS, DEPOSIT_DEADLINE, + LOCKUP_CONFIG, RAGEQUIT_CONFIG, + }, +}; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let next_contract = deps.api.addr_validate(&msg.next_contract)?; + let clock_addr = deps.api.addr_validate(&msg.clock_address)?; + + // ensure that the deposit deadline is in the future + ensure!( + !msg.deposit_deadline.is_expired(&env.block), + ContractError::DepositDeadlineValidationError {} + ); + + // validate that lockup expiration is after the deposit deadline + match msg.deposit_deadline.partial_cmp(&msg.lockup_config) { + Some(ordering) => ensure!( + ordering == Ordering::Less, + ContractError::LockupValidationError {} + ), + // we validate incompatible expirations + None => return Err(ContractError::ExpirationValidationError {}), + }; + + if let Some(addr) = &msg.emergency_committee_addr { + let committee_addr = deps.api.addr_validate(addr)?; + EMERGENCY_COMMITTEE_ADDR.save(deps.storage, &committee_addr)?; + } + + msg.covenant_config.validate(deps.api)?; + msg.ragequit_config.validate( + msg.covenant_config.party_a.allocation, + msg.covenant_config.party_b.allocation, + )?; + + // validate the splits and collect them into map + let explicit_splits: BTreeMap = msg + .splits + .iter() + .filter_map(|(denom, split)| { + split + .validate( + &msg.covenant_config.party_a.router, + &msg.covenant_config.party_b.router, + ) + .ok()?; + Some((denom.to_string(), split.to_owned())) + }) + .collect(); + + msg.fallback_split + .as_ref() + .map(|split_config| { + split_config.validate( + &msg.covenant_config.party_a.router, + &msg.covenant_config.party_b.router, + ) + }) + .transpose()?; + + DENOM_SPLITS.save( + deps.storage, + &DenomSplits { + explicit_splits, + fallback_split: msg.fallback_split.clone(), + }, + )?; + LIQUID_POOLER_ADDRESS.save(deps.storage, &next_contract)?; + CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; + LOCKUP_CONFIG.save(deps.storage, &msg.lockup_config)?; + RAGEQUIT_CONFIG.save(deps.storage, &msg.ragequit_config)?; + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + COVENANT_CONFIG.save(deps.storage, &msg.covenant_config)?; + DEPOSIT_DEADLINE.save(deps.storage, &msg.deposit_deadline)?; + + Ok(Response::default() + .add_message(enqueue_msg(clock_addr.as_str())?) + .add_attribute("method", "two_party_pol_holder_instantiate") + .add_attributes(msg.get_response_attributes())) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Ragequit {} => try_ragequit(deps, env, info), + ExecuteMsg::Tick {} => try_tick(deps, env, info), + ExecuteMsg::Claim {} => try_claim(deps, info), + ExecuteMsg::Distribute {} => try_distribute(deps, info), + ExecuteMsg::WithdrawFailed {} => try_withdraw_failed(deps, info), + ExecuteMsg::DistributeFallbackSplit { denoms } => { + try_distribute_fallback_split(deps, env, denoms) + } + ExecuteMsg::EmergencyWithdraw {} => try_emergency_withdraw(deps, info), + } +} + +fn try_distribute_fallback_split( + deps: DepsMut, + env: Env, + denoms: Vec, +) -> Result { + let mut available_balances = Vec::with_capacity(denoms.len()); + let denom_splits = DENOM_SPLITS.load(deps.storage)?; + + for denom in denoms { + if denom_splits.explicit_splits.contains_key(&denom) { + return Err(ContractError::UnauthorizedDenomDistribution {}); + } + let queried_coin = deps + .querier + .query_balance(env.contract.address.to_string(), denom)?; + available_balances.push(queried_coin); + } + + let fallback_distribution_messages = + denom_splits.get_fallback_distribution_messages(available_balances); + + Ok(Response::default() + .add_attribute("method", "try_distribute_fallback_split") + .add_messages(fallback_distribution_messages)) +} + +/// On claim, we should simply ask the LPer to withdraw the liquidity and execute a Distribute msg on the holder +fn try_claim(deps: DepsMut, info: MessageInfo) -> Result { + if WITHDRAW_STATE.load(deps.storage).is_ok() { + return Err(ContractError::WithdrawAlreadyStarted {}); + } + + let covenant_config = COVENANT_CONFIG.load(deps.storage)?; + let (claim_party, counterparty) = covenant_config.authorize_sender(info.sender.to_string())?; + + // if both parties already claimed everything we complete early + if claim_party.allocation.is_zero() && counterparty.allocation.is_zero() { + let clock_address = CLOCK_ADDRESS.load(deps.storage)?; + let dequeue_message = ContractState::complete_and_dequeue(deps, clock_address.as_str())?; + + return Ok(Response::default() + .add_attribute("method", "try_claim") + .add_attribute("contract_state", "complete") + .add_message(dequeue_message)); + } + + // we exit early if contract is not in ragequit or expired state + let contract_state = CONTRACT_STATE.load(deps.storage)?; + contract_state.validate_claim_state()?; + + // set WithdrawState to include original data + WITHDRAW_STATE.save( + deps.storage, + &WithdrawState::Processing { + claimer_addr: claim_party.host_addr, + }, + )?; + + // If type is share we only withdraw the claim party allocation + // if type is side, we withdraw 100% of funds + let withdraw_percentage = match covenant_config.covenant_type { + CovenantType::Share => Some(claim_party.allocation), + CovenantType::Side => None, // 100% + }; + + let lper = LIQUID_POOLER_ADDRESS.load(deps.storage)?; + let withdraw_msg = generate_withdraw_msg(lper.to_string(), withdraw_percentage)?; + + Ok(Response::default().add_message(withdraw_msg)) +} + +fn try_emergency_withdraw(deps: DepsMut, info: MessageInfo) -> Result { + if WITHDRAW_STATE.load(deps.storage).is_ok() { + return Err(ContractError::WithdrawAlreadyStarted {}); + } + + let committee_addr = EMERGENCY_COMMITTEE_ADDR.load(deps.storage)?; + ensure!( + info.sender == committee_addr, + ContractError::Unauthorized {} + ); + + WITHDRAW_STATE.save(deps.storage, &WithdrawState::Emergency {})?; + + let lper = LIQUID_POOLER_ADDRESS.load(deps.storage)?; + let withdraw_msg = generate_withdraw_msg(lper.to_string(), None)?; + + Ok(Response::default().add_message(withdraw_msg)) +} + +fn try_distribute(mut deps: DepsMut, info: MessageInfo) -> Result { + // Only pooler can call this + let pooler_addr = LIQUID_POOLER_ADDRESS.load(deps.storage)?; + ensure!(info.sender == pooler_addr, ContractError::Unauthorized {}); + + let withdraw_state = WITHDRAW_STATE + .load(deps.storage) + .map_err(|_| ContractError::WithdrawStateNotStarted {})?; + + let covenant_config = COVENANT_CONFIG.load(deps.storage)?; + let denom_splits = DENOM_SPLITS.load(deps.storage)?; + + let (claim_party, counterparty, denom_splits, is_rq) = match withdraw_state { + WithdrawState::Processing { claimer_addr } => { + let (claim_party, counterparty) = covenant_config.authorize_sender(claimer_addr)?; + + (claim_party, counterparty, denom_splits, false) + } + WithdrawState::ProcessingRagequit { + claimer_addr, + terms, + } => { + let (rq_party, counterparty) = covenant_config.authorize_sender(claimer_addr)?; + let new_denom_split = + denom_splits.apply_penalty(terms.penalty, &rq_party, &counterparty)?; + + (rq_party, counterparty, new_denom_split, true) + } + WithdrawState::Emergency {} => { + return try_claim_side_based( + deps, + covenant_config.party_a.clone(), + covenant_config.party_b.clone(), + info.funds, + covenant_config, + denom_splits, + ) + } + }; + + WITHDRAW_STATE.remove(deps.storage); + + match covenant_config.covenant_type { + CovenantType::Share => { + if is_rq { + apply_rq_state_share(deps.branch(), claim_party.clone(), info.funds.clone())?; + } + + try_claim_share_based( + deps, + claim_party, + counterparty, + info.funds, + covenant_config, + denom_splits, + ) + } + CovenantType::Side => { + if is_rq { + apply_rq_state_side(deps.branch(), claim_party.clone(), info.funds.clone())?; + } + + try_claim_side_based( + deps, + claim_party, + counterparty, + info.funds, + covenant_config, + denom_splits, + ) + } + } +} + +/// We don't do much on failed withdraw, as nothing changed so far. +/// We only change state on distribute msg. +fn try_withdraw_failed(deps: DepsMut, info: MessageInfo) -> Result { + // Assert the caller is the pooler + let pooler_addr = LIQUID_POOLER_ADDRESS.load(deps.storage)?; + ensure!(info.sender == pooler_addr, ContractError::Unauthorized {}); + + WITHDRAW_STATE.remove(deps.storage); + + Ok(Response::default()) +} + +#[allow(clippy::too_many_arguments)] +fn try_claim_share_based( + mut deps: DepsMut, + mut claim_party: TwoPartyPolCovenantParty, + mut counterparty: TwoPartyPolCovenantParty, + funds: Vec, + mut covenant_config: TwoPartyPolCovenantConfig, + denom_splits: DenomSplits, +) -> Result { + let mut messages = denom_splits + .get_single_receiver_distribution_messages(funds, claim_party.router.to_string()); + + claim_party.allocation = Decimal::zero(); + + // if other party had not claimed yet, we assign it the full position + if !counterparty.allocation.is_zero() { + counterparty.allocation = Decimal::one(); + } else { + // otherwise both parties claimed everything and we can complete + let clock_address = CLOCK_ADDRESS.load(deps.storage)?; + let dequeue_message = + ContractState::complete_and_dequeue(deps.branch(), clock_address.as_str())?; + messages.push(dequeue_message.into()); + }; + + covenant_config.update_parties(claim_party, counterparty); + + COVENANT_CONFIG.save(deps.storage, &covenant_config)?; + + Ok(Response::default() + .add_attribute("method", "claim_share_based") + .add_messages(messages)) +} + +#[allow(clippy::too_many_arguments)] +fn try_claim_side_based( + deps: DepsMut, + mut claim_party: TwoPartyPolCovenantParty, + mut counterparty: TwoPartyPolCovenantParty, + funds: Vec, + mut covenant_config: TwoPartyPolCovenantConfig, + denom_splits: DenomSplits, +) -> Result { + let messages: Vec = denom_splits.get_shared_distribution_messages(funds); + + claim_party.allocation = Decimal::zero(); + counterparty.allocation = Decimal::zero(); + covenant_config.update_parties(claim_party, counterparty); + + // update the states and dequeue from the clock + COVENANT_CONFIG.save(deps.storage, &covenant_config)?; + let clock_address = CLOCK_ADDRESS.load(deps.storage)?; + let dequeue_message = ContractState::complete_and_dequeue(deps, clock_address.as_str())?; + + Ok(Response::default() + .add_attribute("method", "claim_side_based") + .add_messages(messages) + .add_message(dequeue_message)) +} + +fn try_tick(deps: DepsMut, env: Env, info: MessageInfo) -> Result { + let state = CONTRACT_STATE.load(deps.storage)?; + let clock_addr = CLOCK_ADDRESS.load(deps.storage)?; + verify_clock(&info.sender, &clock_addr) + .map_err(|e| ContractError::Std(StdError::generic_err(e.to_string())))?; + + match state { + ContractState::Instantiated => try_deposit(deps, env, info), + ContractState::Active => check_expiration(deps, env), + ContractState::Expired | ContractState::Ragequit => Ok(Response::default() + .add_attribute("method", "tick") + .add_attribute("contract_state", state.to_string())), + ContractState::Complete => try_refund(deps, env), + } +} + +/// attempts to route any available covenant party contribution denoms to +/// the parties that were responsible for contributing that denom. +fn try_refund(deps: DepsMut, env: Env) -> Result { + let config = COVENANT_CONFIG.load(deps.storage)?; + + // assert the balances + let party_a_bal = deps.querier.query_balance( + env.contract.address.to_string(), + config.party_a.contribution.denom, + )?; + let party_b_bal = deps.querier.query_balance( + env.contract.address.to_string(), + config.party_b.contribution.denom, + )?; + + let refund_messages: Vec = + match (party_a_bal.amount.is_zero(), party_b_bal.amount.is_zero()) { + // both balances empty, nothing to refund + (true, true) => vec![], + // refund party B + (true, false) => vec![CosmosMsg::Bank(BankMsg::Send { + to_address: config.party_b.router, + amount: vec![party_b_bal], + })], + // refund party A + (false, true) => vec![CosmosMsg::Bank(BankMsg::Send { + to_address: config.party_a.router, + amount: vec![party_a_bal], + })], + // refund both + (false, false) => vec![ + CosmosMsg::Bank(BankMsg::Send { + to_address: config.party_a.router.to_string(), + amount: vec![party_a_bal], + }), + CosmosMsg::Bank(BankMsg::Send { + to_address: config.party_b.router, + amount: vec![party_b_bal], + }), + ], + }; + + Ok(Response::default() + .add_attribute("contract_state", "complete") + .add_attribute("method", "try_refund") + .add_messages(refund_messages)) +} + +fn try_deposit(deps: DepsMut, env: Env, _info: MessageInfo) -> Result { + let deposit_deadline = DEPOSIT_DEADLINE.load(deps.storage)?; + if deposit_deadline.is_expired(&env.block) { + CONTRACT_STATE.save(deps.storage, &ContractState::Complete)?; + return Ok(Response::default() + .add_attribute("method", "try_deposit") + .add_attribute("deposit_deadline", "expired") + .add_attribute("action", "complete")); + } + + let config = COVENANT_CONFIG.load(deps.storage)?; + + // assert the balances + let party_a_bal = deps.querier.query_balance( + env.contract.address.to_string(), + config.party_a.contribution.denom, + )?; + let party_b_bal = deps.querier.query_balance( + env.contract.address.to_string(), + config.party_b.contribution.denom, + )?; + + let party_a_fulfilled = config.party_a.contribution.amount <= party_a_bal.amount; + let party_b_fulfilled = config.party_b.contribution.amount <= party_b_bal.amount; + + if !party_a_fulfilled || !party_b_fulfilled { + // if deposit deadline is not yet due and both parties did not fulfill we error + return Err(ContractError::InsufficientDeposits {}); + } + + // LiquidPooler is the next contract + let liquid_pooler = LIQUID_POOLER_ADDRESS.load(deps.storage)?; + let msg = BankMsg::Send { + to_address: liquid_pooler.to_string(), + amount: vec![party_a_bal, party_b_bal], + }; + + // advance the state to Active + CONTRACT_STATE.save(deps.storage, &ContractState::Active)?; + + Ok(Response::default() + .add_attribute("method", "deposit_to_next_contract") + .add_message(msg)) +} + +fn check_expiration(deps: DepsMut, env: Env) -> Result { + let lockup_config = LOCKUP_CONFIG.load(deps.storage)?; + + if !lockup_config.is_expired(&env.block) { + return Ok(Response::default() + .add_attribute("method", "check_expiration") + .add_attribute("result", "not_due")); + } + + // advance state to Expired to enable claims + CONTRACT_STATE.save(deps.storage, &ContractState::Expired)?; + + Ok(Response::default() + .add_attribute("method", "check_expiration") + .add_attribute("contract_state", "expired")) +} + +fn try_ragequit(deps: DepsMut, env: Env, info: MessageInfo) -> Result { + // first we error out if ragequit is disabled + let rq_terms = match RAGEQUIT_CONFIG.load(deps.storage)? { + RagequitConfig::Disabled => return Err(ContractError::RagequitDisabled {}), + RagequitConfig::Enabled(terms) => terms, + }; + let current_state = CONTRACT_STATE.load(deps.storage)?; + + // ragequit is only possible when contract is in Active state. + if current_state != ContractState::Active { + return Err(ContractError::NotActive {}); + } + + if WITHDRAW_STATE.load(deps.storage).is_ok() { + return Err(ContractError::WithdrawAlreadyStarted {}); + } + + let lockup_config = LOCKUP_CONFIG.load(deps.storage)?; + + // we also validate an edge case where it did expire but + // did not receive a tick yet. tick is then required to advance. + if lockup_config.is_expired(&env.block) { + return Err(ContractError::Expired {}); + } + + // authorize the message sender + let covenant_config = COVENANT_CONFIG.load(deps.storage)?; + let (rq_party, _) = covenant_config.authorize_sender(info.sender.to_string())?; + + // If type is share we only withdraw the claim party allocation + // if type is side, we withdraw 100% of funds + let withdraw_percentage = match covenant_config.covenant_type { + CovenantType::Share => Some(rq_party.allocation - rq_terms.penalty), + CovenantType::Side => None, // 100% + }; + + // set WithdrawState to include original data + WITHDRAW_STATE.save( + deps.storage, + &WithdrawState::ProcessingRagequit { + claimer_addr: rq_party.host_addr, + terms: rq_terms, + }, + )?; + + let lper = LIQUID_POOLER_ADDRESS.load(deps.storage)?; + let withdraw_msg = generate_withdraw_msg(lper.to_string(), withdraw_percentage)?; + + Ok(Response::default().add_message(withdraw_msg)) +} + +pub fn apply_rq_state_side( + deps: DepsMut, + rq_party: TwoPartyPolCovenantParty, + coins: Vec, +) -> Result<(), ContractError> { + if let RagequitConfig::Enabled(mut rq_terms) = RAGEQUIT_CONFIG.load(deps.storage)? { + rq_terms.state = Some(RagequitState { coins, rq_party }); + + RAGEQUIT_CONFIG.save(deps.storage, &RagequitConfig::Enabled(rq_terms))?; + } + Ok(()) +} + +pub fn apply_rq_state_share( + deps: DepsMut, + rq_party: TwoPartyPolCovenantParty, + coins: Vec, +) -> Result<(), ContractError> { + if let RagequitConfig::Enabled(mut rq_terms) = RAGEQUIT_CONFIG.load(deps.storage)? { + rq_terms.state = Some(RagequitState { coins, rq_party }); + + RAGEQUIT_CONFIG.save(deps.storage, &RagequitConfig::Enabled(rq_terms))?; + }; + + CONTRACT_STATE.save(deps.storage, &ContractState::Ragequit)?; + + Ok(()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ContractState {} => Ok(to_json_binary(&CONTRACT_STATE.load(deps.storage)?)?), + QueryMsg::RagequitConfig {} => Ok(to_json_binary(&RAGEQUIT_CONFIG.load(deps.storage)?)?), + QueryMsg::LockupConfig {} => Ok(to_json_binary(&LOCKUP_CONFIG.load(deps.storage)?)?), + QueryMsg::ClockAddress {} => Ok(to_json_binary(&CLOCK_ADDRESS.load(deps.storage)?)?), + QueryMsg::NextContract {} => { + Ok(to_json_binary(&LIQUID_POOLER_ADDRESS.load(deps.storage)?)?) + } + QueryMsg::ConfigPartyA {} => Ok(to_json_binary( + &COVENANT_CONFIG.load(deps.storage)?.party_a, + )?), + QueryMsg::ConfigPartyB {} => Ok(to_json_binary( + &COVENANT_CONFIG.load(deps.storage)?.party_b, + )?), + QueryMsg::DepositDeadline {} => Ok(to_json_binary(&DEPOSIT_DEADLINE.load(deps.storage)?)?), + QueryMsg::Config {} => Ok(to_json_binary(&COVENANT_CONFIG.load(deps.storage)?)?), + QueryMsg::DepositAddress {} => Ok(to_json_binary(&env.contract.address)?), + QueryMsg::DenomSplits {} => Ok(to_json_binary(&DENOM_SPLITS.load(deps.storage)?)?), + QueryMsg::EmergencyCommittee {} => Ok(to_json_binary( + &EMERGENCY_COMMITTEE_ADDR.may_load(deps.storage)?, + )?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult { + match msg { + MigrateMsg::UpdateConfig { + clock_addr, + next_contract, + lockup_config, + deposit_deadline, + ragequit_config, + covenant_config, + denom_splits, + fallback_split, + emergency_committee, + } => { + let mut resp = Response::default().add_attribute("method", "update_config"); + + if let Some(addr) = clock_addr { + let clock_address = deps.api.addr_validate(&addr)?; + CLOCK_ADDRESS.save(deps.storage, &clock_address)?; + resp = resp.add_attribute("clock_addr", addr); + } + + if let Some(addr) = next_contract { + let next_contract_addr = deps.api.addr_validate(&addr)?; + LIQUID_POOLER_ADDRESS.save(deps.storage, &next_contract_addr)?; + resp = resp.add_attribute("next_contract", addr); + } + + if let Some(expiry_config) = lockup_config { + if expiry_config.is_expired(&env.block) { + return Err(StdError::generic_err("lockup config is already past")); + } + LOCKUP_CONFIG.save(deps.storage, &expiry_config)?; + resp = resp.add_attribute("lockup_config", expiry_config.to_string()); + } + + if let Some(expiry_config) = deposit_deadline { + if expiry_config.is_expired(&env.block) { + return Err(StdError::generic_err("deposit deadline is already past")); + } + DEPOSIT_DEADLINE.save(deps.storage, &expiry_config)?; + resp = resp.add_attribute("deposit_deadline", expiry_config.to_string()); + } + + if let Some(addr) = emergency_committee { + let committee_addr = deps.api.addr_validate(&addr)?; + EMERGENCY_COMMITTEE_ADDR.save(deps.storage, &committee_addr)?; + resp = resp.add_attribute("emergency_committee_addr", committee_addr); + } + + if let Some(config) = *ragequit_config { + RAGEQUIT_CONFIG.save(deps.storage, &config)?; + resp = resp.add_attributes(config.get_response_attributes()); + } + + if let Some(config) = *covenant_config { + COVENANT_CONFIG.save(deps.storage, &config)?; + resp = resp.add_attribute("covenant_config", format!("{:?}", config)); + } + + if let Some(splits) = denom_splits { + for config in splits.values() { + config.validate_shares_and_receiver_addresses(deps.api)?; + } + resp = resp.add_attribute("explicit_splits", format!("{:?}", splits)); + DENOM_SPLITS.update(deps.storage, |mut current_splits| -> StdResult<_> { + current_splits.explicit_splits = splits; + Ok(current_splits) + })?; + } + + if let Some(split) = fallback_split { + split.validate_shares_and_receiver_addresses(deps.api)?; + resp = resp.add_attribute("fallback_split", format!("{:?}", split)); + DENOM_SPLITS.update(deps.storage, |mut current_splits| -> StdResult<_> { + current_splits.fallback_split = Some(split); + Ok(current_splits) + })?; + } + + Ok(resp) + } + MigrateMsg::UpdateCodeId { data: _ } => { + // This is a migrate message to update code id, + // Data is optional base64 that we can parse to any data we would like in the future + // let data: SomeStruct = from_binary(&data)?; + Ok(Response::default()) + } + } +} diff --git a/contracts/two-party-pol-holder/src/error.rs b/contracts/two-party-pol-holder/src/error.rs new file mode 100644 index 00000000..4c6f6de8 --- /dev/null +++ b/contracts/two-party-pol-holder/src/error.rs @@ -0,0 +1,80 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("party allocations must add up to 1.0")] + AllocationValidationError {}, + + #[error("Ragequit penalty must be in range of [0.0, 1.0)")] + RagequitPenaltyRangeError {}, + + #[error("Ragequit penalty exceeds party allocation")] + RagequitPenaltyExceedsPartyAllocationError {}, + + #[error("unauthorized")] + Unauthorized {}, + + #[error("contract needs to be in ragequit or expired state in order to claim")] + ClaimError {}, + + #[error("covenant is not in active state")] + NotActive {}, + + #[error("covenant is active but expired; tick to proceed")] + Expired {}, + + #[error("both parties have not deposited")] + InsufficientDeposits {}, + + #[error("failed to multiply amount by share")] + FractionMulError {}, + + #[error("expiry block is already past")] + InvalidExpiryBlockHeight {}, + + #[error("lockup deadline must be after the deposit deadline")] + LockupValidationError {}, + + #[error("cannot validate deposit and lockup expirations")] + ExpirationValidationError {}, + + #[error("deposit deadline is already past")] + DepositDeadlineValidationError {}, + + #[error("shares of covenant parties must add up to 1.0")] + InvolvedPartiesConfigError {}, + + #[error("unknown party")] + PartyNotFound {}, + + #[error("ragequit is disabled")] + RagequitDisabled {}, + + #[error("only covenant parties can initiate ragequit")] + RagequitUnauthorized {}, + + #[error("ragequit attempt with lockup period passed")] + RagequitWithLockupPassed {}, + + #[error("ragequit already active")] + RagequitInProgress {}, + + #[error("unauthorized to distribute explicitly defined denom")] + UnauthorizedDenomDistribution {}, + + #[error("A withdraw process already started")] + WithdrawAlreadyStarted {}, + + #[error("A withdraw process wasn't started yet")] + WithdrawStateNotStarted {}, + + #[error("Claimer already claimed his share")] + PartyAllocationIsZero {}, + + #[error("Party contribution cannot be zero")] + PartyContributionConfigError {}, +} diff --git a/contracts/two-party-pol-holder/src/lib.rs b/contracts/two-party-pol-holder/src/lib.rs new file mode 100644 index 00000000..47bc573c --- /dev/null +++ b/contracts/two-party-pol-holder/src/lib.rs @@ -0,0 +1,6 @@ +extern crate core; + +pub mod contract; +pub mod error; +pub mod msg; +pub mod state; diff --git a/contracts/two-party-pol-holder/src/msg.rs b/contracts/two-party-pol-holder/src/msg.rs new file mode 100644 index 00000000..d09f066f --- /dev/null +++ b/contracts/two-party-pol-holder/src/msg.rs @@ -0,0 +1,519 @@ +use std::{collections::BTreeMap, fmt}; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{ + ensure, to_json_binary, Addr, Api, Attribute, Binary, Coin, CosmosMsg, Decimal, DepsMut, + StdError, StdResult, WasmMsg, +}; +use covenant_macros::{ + clocked, covenant_clock_address, covenant_deposit_address, covenant_holder_distribute, + covenant_holder_emergency_withdraw, covenant_next_contract, +}; +use covenant_utils::{instantiate2_helper::Instantiate2HelperConfig, split::SplitConfig}; +use cw_utils::Expiration; +use valence_clock::helpers::dequeue_msg; + +use crate::{error::ContractError, state::CONTRACT_STATE}; + +#[cw_serde] +pub struct InstantiateMsg { + /// address of authorized clock + pub clock_address: String, + /// liquid pooler address + pub next_contract: String, + /// config describing the agreed upon duration of POL + pub lockup_config: Expiration, + /// config describing early exit dynamics + pub ragequit_config: RagequitConfig, + /// deadline for both parties to deposit their funds + pub deposit_deadline: Expiration, + /// config describing the covenant dynamics + pub covenant_config: TwoPartyPolCovenantConfig, + /// mapping of denoms to their splits + pub splits: BTreeMap, + /// a split for all denoms that are not covered in the + /// regular `splits` list + pub fallback_split: Option, + /// address of the emergency committee + pub emergency_committee_addr: Option, +} + +impl InstantiateMsg { + pub fn to_instantiate2_msg( + &self, + instantiate2_helper: &Instantiate2HelperConfig, + admin: String, + label: String, + ) -> StdResult { + Ok(WasmMsg::Instantiate2 { + admin: Some(admin), + code_id: instantiate2_helper.code, + label, + msg: to_json_binary(self)?, + funds: vec![], + salt: instantiate2_helper.salt.clone(), + }) + } +} + +impl InstantiateMsg { + pub fn get_response_attributes(&self) -> Vec { + let fallback_attr = match self.fallback_split.as_ref() { + Some(split) => split.get_response_attribute("fallback_split".to_string()), + None => Attribute::new("fallback_split".to_string(), "none".to_string()), + }; + let splits_attr: Vec = self + .splits + .iter() + .map(|(denom, split_config)| split_config.get_response_attribute(denom.to_string())) + .collect(); + + let mut attrs = vec![ + Attribute::new("clock_addr", self.clock_address.to_string()), + Attribute::new("next_contract", self.next_contract.to_string()), + Attribute::new("lockup_config", self.lockup_config.to_string()), + Attribute::new("deposit_deadline", self.deposit_deadline.to_string()), + fallback_attr, + ]; + attrs.extend(self.ragequit_config.get_response_attributes()); + attrs.extend(splits_attr); + attrs.extend(self.covenant_config.get_response_attributes()); + attrs + } +} + +#[cw_serde] +pub enum CovenantType { + Share, + Side, +} + +impl CovenantType { + pub fn get_response_attribute(&self) -> Attribute { + Attribute::new( + "covenant_type", + match self { + CovenantType::Share => "share", + CovenantType::Side => "side", + }, + ) + } +} + +#[cw_serde] +pub struct DenomSplits { + pub explicit_splits: BTreeMap, + pub fallback_split: Option, +} + +impl DenomSplits { + pub fn get_fallback_distribution_messages(self, available_coins: Vec) -> Vec { + available_coins + .iter() + .filter_map(|c| { + // explicit splits are distributed via claim/ragequit + if self.explicit_splits.contains_key(&c.denom) { + None + } else if let Some(fallback_split) = &self.fallback_split { + match fallback_split.get_transfer_messages(c.amount, c.denom.to_string(), None) + { + Ok(msgs) => Some(msgs), + Err(_) => None, + } + } else { + None + } + }) + .flatten() + .collect() + } + + pub fn get_single_receiver_distribution_messages( + self, + available_coins: Vec, + addr: String, + ) -> Vec { + available_coins + .iter() + .filter_map(|c| { + // for each coin denom we want to distribute, + // we look for it in our explicitly defined split configs + if let Some(config) = self.explicit_splits.get(&c.denom) { + // found it, generate the msg or filter out + match config.get_transfer_messages( + c.amount, + c.denom.to_string(), + Some(addr.to_string()), + ) { + Ok(msgs) => Some(msgs), + Err(_) => None, + } + } else { + None + } + }) + .flatten() + .collect() + } + + pub fn get_shared_distribution_messages(self, available_coins: Vec) -> Vec { + available_coins + .iter() + .filter_map(|c| { + // for each coin denom we want to distribute, + // we look for it in our explicitly defined split configs + let split = self.explicit_splits.get(&c.denom); + if let Some(config) = split { + // found it, generate the msg or filter out + match config.get_transfer_messages(c.amount, c.denom.to_string(), None) { + Ok(msgs) => Some(msgs), + Err(_) => None, + } + } else { + None + } + }) + .flatten() + .collect() + } + + pub fn apply_penalty( + mut self, + penalty: Decimal, + party: &TwoPartyPolCovenantParty, + counterparty: &TwoPartyPolCovenantParty, + ) -> Result { + // we iterate over explicitly defined splits for each denom + for (denom, mut config) in self.explicit_splits.clone().into_iter() { + let party_share = config + .receivers + // get current party shares or error out if not found + .get(&party.router) + .ok_or(ContractError::PartyNotFound {})?; + + // we do not penalize already null allocations of + // the ragequitting party + if party_share > &Decimal::zero() { + let new_party_share = party_share + // add the penalty or return overflow + .checked_sub(penalty) + .map_err(StdError::overflow)?; + + let new_counterparty_share = config + .receivers + .get(&counterparty.router) + .ok_or(ContractError::PartyNotFound {})? + .checked_add(penalty) + .map_err(StdError::overflow)?; + + // override existing entries with the updated values + // while keeping the keys + config + .receivers + .insert(party.router.to_string(), new_party_share); + config + .receivers + .insert(counterparty.router.to_string(), new_counterparty_share); + + // override the existing denom entry with updated config + self.explicit_splits.insert(denom, config); + } + } + + if let Some(mut split_config) = self.fallback_split { + // apply the ragequit penalty to rq party and its counterparty + let new_party_share = split_config + .receivers + // get current party shares or error out if not found + .get(party.router.as_str()) + .ok_or(ContractError::PartyNotFound {})? + // add the penalty or return overflow + .checked_sub(penalty) + .map_err(StdError::overflow)?; + + let new_counterparty_share = split_config + .receivers + .get(counterparty.router.as_str()) + .ok_or(ContractError::PartyNotFound {})? + .checked_add(penalty) + .map_err(StdError::overflow)?; + + // override existing entries with the updated values + // while keeping the keys + split_config + .receivers + .insert(party.router.to_string(), new_party_share); + split_config + .receivers + .insert(counterparty.router.to_string(), new_counterparty_share); + + // reflect the updated values in self + self.fallback_split = Some(split_config); + } + + Ok(self) + } +} + +#[cw_serde] +pub struct TwoPartyPolCovenantConfig { + pub party_a: TwoPartyPolCovenantParty, + pub party_b: TwoPartyPolCovenantParty, + pub covenant_type: CovenantType, +} + +impl TwoPartyPolCovenantConfig { + pub fn update_parties(&mut self, p1: TwoPartyPolCovenantParty, p2: TwoPartyPolCovenantParty) { + if self.party_a.controller_addr == p1.controller_addr { + self.party_a = p1; + self.party_b = p2; + } else { + self.party_a = p2; + self.party_b = p1; + } + } + + pub fn validate(&self, api: &dyn Api) -> Result<(), ContractError> { + api.addr_validate(&self.party_a.router)?; + api.addr_validate(&self.party_b.router)?; + api.addr_validate(&self.party_a.host_addr)?; + api.addr_validate(&self.party_b.host_addr)?; + + ensure!( + !self.party_a.contribution.amount.is_zero() + && !self.party_b.contribution.amount.is_zero(), + ContractError::PartyContributionConfigError {} + ); + + ensure!( + self.party_a.allocation + self.party_b.allocation == Decimal::one(), + ContractError::AllocationValidationError {} + ); + + Ok(()) + } + + pub fn get_response_attributes(&self) -> Vec { + let mut attributes = vec![]; + let party_a_attributes: Vec = self.party_a.get_response_attributes(); + let party_b_attributes: Vec = self.party_b.get_response_attributes(); + attributes.extend(party_a_attributes); + attributes.extend(party_b_attributes); + attributes.push(self.covenant_type.get_response_attribute()); + attributes + } +} + +#[cw_serde] +pub struct TwoPartyPolCovenantParty { + /// the `denom` and `amount` (`Uint128`) to be contributed by the party + pub contribution: Coin, + /// neutron address authorized by the party to perform claims/ragequits + pub host_addr: String, + /// address of the party on the controller chain (final receiver) + pub controller_addr: String, + /// fraction of the entire LP position owned by the party. + /// upon exiting it becomes 0.00. if counterparty exits, this would + /// become 1.00, meaning that this party owns the entire position + /// managed by the covenant. + pub allocation: Decimal, + /// address of the interchain router associated with this party + pub router: String, +} + +impl TwoPartyPolCovenantParty { + pub fn get_response_attributes(&self) -> Vec { + vec![ + Attribute::new("contribution", self.contribution.to_string()), + Attribute::new("host_addr", self.host_addr.to_string()), + Attribute::new("controller_addr", self.controller_addr.to_string()), + Attribute::new("allocation", self.allocation.to_string()), + Attribute::new("router", self.router.to_string()), + ] + } +} + +impl TwoPartyPolCovenantConfig { + /// if authorized, returns (party, counterparty). otherwise errors + pub fn authorize_sender( + &self, + sender: String, + ) -> Result<(TwoPartyPolCovenantParty, TwoPartyPolCovenantParty), ContractError> { + let party_a = self.party_a.clone(); + let party_b = self.party_b.clone(); + let parties = if party_a.host_addr == sender { + (party_a, party_b) + } else if party_b.host_addr == sender { + (party_b, party_a) + } else { + return Err(ContractError::Unauthorized {}); + }; + + ensure!( + !parties.0.allocation.is_zero(), + ContractError::PartyAllocationIsZero {} + ); + + Ok(parties) + } +} + +#[clocked] +#[covenant_holder_distribute] +#[covenant_holder_emergency_withdraw] +#[cw_serde] +pub enum ExecuteMsg { + /// initiate the ragequit + Ragequit {}, + /// withdraw the liquidity party is entitled to + Claim {}, + /// distribute any unspecified denoms + DistributeFallbackSplit { denoms: Vec }, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + next_contract: Option, + emergency_committee: Option, + lockup_config: Option, + deposit_deadline: Option, + ragequit_config: Box>, + covenant_config: Box>, + denom_splits: Option>, + fallback_split: Option, + }, + UpdateCodeId { + data: Option, + }, +} + +#[cw_serde] +pub enum ContractState { + /// contract is instantiated and awaiting for deposits from + /// both parties involved + Instantiated, + /// funds have been forwarded to the LP module. from the perspective + /// of this contract that indicates an active LP position. + Active, + /// one of the parties have initiated ragequit. the remaining + /// counterparty with an active position is free to exit at any time. + Ragequit, + /// covenant has reached its expiration date. + Expired, + /// underlying funds have been withdrawn. + Complete, +} + +impl ContractState { + pub fn validate_claim_state(&self) -> Result<(), ContractError> { + match self { + ContractState::Ragequit => Ok(()), + ContractState::Expired => Ok(()), + _ => Err(ContractError::ClaimError {}), + } + } + + pub fn complete_and_dequeue(deps: DepsMut, clock_addr: &str) -> Result { + CONTRACT_STATE.save(deps.storage, &ContractState::Complete)?; + dequeue_msg(clock_addr) + } +} + +impl fmt::Display for ContractState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ContractState::Instantiated => write!(f, "instantiated"), + ContractState::Active => write!(f, "active"), + ContractState::Ragequit => write!(f, "ragequit"), + ContractState::Expired => write!(f, "expired"), + ContractState::Complete => write!(f, "complete"), + } + } +} + +#[covenant_clock_address] +#[covenant_next_contract] +#[covenant_deposit_address] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(ContractState)] + ContractState {}, + #[returns(RagequitConfig)] + RagequitConfig {}, + #[returns(Expiration)] + LockupConfig {}, + #[returns(TwoPartyPolCovenantParty)] + ConfigPartyA {}, + #[returns(TwoPartyPolCovenantParty)] + ConfigPartyB {}, + #[returns(Expiration)] + DepositDeadline {}, + #[returns(TwoPartyPolCovenantConfig)] + Config {}, + #[returns(DenomSplits)] + DenomSplits {}, + #[returns(Addr)] + EmergencyCommittee {}, +} + +#[cw_serde] +pub enum RagequitConfig { + /// ragequit is disabled + Disabled, + /// ragequit is enabled with `RagequitTerms` + Enabled(RagequitTerms), +} + +impl RagequitConfig { + pub fn get_response_attributes(&self) -> Vec { + match self { + RagequitConfig::Disabled => vec![Attribute::new("ragequit_config", "disabled")], + RagequitConfig::Enabled(c) => vec![ + Attribute::new("ragequit_config", "enabled"), + Attribute::new("ragequit_penalty", c.penalty.to_string()), + ], + } + } + + pub fn validate( + &self, + a_allocation: Decimal, + b_allocation: Decimal, + ) -> Result<(), ContractError> { + match self { + RagequitConfig::Disabled => Ok(()), + RagequitConfig::Enabled(terms) => { + // first we validate the range: [0.00, 1.00) + if terms.penalty >= Decimal::one() || terms.penalty < Decimal::zero() { + return Err(ContractError::RagequitPenaltyRangeError {}); + } + // then validate that rq penalty does not exceed either party allocations + if terms.penalty > a_allocation || terms.penalty > b_allocation { + return Err(ContractError::RagequitPenaltyExceedsPartyAllocationError {}); + } + + Ok(()) + } + } + } +} + +#[cw_serde] +pub struct RagequitTerms { + /// decimal based penalty to be applied on a party + /// for initiating ragequit. Must be in the range of (0.00, 1.00). + /// Also must not exceed either party allocations in raw values. + pub penalty: Decimal, + /// optional rq state. none indicates no ragequit. + /// some holds the ragequit related config + pub state: Option, +} + +#[cw_serde] +pub struct RagequitState { + pub coins: Vec, + pub rq_party: TwoPartyPolCovenantParty, +} diff --git a/contracts/two-party-pol-holder/src/state.rs b/contracts/two-party-pol-holder/src/state.rs new file mode 100644 index 00000000..1055cf92 --- /dev/null +++ b/contracts/two-party-pol-holder/src/state.rs @@ -0,0 +1,48 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; +use cw_storage_plus::Item; +use cw_utils::Expiration; + +use crate::msg::{ + ContractState, DenomSplits, RagequitConfig, RagequitTerms, TwoPartyPolCovenantConfig, +}; + +pub const CONTRACT_STATE: Item = Item::new("contract_state"); + +/// authorized clock contract +pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); + +/// address of the liquidity pool to which we provide liquidity +pub const LIQUID_POOLER_ADDRESS: Item = Item::new("pooler_address"); + +/// configuration describing the lockup period after which parties are +/// no longer subject to ragequit penalties in order to exit their position +pub const LOCKUP_CONFIG: Item = Item::new("lockup_config"); + +/// configuration describing the deposit period during which parties +/// are expected to fulfill their parts of the covenant +pub const DEPOSIT_DEADLINE: Item = Item::new("deposit_deadline"); + +/// configuration describing the penalty applied to the allocation +/// of the party initiating the ragequit +pub const RAGEQUIT_CONFIG: Item = Item::new("ragequit_config"); + +/// configuration storing both parties information +pub const COVENANT_CONFIG: Item = Item::new("covenant_config"); + +/// stores the configuration describing how to distribute every denom +pub const DENOM_SPLITS: Item = Item::new("denom_splits"); + +pub const WITHDRAW_STATE: Item = Item::new("withdraw_state"); + +#[cw_serde] +pub enum WithdrawState { + Processing { + claimer_addr: String, + }, + ProcessingRagequit { + claimer_addr: String, + terms: RagequitTerms, + }, + Emergency {}, +} diff --git a/interchaintest/README.md b/interchaintest/README.md new file mode 100644 index 00000000..070e612f --- /dev/null +++ b/interchaintest/README.md @@ -0,0 +1,31 @@ +# covenant interchaintest + +Covenant e2e testing is done using the interchaintest framework. + +`utils/` contains various functions and types for bootstrapping our testing environment. + +`swap/` and `two-party-pol/` contains the actual e2e tests. + +Tests can be ran by using our `just` helpers. There are two main recipes: + +### `local-e2e-rebuild TEST PATTERN='.*': optimize` + +This recipe rebuilds the contracts using `wasm-optimizer`. +Resulting wasm files are then copied over into our test directory and tests are ran. + +Pattern is an optional parameter for running a specific test. +E.g. to run the two party pol test involving a native and interchain party, run the following: +```sh +just local-e2e-rebuild two-party-pol TestTwoPartyNativePartyPol +``` + +### `local-e2e TEST PATTERN='.*':` + +This recipe does not rebuild the contracts. Instead, existing wasm files found under `artifacts/` directory are used. +This can be used in cases where you only changed the interchaintest code so that you do not need to wait for contracts to be rebuilt and optimized. + +Pattern is an optional parameter for running a specific test. +E.g. to run the two party pol test involving a native and interchain party, run the following: +```sh +just local-e2e two-party-pol TestTwoPartyNativePartyPol +``` diff --git a/stride-covenant/tests/interchaintest/go.mod b/interchaintest/go.mod similarity index 71% rename from stride-covenant/tests/interchaintest/go.mod rename to interchaintest/go.mod index 80b7e639..e9ea24d2 100644 --- a/stride-covenant/tests/interchaintest/go.mod +++ b/interchaintest/go.mod @@ -1,58 +1,61 @@ -module github.com/timewave-computer/covenants +module github.com/timewave-computer/covenants/interchaintest go 1.20 -// replace block copied from interchaintest v3-ics branch: -// -replace ( - github.com/ChainSafe/go-schnorrkel => github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d - github.com/ChainSafe/go-schnorrkel/1 => github.com/ChainSafe/go-schnorrkel v1.0.0 - github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.45.11-ics - github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 - github.com/vedhavyas/go-subkey => github.com/strangelove-ventures/go-subkey v1.0.7 -) - require ( - github.com/cosmos/cosmos-sdk v0.45.15 - github.com/cosmos/ibc-go/v3 v3.4.0 - github.com/icza/dyno v0.0.0-20220812133438-f0b6f8a18845 - github.com/strangelove-ventures/interchaintest/v3 v3.0.0-20230424185430-002b69e57bc7 - github.com/stretchr/testify v1.8.2 - go.uber.org/zap v1.23.0 + github.com/cosmos/cosmos-sdk v0.45.16 + github.com/cosmos/ibc-go/v4 v4.4.2 + github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0 + github.com/strangelove-ventures/interchaintest/v4 v4.0.0-20230316161044-8d8c01f96b4a + github.com/stretchr/testify v1.8.4 + go.uber.org/zap v1.24.0 ) require ( + cosmossdk.io/api v0.2.6 // indirect; indirectq + cosmossdk.io/core v0.5.1 // indirect + cosmossdk.io/depinject v1.0.0-alpha.3 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect github.com/BurntSushi/toml v1.2.1 // indirect github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect github.com/ChainSafe/go-schnorrkel/1 v0.0.0-00010101000000-000000000000 // indirect + github.com/CosmWasm/wasmvm v1.5.0 + github.com/DataDog/zstd v1.5.0 // indirect + github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/StirlingMarketingGroup/go-namecase v1.0.0 // indirect - github.com/armon/go-metrics v0.4.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/avast/retry-go/v4 v4.0.4 // indirect github.com/benbjohnson/clock v1.3.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bgentry/speakeasy v0.1.0 // indirect - github.com/btcsuite/btcd v0.22.1 // indirect + github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect + github.com/btcsuite/btcd v0.23.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.4 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/confio/ics23/go v0.7.0 // indirect + github.com/cockroachdb/errors v1.9.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v0.0.0-20220817183557-09c6e030a677 // indirect + github.com/cockroachdb/redact v1.1.3 // indirect + github.com/confio/ics23/go v0.9.0 // indirect github.com/cosmos/btcutil v1.0.4 // indirect + github.com/cosmos/cosmos-db v0.0.0-20221226095112-f3c38ecb5e32 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.1 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect - github.com/cosmos/iavl v0.19.4 // indirect - github.com/cosmos/interchain-security v1.0.0-rc2 // indirect - github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect - github.com/cosmos/ledger-go v0.9.3 // indirect + github.com/cosmos/iavl v0.19.5 // indirect + github.com/cosmos/interchain-security v1.0.0 // indirect + github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set v1.8.0 // indirect github.com/decred/base58 v1.0.3 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect @@ -65,9 +68,11 @@ require ( github.com/ethereum/go-ethereum v1.10.17 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/getsentry/sentry-go v0.17.0 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/gateway v1.1.0 // indirect @@ -76,6 +81,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/mux v1.8.0 // indirect @@ -86,19 +92,23 @@ require ( github.com/gtank/merlin v0.1.1 // indirect github.com/gtank/ristretto255 v0.1.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/ipfs/go-cid v0.0.7 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/compress v1.15.11 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-libp2p-core v0.15.1 // indirect github.com/libp2p/go-openssl v0.0.7 // indirect + github.com/linxGnu/grocksdb v1.7.10 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect @@ -115,22 +125,25 @@ require ( github.com/multiformats/go-multicodec v0.4.1 // indirect github.com/multiformats/go-multihash v0.1.0 // indirect github.com/multiformats/go-varint v0.0.6 // indirect + github.com/onsi/gomega v1.20.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect + github.com/osmosis-labs/osmosis v1.0.4 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pierrec/xxHash v0.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.13.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/rakyll/statik v0.1.7 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rs/cors v1.8.2 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/sirupsen/logrus v1.9.0 // indirect @@ -138,33 +151,35 @@ require ( github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/cobra v1.6.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.14.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect - github.com/tendermint/btcd v0.1.1 // indirect - github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect github.com/tendermint/go-amino v0.16.0 // indirect - github.com/tendermint/tendermint v0.34.24 // indirect + github.com/tendermint/tendermint v0.34.27 // indirect github.com/tendermint/tm-db v0.6.7 // indirect + github.com/tidwall/btree v1.5.0 // indirect + github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/vedhavyas/go-subkey v1.0.3 // indirect github.com/zondax/hid v0.9.1 // indirect + github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.8.0 // indirect - golang.org/x/crypto v0.1.0 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/mod v0.6.0 // indirect - golang.org/x/net v0.1.0 // indirect + golang.org/x/crypto v0.5.0 // indirect + golang.org/x/exp v0.0.0-20221019170559-20944726eadf // indirect + golang.org/x/mod v0.7.0 // indirect + golang.org/x/net v0.7.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/term v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect - golang.org/x/tools v0.2.0 // indirect - google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e // indirect - google.golang.org/grpc v1.50.1 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + golang.org/x/time v0.1.0 // indirect + golang.org/x/tools v0.4.0 // indirect + google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa // indirect + google.golang.org/grpc v1.52.3 // indirect google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect @@ -182,3 +197,16 @@ require ( modernc.org/strutil v1.1.1 // indirect modernc.org/token v1.0.0 // indirect ) + +// replace block copied from interchaintest v3-ics branch: +// +replace ( + github.com/ChainSafe/go-schnorrkel => github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d + github.com/ChainSafe/go-schnorrkel/1 => github.com/ChainSafe/go-schnorrkel v1.0.0 + github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.22.2 //indirect + github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.45.16-ics + github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 + github.com/tendermint/tendermint => github.com/cometbft/cometbft v0.34.27 + // github.com/tidwall/btree => github.com/tidwall/btree v1.5.0 + github.com/vedhavyas/go-subkey => github.com/strangelove-ventures/go-subkey v1.0.7 +) diff --git a/interchaintest/go.sum b/interchaintest/go.sum new file mode 100644 index 00000000..4a67d37d --- /dev/null +++ b/interchaintest/go.sum @@ -0,0 +1,4098 @@ +4d63.com/gochecknoglobals v0.1.0/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo= +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= +bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= +cloud.google.com/go v0.25.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/firestore v1.8.0/go.mod h1:r3KB8cAdRIe8znzoPWLw8S6gpDVd9treohhn8b09424= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +code.gitea.io/sdk/gitea v0.12.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= +collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= +contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= +contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= +contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= +contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= +contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= +contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= +cosmossdk.io/api v0.2.6 h1:AoNwaLLapcLsphhMK6+o0kZl+D6MMUaHVqSdwinASGU= +cosmossdk.io/api v0.2.6/go.mod h1:u/d+GAxil0nWpl1XnQL8nkziQDIWuBDhv8VnDm/s6dI= +cosmossdk.io/core v0.5.1 h1:vQVtFrIYOQJDV3f7rw4pjjVqc1id4+mE0L9hHP66pyI= +cosmossdk.io/core v0.5.1/go.mod h1:KZtwHCLjcFuo0nmDc24Xy6CRNEL9Vl/MeimQ2aC7NLE= +cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z+zfw= +cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU= +cosmossdk.io/math v1.0.0-beta.4/go.mod h1:An0MllWJY6PxibUpnwGk8jOm+a/qIxlKmL5Zyp9NnaM= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= +filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= +git.sr.ht/~sircmpwn/go-bare v0.0.0-20210406120253-ab86bc2846d9/go.mod h1:BVJwbDfVjCjoFiKrhkei6NdGcZYpkDkdyCdg1ukytRA= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= +github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= +github.com/Abirdcfly/dupword v0.0.7/go.mod h1:K/4M1kj+Zh39d2aotRwypvasonOyAMH1c/IZJzE0dmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= +github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0/go.mod h1:0mMDvQFeLbbn1Wy8P2j3hwFhqBq+FKn8OZPno8WLmp8= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/Antonboom/errname v0.1.7/go.mod h1:g0ONh16msHIPgJSGsecu1G/dcF2hlYR/0SddnIAGavU= +github.com/Antonboom/nilnil v0.1.1/go.mod h1:L1jBqoWM7AOeTD+tSquifKSesRHs4ZdaxvZR+xdJEaI= +github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v19.1.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v42.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= +github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0= +github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v10.15.5+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.1.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.10.2/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= +github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= +github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= +github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= +github.com/CloudyKit/jet/v6 v6.1.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= +github.com/CosmWasm/wasmvm v1.5.0 h1:3hKeT9SfwfLhxTGKH3vXaKFzBz1yuvP8SlfwfQXbQfw= +github.com/CosmWasm/wasmvm v1.5.0/go.mod h1:fXB+m2gyh4v9839zlIXdMZGeLAxqUdYdFQqYsTha2hc= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/DataDog/zstd v1.5.0 h1:+K/VEwIAaPcHiMtQvpLD4lqW7f0Gk3xdYZmI1hD+CXo= +github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmcxJL+UEG2f8cQploZm1mR/v6BW0mU0= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo= +github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= +github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.10/go.mod h1:g5uw8EV2mAlzqe94tfNBNdr89fnbD/n3HV0OhsddkmM= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= +github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= +github.com/Microsoft/hcsshim v0.9.4/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= +github.com/Microsoft/hcsshim/test v0.0.0-20200826032352-301c83a30e7c/go.mod h1:30A5igQ91GEmhYJF8TaRP79pMBOYynRsyOByfVV0dU4= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc= +github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StirlingMarketingGroup/go-namecase v1.0.0 h1:2CzaNtCzc4iNHirR+5ru9OzGg8rQp860gqLBFqRI02Y= +github.com/StirlingMarketingGroup/go-namecase v1.0.0/go.mod h1:ZsoSKcafcAzuBx+sndbxHu/RjDcDTrEdT4UvhniHfio= +github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= +github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= +github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= +github.com/alecthomas/participle/v2 v2.0.0-alpha7 h1:cK4vjj0VSgb3lN1nuKA5F7dw+1s1pWBe5bx7nNCnN+c= +github.com/alecthomas/participle/v2 v2.0.0-alpha7/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA= +github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= +github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= +github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ= +github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs= +github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= +github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= +github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= +github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= +github.com/avast/retry-go/v4 v4.0.4 h1:38hLf0DsRXh+hOF6HbTni0+5QGTNdw9zbaMD7KAO830= +github.com/avast/retry-go/v4 v4.0.4/go.mod h1:HqmLvS2VLdStPCGDFjSuZ9pzlTqVRldCI4w2dO4m1Ms= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.90/go.mod h1:es1KtYUFs7le0xQ3rOihkuoVD90z7D0fR2Qm4S00/gU= +github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= +github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= +github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= +github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= +github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= +github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= +github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= +github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s= +github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U= +github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg= +github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/breml/bidichk v0.2.3/go.mod h1:8u2C6DnAy0g2cEq+k/A2+tr9O1s+vHGxWn0LTc70T2A= +github.com/breml/errchkjson v0.3.0/go.mod h1:9Cogkyv9gcT8HREpzi3TiqBxCqDzo8awa92zSDFcofU= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/btcsuite/btcd v0.22.2 h1:vBZ+lGGd1XubpOWO67ITJpAEsICWhA0YzqkcpkgNBfo= +github.com/btcsuite/btcd v0.22.2/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcec/v2 v2.2.1/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= +github.com/btcsuite/btcd/btcutil v1.1.2/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/bufbuild/buf v1.9.0/go.mod h1:1Q+rMHiMVcfgScEF/GOldxmu4o9TrQ2sQQh58K6MscE= +github.com/bufbuild/connect-go v1.0.0/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I= +github.com/bufbuild/protocompile v0.1.0/go.mod h1:ix/MMMdsT3fzxfw91dvbfzKW3fRRnuPCP47kpAm5m/4= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bwesterb/go-ristretto v1.2.2/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= +github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= +github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.4 h1:G2kCJurlIkguX0oxxI9sPPENuQqMVhIhV9RVkh/dpDg= +github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.4/go.mod h1:5g1oM4Zu3BOaLpsKQ+O8PAv2kNuq+kPcA1VzFbsSqxE= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= +github.com/chavacava/garif v0.0.0-20220630083739-93517212f375/go.mod h1:4m1Rv7xfuwWPNKXlThldNuJvutYM6J95wNuuVmn55To= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.1/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw= +github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/apd/v3 v3.1.0 h1:MK3Ow7LH0W8zkd5GMKA1PvS9qG3bWFI95WaVNfyZJ/w= +github.com/cockroachdb/apd/v3 v3.1.0/go.mod h1:6qgPBMXjATAdD/VefbRP9NoSLKjbB4LCoA7gN4LpHs4= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4= +github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= +github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= +github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= +github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v0.0.0-20220817183557-09c6e030a677 h1:qbb/AE938DFhOajUYh9+OXELpSF9KZw2ZivtmW6eX1Q= +github.com/cockroachdb/pebble v0.0.0-20220817183557-09c6e030a677/go.mod h1:890yq1fUb9b6dGNwssgeUO5vQV9qfXnCPxAJhBQfXw0= +github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= +github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= +github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= +github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA= +github.com/coinbase/rosetta-sdk-go v0.7.9/go.mod h1:0/knutI7XGVqXmmH4OQD8OckFrbQ8yMsUZTG7FXCR2M= +github.com/cometbft/cometbft v0.34.27 h1:ri6BvmwjWR0gurYjywcBqRe4bbwc3QVs9KRcCzgh/J0= +github.com/cometbft/cometbft v0.34.27/go.mod h1:BcCbhKv7ieM0KEddnYXvQZR+pZykTKReJJYf7YC7qhw= +github.com/cometbft/cometbft-db v0.7.0 h1:uBjbrBx4QzU0zOEnU8KxoDl18dMNgDh+zZRUE0ucsbo= +github.com/cometbft/cometbft-db v0.7.0/go.mod h1:yiKJIm2WKrt6x8Cyxtq9YTEcIMPcEe4XPxhgX59Fzf0= +github.com/confio/ics23/go v0.6.6/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= +github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= +github.com/confio/ics23/go v0.9.0 h1:cWs+wdbS2KRPZezoaaj+qBleXgUk5WOQFMP3CQFGTr4= +github.com/confio/ics23/go v0.9.0/go.mod h1:4LPZ2NYqnYIVRklaozjNR1FScgDJ2s5Xrp+e/mYVRak= +github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= +github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= +github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= +github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= +github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= +github.com/containerd/containerd v1.6.3-0.20220401172941-5ff8fce1fcc6/go.mod h1:WSt2SnDLAGWlu+Vl+EWay37seZLKqgRt6XLjIMy8SYM= +github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= +github.com/containerd/continuity v0.2.3-0.20220330195504-d132b287edc8/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fuse-overlayfs-snapshotter v1.0.2/go.mod h1:nRZceC8a7dRm3Ao6cJAwuJWPFiBPaibHiFntRUnzhwU= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= +github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= +github.com/containerd/go-cni v1.1.4/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= +github.com/containerd/go-cni v1.1.6/go.mod h1:BWtoWl5ghVymxu6MBjg79W9NZrCRyHIdUtk4cauMe34= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= +github.com/containerd/imgcrypt v1.1.4/go.mod h1:LorQnPtzL/T0IyCeftcsMEO7AqxUDbdO8j/tSUpgxvo= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/stargz-snapshotter v0.0.0-20201027054423-3a04e4c2c116/go.mod h1:o59b3PCKVAf9jjiKtCc/9hLAd+5p/rfhBfm6aBcTEr4= +github.com/containerd/stargz-snapshotter v0.11.3/go.mod h1:2j2EAUyvrLU4D9unYlTIwGhDKQIk74KJ9E71lJsQCVM= +github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= +github.com/containerd/stargz-snapshotter/estargz v0.11.3/go.mod h1:7vRJIcImfY8bpifnMjt+HTJoQxASq7T28MYbP15/Nf0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= +github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= +github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/containers/ocicrypt v1.1.3/go.mod h1:xpdkbVAuaH3WzbEabUd5yDsl9SwJA5pABH85425Es2g= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= +github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= +github.com/cosmos/cosmos-db v0.0.0-20221226095112-f3c38ecb5e32 h1:zlCp9n3uwQieELltZWHRmwPmPaZ8+XoL2Sj+A2YJlr8= +github.com/cosmos/cosmos-db v0.0.0-20221226095112-f3c38ecb5e32/go.mod h1:kwMlEC4wWvB48zAShGKVqboJL6w4zCLesaNQ3YLU2BQ= +github.com/cosmos/cosmos-proto v1.0.0-beta.1 h1:iDL5qh++NoXxG8hSy93FdYJut4XfgbShIocllGaXx/0= +github.com/cosmos/cosmos-proto v1.0.0-beta.1/go.mod h1:8k2GNZghi5sDRFw/scPL8gMSowT1vDA+5ouxL8GjaUE= +github.com/cosmos/cosmos-sdk v0.45.16-ics h1:KsPigLNmdyyQMktAsJzW42eBFsq1uajhQF7rlnHDUgM= +github.com/cosmos/cosmos-sdk v0.45.16-ics/go.mod h1:bScuNwWAP0TZJpUf+SHXRU3xGoUPp+X9nAzfeIXts40= +github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= +github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= +github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= +github.com/cosmos/gogoproto v1.4.3/go.mod h1:0hLIG5TR7IvV1fme1HCFKjfzW9X2x0Mo+RooWXCnOWU= +github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= +github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= +github.com/cosmos/iavl v0.16.0/go.mod h1:2A8O/Jz9YwtjqXMO0CjnnbTYEEaovE8jWcwrakH3PoE= +github.com/cosmos/iavl v0.19.5 h1:rGA3hOrgNxgRM5wYcSCxgQBap7fW82WZgY78V9po/iY= +github.com/cosmos/iavl v0.19.5/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= +github.com/cosmos/ibc-go/v4 v4.4.2 h1:PG4Yy0/bw6Hvmha3RZbc53KYzaCwuB07Ot4GLyzcBvo= +github.com/cosmos/ibc-go/v4 v4.4.2/go.mod h1:j/kD2JCIaV5ozvJvaEkWhLxM2zva7/KTM++EtKFYcB8= +github.com/cosmos/interchain-security v1.0.0 h1:xNQjjigqH3mzEKSGQhAhKy8I0TA8XR2z5rRTxRBKK3o= +github.com/cosmos/interchain-security v1.0.0/go.mod h1:J9SbXUJT1GSe+mZy+MDCxtuAfbhwCKBEJRYnfjXsE8Q= +github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA= +github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= +github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cristalhq/acmd v0.8.1/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ= +github.com/cucumber/common/gherkin/go/v22 v22.0.0 h1:4K8NqptbvdOrjL9DEea6HFjSpbdT9+Q5kgLpmmsHYl0= +github.com/cucumber/common/gherkin/go/v22 v22.0.0/go.mod h1:3mJT10B2GGn3MvVPd3FwR7m2u4tLhSRhWUqJU4KN4Fg= +github.com/cucumber/common/messages/go/v17 v17.1.1 h1:RNqopvIFyLWnKv0LfATh34SWBhXeoFTJnSrgm9cT/Ts= +github.com/cucumber/common/messages/go/v17 v17.1.1/go.mod h1:bpGxb57tDE385Rb2EohgUadLkAbhoC4IyCFi89u/JQI= +github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/daixiang0/gci v0.8.1/go.mod h1:EpVfrztufwVgQRXjnX4zuNinEpLj5OmMjtu/+MB0V0c= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= +github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= +github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= +github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/decred/base58 v1.0.3 h1:KGZuh8d1WEMIrK0leQRM47W85KqCAdl2N+uagbctdDI= +github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= +github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU= +github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 h1:3GIJYXQDAKpLEFriGFN8SbSffak10UXHGdIcFaMPykY= +github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= +github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= +github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= +github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.13+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.14+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.6.0-rc.1.0.20180327202408-83389a148052+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.0.0-20200511152416-a93e9eb0e95c/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20180531152204-71cd53e4a197/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v17.12.0-ce-rc1.0.20200730172259-9f28837c1d93+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.0-beta1.0.20201110211921-af34b94a78a1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.3-0.20211208011758-87521affb077+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.19+incompatible h1:lzEmjivyNHFHMNAFLXORMBXyGIhw/UP4DvJwvyKYq64= +github.com/docker/docker v20.10.19+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libnetwork v0.8.0-dev.2.0.20200917202933-d0951081b35f/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac h1:opbrjaN/L8gg6Xh5D04Tem+8xVcz6ajZlGCs49mQgyg= +github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= +github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= +github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= +github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= +github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= +github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= +github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= +github.com/getsentry/sentry-go v0.17.0 h1:UustVWnOoDFHBS7IJUB2QK/nB5pap748ZEp0swnQJak= +github.com/getsentry/sentry-go v0.17.0/go.mod h1:B82dxtBvxG0KaPD8/hfSV+VcHD+Lg/xUS4JuQn1P4cM= +github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= +github.com/go-critic/go-critic v0.4.3/go.mod h1:j4O3D4RoIwRqlZw5jJpx0BNfXWWbpcJoKu5cYSe4YmQ= +github.com/go-critic/go-critic v0.6.5/go.mod h1:ezfP/Lh7MA6dBNn4c6ab5ALv3sKnZVLx37tr00uuaOY= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= +github.com/go-git/go-git/v5 v5.5.1/go.mod h1:uz5PQ3d0gz7mSgzZhSJToM6ALPaKCdSnl58/Xb5hzr8= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= +github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= +github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astcopy v1.0.2/go.mod h1:4TcEdbElGc9twQEYpVo/aieIXfHhiuLh4aLAck6dO7Y= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.2/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= +github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/pkgload v1.0.2-0.20220101231613-e814995d17c5/go.mod h1:3NAwwmD4uY/yggRxoEjk/S00MIV3A+H7rrE3i87eYxM= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc= +github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= +github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2/go.mod h1:9wOXstvyDRshQ9LggQuzBCGysxs3b6Uo/1MvYCR2NMs= +github.com/golangci/golangci-lint v1.23.7/go.mod h1:g/38bxfhp4rI7zeWSxcdIeHTQGS58TCak8FYcyCmavQ= +github.com/golangci/golangci-lint v1.27.0/go.mod h1:+eZALfxIuthdrHPtfM7w/R3POJLjHDfJJw8XZl9xOng= +github.com/golangci/golangci-lint v1.50.1/go.mod h1:AQjHBopYS//oB8xs0y0M/dtxdKHkdhl0RvmjUct0/4w= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6/go.mod h1:0AKcRCkMoKvUvlf89F6O7H2LYdhr1zBh736mBItOdRs= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= +github.com/google/crfs v0.0.0-20191108021818-71d77da419c9/go.mod h1:etGhoOqfwPkooV6aqoX3eBGQOJblqdoc9XvWOeuxpPw= +github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-containerregistry v0.0.0-20191010200024-a3d713f9b7f8/go.mod h1:KyKXa9ciM8+lgMXwOVsXi7UxGrsf9mM61Mzs+xKUrKE= +github.com/google/go-containerregistry v0.1.2/go.mod h1:GPivBPgdAyd2SU+vf6EpsgOtWDuPqjW0hJZt4rNdTZ4= +github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= +github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= +github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw= +github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= +github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= +github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/goreleaser/goreleaser v0.136.0/go.mod h1:wiKrPUeSNh6Wu8nUHxZydSOVQ/OZvOaO7DTtFqie904= +github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w= +github.com/goreleaser/nfpm v1.3.0/go.mod h1:w0p7Kc9TAUgWMyrub63ex3M2Mgw88M4GZXoTq5UCb40= +github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= +github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI= +github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= +github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= +github.com/gotestyourself/gotestyourself v1.4.0/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= +github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= +github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= +github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= +github.com/hanwen/go-fuse/v2 v2.0.3/go.mod h1:0EQM6aH2ctVpvZ6a+onrQ/vaykxh2GH7hy3e13vzTUY= +github.com/hanwen/go-fuse/v2 v2.1.1-0.20220112183258-f57e95bda82d/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= +github.com/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86++L0KbeAMXYCeY= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/consul/sdk v0.11.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= +github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.9.8/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0= +github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= +github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= +github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0 h1:nHoRIX8iXob3Y2kdt9KsjyIb7iApSvb3vgsd93xb5Ow= +github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0/go.mod h1:c1tRKs5Tx7E2+uHGSyyncziFjvGpgv4H2HrqXeUQ/Uk= +github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/improbable-eng/grpc-web v0.14.1/go.mod h1:zEjGHa8DAlkoOXmswrNvhUGEYQA9UI7DhrGeHR1DMGU= +github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= +github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= +github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= +github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= +github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= +github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= +github.com/informalsystems/tm-load-test v1.3.0/go.mod h1:OQ5AQ9TbT5hKWBNIwsMjn6Bf4O0U4b1kRc+0qZlQJKw= +github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= +github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= +github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/httpexpect/v2 v2.3.1/go.mod h1:ICTf89VBKSD3KB0fsyyHviKF8G8hyepP0dOXJPWz3T0= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= +github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= +github.com/iris-contrib/jade v1.1.4/go.mod h1:EDqR+ur9piDl6DUgs6qRrlfzmlx/D5UybogqrXvJTBE= +github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= +github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3YhEW2BmA1wgGpPYI3HZy/5gD705PXKUVSg= +github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jdxcode/netrc v0.0.0-20210204082910-926c7f70242a/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw= +github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= +github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= +github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= +github.com/jhump/protoreflect v1.13.1-0.20220928232736-101791cb1b4c/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw= +github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kataras/blocks v0.0.6/go.mod h1:UK+Iwk0Oxpc0GdoJja7sEildotAUKK1LYeYcVF0COWc= +github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= +github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= +github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= +github.com/kataras/golog v0.1.7/go.mod h1:jOSQ+C5fUqsNSwurB/oAHq1IFSb0KI3l6GMa7xB6dZA= +github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= +github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= +github.com/kataras/iris/v12 v12.2.0-beta5/go.mod h1:q26aoWJ0Knx/00iPKg5iizDK7oQQSPjbD8np0XDh6dc= +github.com/kataras/jwt v0.1.8/go.mod h1:Q5j2IkcIHnfwy+oNY3TVWuEBJNw0ADgCcXK9CaZwV4o= +github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= +github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= +github.com/kataras/neffos v0.0.20/go.mod h1:srdvC/Uo8mgrApWW0AYtiiLgMbyNPf69qPsd2FhE6MQ= +github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= +github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= +github.com/kataras/pio v0.0.10/go.mod h1:gS3ui9xSD+lAUpbYnjOGiQyY7sUMJO+EHpiRzhtZ5no= +github.com/kataras/pio v0.0.11/go.mod h1:38hH6SWH6m4DKSYmRhlrCJ5WItwWgCVrTNU62XZyUvI= +github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= +github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= +github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/errcheck v1.6.2/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkHAIKE/contextcheck v1.1.3/go.mod h1:PG/cwd6c0705/LM0KTr1acO2gORUxkSVWyLJOFW5qoo= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= +github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= +github.com/kunwardeep/paralleltest v1.0.6/go.mod h1:Y0Y0XISdZM5IKm3TREQMZ6iteqn1YuwCsJO/0kL9Zes= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= +github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= +github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= +github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= +github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= +github.com/ldez/tagliatelle v0.3.1/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leonklingele/grouper v1.1.0/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= +github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= +github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/libp2p/go-libp2p-core v0.15.1 h1:0RY+Mi/ARK9DgG1g9xVQLb8dDaaU8tCePMtGALEfBnM= +github.com/libp2p/go-libp2p-core v0.15.1/go.mod h1:agSaboYM4hzB1cWekgVReqV5M4g5M+2eNNejV+1EEhs= +github.com/libp2p/go-openssl v0.0.7 h1:eCAzdLejcNVBzP/iZM9vqHnQm+XyCEbSSIheIPRGNsw= +github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= +github.com/linxGnu/grocksdb v1.7.10 h1:dz7RY7GnFUA+GJO6jodyxgkUeGMEkPp3ikt9hAcNGEw= +github.com/linxGnu/grocksdb v1.7.10/go.mod h1:0hTf+iA+GOr0jDX4CgIYyJZxqOH9XlBh6KVj8+zmF34= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= +github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailgun/raymond/v2 v2.0.46/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= +github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= +github.com/maratori/testpackage v1.1.0/go.mod h1:PeAhzU8qkCwdGEMTEupsHJNlQu2gZopMC6RjbhmHeDc= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= +github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= +github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= +github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= +github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= +github.com/mediocregopher/radix/v3 v3.8.0/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +github.com/mgechev/revive v1.2.4/go.mod h1:iAWlQishqCuj4yhV24FTnKSXGpbAA+0SckXB8GQMX/Q= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= +github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= +github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 h1:QRUSJEgZn2Snx0EmT/QLXibWjSUDjKWvXIT19NBVp94= +github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= +github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= +github.com/moby/buildkit v0.10.4/go.mod h1:Yajz9vt1Zw5q9Pp4pdb3TCSUXJBIroIQGQ3TTs/sLug= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= +github.com/moby/sys/mount v0.1.1/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= +github.com/moby/sys/mount v0.3.0/go.mod h1:U2Z3ur2rXPFrFmy4q6WMwWrBOAQGYtYTRVM8BIvzbwk= +github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= +github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/mountinfo v0.6.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2/go.mod h1:TjQg8pa4iejrUrjiz0MCtMV38jdMNW4doKSiBrEvCQQ= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= +github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= +github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= +github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= +github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= +github.com/multiformats/go-multiaddr v0.4.1 h1:Pq37uLx3hsyNlTDir7FZyU8+cFCTqd5y1KiM2IzOutI= +github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= +github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= +github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= +github.com/multiformats/go-multicodec v0.4.1 h1:BSJbf+zpghcZMZrwTYBGwy0CPcVZGWiC72Cp8bBd4R4= +github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= +github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= +github.com/multiformats/go-multihash v0.1.0 h1:CgAgwqk3//SVEw3T+6DqI4mWMyRuDwZtOWcJT0q9+EA= +github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= +github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= +github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= +github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= +github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= +github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= +github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= +github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/jwt/v2 v2.3.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g= +github.com/nats-io/nats-server/v2 v2.8.4/go.mod h1:8zZa+Al3WsESfmgSs98Fi06dRWLH5Bnq90m5bKD/eT4= +github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.15.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nats.go v1.16.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/neilotoole/errgroup v0.1.6/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE= +github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nishanths/exhaustive v0.8.3/go.mod h1:qj+zJJUgJ76tR92+25+03oYUhzF4R7/2Wk7fGTfCHmg= +github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= +github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.0/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc92/go.mod h1:X1zlU4p7wOlX4+WRCz+hvlRv8phdL7UqbYD+vQwNMmE= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opencontainers/selinux v1.10.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= +github.com/osmosis-labs/osmosis v1.0.4/go.mod h1:ENV3qCKuW18bWUQASaBZa7JyZQQ/ux3qZrvNpXs8AdQ= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= +github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/oxyno-zeta/gomock-extra-matcher v1.1.0 h1:Yyk5ov0ZPKBXtVEeIWtc4J2XVrHuNoIK+0F2BUJgtsc= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= +github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/xxHash v0.1.5 h1:n/jBpwTHiER4xYvK3/CdPVnLDPchj8eTJFFLUb4QHBo= +github.com/pierrec/xxHash v0.1.5/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I= +github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0/go.mod h1:4xpMLz7RBWyB+ElzHu8Llua96TRCB3YwX+l5EP1wmHk= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pjbgf/sha1cd v0.2.3/go.mod h1:HOK9QrgzdHpbc2Kzip0Q1yi3M2MFGPADtR6HjG65m5M= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= +github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polyfloyd/go-errorlint v1.0.5/go.mod h1:APVvOesVSAnne5SClsPxPdfvZTVDojXh1/G3qb5wjGI= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= +github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= +github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1:KsAh3x0e7Fkpgs+Q9pNLS5XpFSvYCEVl5gP9Pp1xp30= +github.com/quasilyte/go-ruleguard v0.3.18/go.mod h1:lOIzcYlgxrQ2sGJ735EHXmf/e9MJ516j16K/Ifcttvs= +github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/dsl v0.3.21/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= +github.com/quasilyte/gogrep v0.0.0-20220828223005-86e4605de09f/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= +github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= +github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/regen-network/cosmos-proto v0.3.1 h1:rV7iM4SSFAagvy8RiyhiACbWEGotmqzywPxOvwMdxcg= +github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNwWejXC7QqCOnH3O0+YM= +github.com/regen-network/gocuke v0.6.2 h1:pHviZ0kKAq2U2hN2q3smKNxct6hS0mGByFMHGnWA97M= +github.com/regen-network/gocuke v0.6.2/go.mod h1:zYaqIHZobHyd0xOrHGPQjbhGJsuZ1oElx150u2o1xuk= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/go-dbus v0.0.0-20121104212943-b7232d34b1d5/go.mod h1:+u151txRmLpwxBmpYn9z3d1sdJdjRPQpsXuYeY9jNls= +github.com/remyoudompheng/go-liblzma v0.0.0-20190506200333-81bf2d431b96/go.mod h1:90HvCY7+oHHUKkbeMCiHt1WuFR2/hPJ9QrljDG+v6ls= +github.com/remyoudompheng/go-misc v0.0.0-20190427085024-2d6ac652a50e/go.mod h1:80FQABjoFzZ2M5uEa6FUaJYEmqU2UOKojlFVak1UAwI= +github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= +github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= +github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= +github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.0.4/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE= +github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM= +github.com/ryancurrah/gomodguard v1.2.4/go.mod h1:+Kem4VjWwvFpUJRJSwa16s1tBJe+vbv02+naTow2f6M= +github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= +github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= +github.com/sagikazarmark/crypt v0.8.0/go.mod h1:TmKwZAo97S4Fy4sfMH/HX/cQP5D+ijra2NyLpNNmttY= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= +github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= +github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= +github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= +github.com/sashamelentyev/usestdlibvars v1.20.0/go.mod h1:0GaP+ecfZMXShS0A94CJn6aEuPRILv8h/VuWI9n1ygg= +github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= +github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A= +github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME= +github.com/securego/gosec/v2 v2.13.1/go.mod h1:EO1sImBMBWFjOTFzMWfTRrZW6M15gm60ljzrmy/wtHo= +github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= +github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI= +github.com/shirou/gopsutil/v3 v3.22.9/go.mod h1:bBYl1kjgEJpWpxeHmLI+dVHWtyAwfcmSBLDsp2TNT8A= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sivchari/containedctx v1.0.2/go.mod h1:PwZOeqm4/DLoJOqMSIJs3aKqXRX4YO+uXww087KZ7Bw= +github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= +github.com/sivchari/tenv v1.7.0/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= +github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/assertions v1.13.0/go.mod h1:wDmR7qL282YbGsPy6H/yAsesrxfxaaSlJazyFLYVFx8= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= +github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= +github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= +github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= +github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= +github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= +github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/strangelove-ventures/go-subkey v1.0.7 h1:cOP/Lajg3uxV/tvspu0m6+0Cu+DJgygkEAbx/s+f35I= +github.com/strangelove-ventures/go-subkey v1.0.7/go.mod h1:E34izOIEm+sZ1YmYawYRquqBQWeZBjVB4pF7bMuhc1c= +github.com/strangelove-ventures/interchaintest/v4 v4.0.0-20230316161044-8d8c01f96b4a h1:ReDdhlzY19zH7Ql6aPUsxnO7H5H/HT5PcfXtkn2OWPc= +github.com/strangelove-ventures/interchaintest/v4 v4.0.0-20230316161044-8d8c01f96b4a/go.mod h1:1iRRVEhAIGtYMl7/UVEwGx7O1tN4x8C+SzS/MBpi+JY= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= +github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tdakkota/asciicheck v0.1.1/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tdewolff/minify/v2 v2.12.1/go.mod h1:p5pwbvNs1ghbFED/ZW1towGsnnWwzvM8iz8l0eURi9g= +github.com/tdewolff/minify/v2 v2.12.4/go.mod h1:h+SRvSIX3kwgwTFOpSckvSxgax3uy8kZTSF1Ojrr3bk= +github.com/tdewolff/parse/v2 v2.6.3/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= +github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= +github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= +github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= +github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tendermint/tm-db v0.6.4/go.mod h1:dptYhIpJ2M5kUuenLr+Yyf3zQOv1SgBZcl8/BmWlMBw= +github.com/tendermint/tm-db v0.6.6/go.mod h1:wP8d49A85B7/erz/r4YbKssKw6ylsO/hKtFk7E1aWZI= +github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= +github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/tetafro/godot v0.3.7/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= +github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= +github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= +github.com/tidwall/btree v1.5.0 h1:iV0yVY/frd7r6qGBXfEYs7DH0gTDgrKTrDjS7xt/IyQ= +github.com/tidwall/btree v1.5.0/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/timonwong/loggercheck v0.9.3/go.mod h1:wUqnk9yAOIKtGA39l1KLE9Iz0QiTocu/YZoOf+OzFdw= +github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= +github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= +github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= +github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= +github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= +github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= +github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= +github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tomarrell/wrapcheck/v2 v2.7.0/go.mod h1:ao7l5p0aOlUNJKI0qVwB4Yjlqutd0IvAB9Rdwyilxvg= +github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo= +github.com/tonistiigi/fsutil v0.0.0-20220115021204-b19f7f9cb274/go.mod h1:oPAfvw32vlUJSjyDcQ3Bu0nb2ON2B+G0dtVN/SZNJiA= +github.com/tonistiigi/go-actions-cache v0.0.0-20220404170428-0bdeb6e1eac7/go.mod h1:qqvyZqkfwkoJuPU/bw61bItaoO0SJ8YSW0vSVRRvsRg= +github.com/tonistiigi/go-archvariant v1.0.0/go.mod h1:TxFmO5VS6vMq2kvs3ht04iPXtu2rUT/erOnGFYfk5Ho= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= +github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= +github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= +github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= +github.com/vektra/mockery/v2 v2.14.0/go.mod h1:bnD1T8tExSgPD1ripLkDbr60JA9VtQeu12P3wgLZd7M= +github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= +github.com/xanzy/go-gitlab v0.32.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= +github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= +github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= +github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.1 h1:Pip65OOl4iJ84WTpA4BKChvOufMhhbxED3BaihoZN4c= +github.com/zondax/ledger-go v0.14.1/go.mod h1:fZ3Dqg6qcdXWSOJFKMG8GCTnD7slO/RL2feOQv8K320= +gitlab.com/bosi/decorder v0.2.3/go.mod h1:9K1RB5+VPNQYtXtTDAzd2OEftsZb1oV0IrJrzChSdGE= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= +go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= +go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= +go.etcd.io/etcd/client/v2 v2.305.5/go.mod h1:zQjKllfqfBVyVStbt4FaosoX2iYd8fV/GRy/PbowgP4= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= +go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= +go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= +go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0/go.mod h1:LsankqVDx4W+RhZNA5uWarULII/MBhF5qwCYxTuyXjs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.3/go.mod h1:Dts42MGkzZne2yCru741+bFiTMWkIj/LLRizad7b9tw= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.29.0/go.mod h1:vHItvsnJtp7ES++nFLLFBzUWny7fJQSvTlxFcqQGUr4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.29.0/go.mod h1:tLYsuf2v8fZreBVwp9gVMhefZlLFZaUiNVSq8QxXRII= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= +go.opentelemetry.io/otel v1.4.0/go.mod h1:jeAqMFKy2uLIxCtKxoFj0FAL5zAPKQagc3+GtBWakzk= +go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= +go.opentelemetry.io/otel v1.11.0/go.mod h1:H2KtuEphyMvlhZ+F7tg9GRhAOe60moNx61Ex+WmiKkk= +go.opentelemetry.io/otel/exporters/jaeger v1.4.1/go.mod h1:ZW7vkOu9nC1CxsD8bHNHCia5JUbwP39vxgd1q4Z5rCI= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.4.1/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.4.1/go.mod h1:o5RW5o2pKpJLD5dNTCmjF1DorYwMeFJmb/rKr5sLaa8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.1/go.mod h1:c6E4V3/U+miqjs/8l950wggHGL1qzlp0Ypj9xoGrPqo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.4.1/go.mod h1:VwYo0Hak6Efuy0TXsZs8o1hnV3dHDPNtDbycG0hI8+M= +go.opentelemetry.io/otel/internal/metric v0.27.0/go.mod h1:n1CVxRqKqYZtqyTh9U/onvKapPGv7y/rpyOTI+LFNzw= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/metric v0.27.0/go.mod h1:raXDJ7uP2/Jc0nVZWQjJtzoyssOYWu/+pjZqRzfvZ7g= +go.opentelemetry.io/otel/metric v0.32.3/go.mod h1:pgiGmKohxHyTPHGOff+vrtIH39/R9fiO/WoenUQ3kcc= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= +go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= +go.opentelemetry.io/otel/trace v1.4.0/go.mod h1:uc3eRsqDfWs9R7b92xbQbU42/eTNz4N+gLP8qJCi4aE= +go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= +go.opentelemetry.io/otel/trace v1.11.0/go.mod h1:nyYjis9jy0gytE9LXGU+/m1sHTKbRY0fX0hulNNDP1U= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= +go.opentelemetry.io/proto/otlp v0.12.0/go.mod h1:TsIjwGWIx5VFYv9KGVlOpxoBl5Dy+63SUguV7GGvlSQ= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI= +golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20221019170559-20944726eadf h1:nFVjjKDgNY37+ZSYCJmtYf7tOlfQswHqplG2eosjOMg= +golang.org/x/exp v0.0.0-20221019170559-20944726eadf/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20220613132600-b0d781184e0d/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221017152216-f25eb7ecb193/go.mod h1:RpDiru2p0u2F0lLpEoqnP2+7xs0ifAuOcJ442g6GU2s= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210909193231-528a39cd75f3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190228203856-589c23e65e65/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200102140908-9497f49d5709/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204192400-7124308813f3/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201028025901-8cd080b735b3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.1.12-0.20220628192153-7743d1d949f1/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= +google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201119123407-9b1e624d6bc4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210114201628-6edceaf6022f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa h1:qQPhfbPO23fwm/9lQr91L1u62Zo6cm+zI+slZT+uf+o= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= +google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.6/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= +gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= +k8s.io/api v0.0.0-20180904230853-4e7be11eab3f/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA= +k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= +k8s.io/api v0.23.4/go.mod h1:i77F4JfyNNrhOjZF7OwwNJS5Y1S9dpwvb9iYRYRczfI= +k8s.io/apimachinery v0.0.0-20180904193909-def12e63c512/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g= +k8s.io/apimachinery v0.19.0/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= +k8s.io/apimachinery v0.23.4/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= +k8s.io/client-go v0.0.0-20180910083459-2cefa64ff137/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc= +k8s.io/client-go v0.19.0/go.mod h1:H9E/VT95blcFQnlyShFgnFT9ZnJOAceiUHM3MlRC+mU= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= +k8s.io/client-go v0.23.4/go.mod h1:PKnIL4pqLuvYUK1WU7RLTMYKPiIh7MYShLshtRY9cj0= +k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KNK9U= +k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= +k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= +k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= +k8s.io/cri-api v0.24.0-alpha.3/go.mod h1:c/NLI5Zdyup5+oEYqFO2IE32ptofNiZpS1nL2y51gAg= +k8s.io/csi-translation-lib v0.17.4/go.mod h1:CsxmjwxEI0tTNMzffIAcgR9lX4wOh6AKHdxQrT7L0oo= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= +lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6 h1:3l18poV+iUemQ98O3X5OMr97LOqlzis+ytivU4NqGhA= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.7 h1:qzQtHhsZNpVPpeCu+aMIQldXeV1P0vRhSqCL0nOIJOA= +modernc.org/libc v1.16.7/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.17.3 h1:iE+coC5g17LtByDYDWKpR6m2Z9022YrSh3bumwOnIrI= +modernc.org/sqlite v1.17.3/go.mod h1:10hPVYar9C0kfXuTWGz8s0XtB8uAGymUy51ZzStYe3k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= +mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= +mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc= +mvdan.cc/unparam v0.0.0-20220706161116-678bad134442/go.mod h1:F/Cxw/6mVrNKqrR2YjFf5CaW0Bw4RL8RfbEf4GRggJk= +nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= +pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= +pgregory.net/rapid v0.5.2/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +pgregory.net/rapid v0.5.3 h1:163N50IHFqr1phZens4FQOdPgfJscR7a562mjQqeo4M= +pgregory.net/rapid v0.5.3/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= +sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4= diff --git a/interchaintest/single-party-pol/README.md b/interchaintest/single-party-pol/README.md new file mode 100644 index 00000000..972b3d82 --- /dev/null +++ b/interchaintest/single-party-pol/README.md @@ -0,0 +1 @@ +# single party pol e2e tests diff --git a/interchaintest/single-party-pol/chains.yaml b/interchaintest/single-party-pol/chains.yaml new file mode 100644 index 00000000..06ba69b9 --- /dev/null +++ b/interchaintest/single-party-pol/chains.yaml @@ -0,0 +1,59 @@ +## Set the environment variable: IBCTEST_CONFIGURED_CHAINS to a path +## to use custom versions of this file + +gaia: + name: gaia + type: cosmos + bin: gaiad + bech32-prefix: cosmos + denom: uatom + gas-prices: 0.01uatom + gas-adjustment: 1.3 + trusting-period: 504h + images: + - repository: ghcr.io/strangelove-ventures/heighliner/gaia + uid-gid: 1025:1025 + no-host-mount: false + +neutron: + name: neutron + type: cosmos + bin: neutrond + bech32-prefix: neutron + denom: untrn + gas-prices: 0.01untrn + gas-adjustment: 1.3 + trusting-period: 336h + images: + - repository: ghcr.io/strangelove-ventures/heighliner/neutron + uid-gid: 1025:1025 + no-host-mount: false + +persistence: + name: persistence + type: cosmos + bin: persistenceCore + bech32-prefix: persistence + denom: uxprt + gas-prices: 0.01uxprt + gas-adjustment: 1.3 + coin-type: 750 + trusting-period: "504h" + images: + - repository: ghcr.io/strangelove-ventures/heighliner/persistence + uid-gid: 1025:1025 + no-host-mount: false + +stride: + name: stride + type: cosmos + bin: strided + bech32-prefix: stride + denom: ustrd + gas-prices: 0.01ustrd + gas-adjustment: 1.3 + trusting-period: "336h" + images: + - repository: ghcr.io/strangelove-ventures/heighliner/stride + uid-gid: 1025:1025 + no-host-mount: false diff --git a/interchaintest/single-party-pol/single_party_pol_test.go b/interchaintest/single-party-pol/single_party_pol_test.go new file mode 100644 index 00000000..cbb65130 --- /dev/null +++ b/interchaintest/single-party-pol/single_party_pol_test.go @@ -0,0 +1,1018 @@ +package covenant_single_party_pol + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + ibctest "github.com/strangelove-ventures/interchaintest/v4" + "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v4/ibc" + "github.com/strangelove-ventures/interchaintest/v4/relayer" + "github.com/strangelove-ventures/interchaintest/v4/testreporter" + "github.com/strangelove-ventures/interchaintest/v4/testutil" + "github.com/stretchr/testify/require" + utils "github.com/timewave-computer/covenants/interchaintest/utils" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +const gaiaNeutronICSPath = "gn-ics-path" +const gaiaNeutronIBCPath = "gn-ibc-path" +const gaiaStrideIBCPath = "go-ibc-path" +const neutronStrideIBCPath = "no-ibc-path" +const nativeAtomDenom = "uatom" +const nativeStatomDenom = "stuatom" +const nativeNtrnDenom = "untrn" + +var covenantAddress string +var clockAddress string +var liquidPoolerAddress string +var partyDepositAddress string +var holderAddress string +var liquidStakerAddress string +var lsForwarderAddress string +var remoteChainSplitterAddress string +var liquidPoolerForwarderAddress string +var strideIcaAddress string +var interchainRouterAddress string +var lsForwarderIcaAddress, liquidPoolerForwarderIcaAddress string + +var neutronAtomIbcDenom, neutronStatomIbcDenom, strideAtomIbcDenom, hubStAtomIbcDenom, hubNeutronStAtomIbcDenom string +var atomNeutronICSConnectionId, neutronAtomICSConnectionId string +var neutronStrideIBCConnId, strideNeutronIBCConnId string +var atomNeutronIBCConnId, neutronAtomIBCConnId string +var atomStrideIBCConnId, strideAtomIBCConnId string +var gaiaStrideIBCConnId, strideGaiaIBCConnId string +var tokenAddress string +var whitelistAddress string +var factoryAddress string +var coinRegistryAddress string +var stableswapAddress string +var liquidityTokenAddress string + +// PARTY_A +const atomContributionAmount uint64 = 5_000_000_000 // in uatom + +// sets up and tests a single party pol by hub +func TestSinglePartyPol(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + os.Setenv("IBCTEST_CONFIGURED_CHAINS", "./chains.yaml") + + ctx := context.Background() + + // Modify the the timeout_commit in the config.toml node files + // to reduce the block commit times. This speeds up the tests + // by about 35% + configFileOverrides := make(map[string]any) + configTomlOverrides := make(testutil.Toml) + consensus := make(testutil.Toml) + consensus["timeout_commit"] = "1s" + configTomlOverrides["consensus"] = consensus + configFileOverrides["config/config.toml"] = configTomlOverrides + + // Chain Factory + cf := ibctest.NewBuiltinChainFactory(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)), []*ibctest.ChainSpec{ + {Name: "gaia", Version: "v9.1.0", ChainConfig: ibc.ChainConfig{ + GasAdjustment: 1.3, + GasPrices: "0.0atom", + ModifyGenesis: utils.SetupGaiaGenesis(utils.GetDefaultInterchainGenesisMessages()), + ConfigFileOverrides: configFileOverrides, + }}, + { + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Name: "neutron", + ChainID: "neutron-2", + Images: []ibc.DockerImage{ + { + Repository: "ghcr.io/strangelove-ventures/heighliner/neutron", + Version: "v2.0.0", + UidGid: "1025:1025", + }, + }, + Bin: "neutrond", + Bech32Prefix: "neutron", + Denom: nativeNtrnDenom, + GasPrices: "0.0untrn,0.0uatom", + GasAdjustment: 1.3, + TrustingPeriod: "1197504s", + NoHostMount: false, + ModifyGenesis: utils.SetupNeutronGenesis( + "0.05", + []string{nativeNtrnDenom}, + []string{nativeAtomDenom}, + utils.GetDefaultNeutronInterchainGenesisMessages(), + ), + ConfigFileOverrides: configFileOverrides, + }, + }, + { + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Name: "stride", + ChainID: "stride-3", + Images: []ibc.DockerImage{ + { + Repository: "stride", + Version: "non-ics", + UidGid: "1025:1025", + }, + }, + Bin: "strided", + Bech32Prefix: "stride", + Denom: "ustrd", + ModifyGenesis: utils.SetupStrideGenesis([]string{ + "/cosmos.bank.v1beta1.MsgSend", + "/cosmos.bank.v1beta1.MsgMultiSend", + "/cosmos.staking.v1beta1.MsgDelegate", + "/cosmos.staking.v1beta1.MsgUndelegate", + "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "/cosmos.staking.v1beta1.MsgRedeemTokensforShares", + "/cosmos.staking.v1beta1.MsgTokenizeShares", + "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress", + "/ibc.applications.transfer.v1.MsgTransfer", + }), + GasPrices: "0.0ustrd", + GasAdjustment: 1.3, + TrustingPeriod: "336h", + NoHostMount: false, + ConfigFileOverrides: configFileOverrides, + }, + }, + }) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + + // We have three chains + atom, neutron, stride := chains[0], chains[1], chains[2] + cosmosAtom, cosmosNeutron, cosmosStride := atom.(*cosmos.CosmosChain), neutron.(*cosmos.CosmosChain), stride.(*cosmos.CosmosChain) + + // Relayer Factory + client, network := ibctest.DockerSetup(t) + r := ibctest.NewBuiltinRelayerFactory( + ibc.CosmosRly, + zaptest.NewLogger(t, zaptest.Level(zap.InfoLevel)), + relayer.CustomDockerImage("ghcr.io/cosmos/relayer", "v2.4.0", "1000:1000"), + relayer.RelayerOptionExtraStartFlags{Flags: []string{"-p", "events", "-b", "100", "-d", "--log-format", "console"}}, + ).Build(t, client, network) + + // Prep Interchain + ic := ibctest.NewInterchain(). + AddChain(cosmosAtom). + AddChain(cosmosNeutron). + AddChain(cosmosStride). + AddRelayer(r, "relayer"). + AddProviderConsumerLink(ibctest.ProviderConsumerLink{ + Provider: cosmosAtom, + Consumer: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronICSPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronIBCPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosNeutron, + Chain2: cosmosStride, + Relayer: r, + Path: neutronStrideIBCPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosStride, + Relayer: r, + Path: gaiaStrideIBCPath, + }) + + // Log location + f, err := ibctest.CreateLogFile(fmt.Sprintf("%d.json", time.Now().Unix())) + require.NoError(t, err) + // Reporter/logs + rep := testreporter.NewReporter(f) + eRep := rep.RelayerExecReporter(t) + + // Build interchain + require.NoError( + t, + ic.Build(ctx, eRep, ibctest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + BlockDatabaseFile: ibctest.DefaultBlockDatabaseFilepath(), + SkipPathCreation: true, + }), + "failed to build interchain") + + testCtx := &utils.TestContext{ + Neutron: cosmosNeutron, + Hub: cosmosAtom, + Stride: cosmosStride, + StrideClients: []*ibc.ClientOutput{}, + GaiaClients: []*ibc.ClientOutput{}, + NeutronClients: []*ibc.ClientOutput{}, + StrideConnections: []*ibc.ConnectionOutput{}, + GaiaConnections: []*ibc.ConnectionOutput{}, + NeutronConnections: []*ibc.ConnectionOutput{}, + NeutronTransferChannelIds: make(map[string]string), + GaiaTransferChannelIds: make(map[string]string), + StrideTransferChannelIds: make(map[string]string), + GaiaIcsChannelIds: make(map[string]string), + NeutronIcsChannelIds: make(map[string]string), + T: t, + Ctx: ctx, + } + + testCtx.SkipBlocksStride(5) + + t.Run("generate IBC paths", func(t *testing.T) { + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosNeutron.Config().ChainID, gaiaNeutronIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosStride.Config().ChainID, gaiaStrideIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosStride.Config().ChainID, neutronStrideIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosAtom.Config().ChainID, gaiaNeutronICSPath) + }) + + t.Run("setup neutron-gaia ICS", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + neutronClients := testCtx.GetChainClients(cosmosNeutron.Config().Name) + atomClients := testCtx.GetChainClients(cosmosAtom.Config().Name) + + err = r.UpdatePath(ctx, eRep, gaiaNeutronICSPath, ibc.PathUpdateOptions{ + SrcClientID: &neutronClients[0].ClientID, + DstClientID: &atomClients[0].ClientID, + }) + require.NoError(t, err) + + atomNeutronICSConnectionId, neutronAtomICSConnectionId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + + utils.GenerateICSChannel(t, ctx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + + utils.CreateValidator(t, ctx, r, eRep, atom, neutron) + testCtx.SkipBlocksStride(2) + }) + + t.Run("setup IBC interchain clients, connections, and links", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, neutronStrideIBCPath, cosmosNeutron, cosmosStride) + neutronStrideIBCConnId, strideNeutronIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, neutronStrideIBCPath, cosmosNeutron, cosmosStride) + utils.LinkPath(t, ctx, r, eRep, cosmosNeutron, cosmosStride, neutronStrideIBCPath) + + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaStrideIBCPath, cosmosAtom, cosmosStride) + gaiaStrideIBCConnId, strideGaiaIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaStrideIBCPath, cosmosAtom, cosmosStride) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosStride, gaiaStrideIBCPath) + + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + atomNeutronIBCConnId, neutronAtomIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosNeutron, gaiaNeutronIBCPath) + }) + + // Start the relayer and clean it up when the test ends. + err = r.StartRelayer(ctx, eRep, gaiaNeutronICSPath, gaiaNeutronIBCPath, gaiaStrideIBCPath, neutronStrideIBCPath) + require.NoError(t, err, "failed to start relayer with given paths") + t.Cleanup(func() { + err = r.StopRelayer(ctx, eRep) + if err != nil { + t.Logf("failed to stop relayer: %s", err) + } + }) + testCtx.SkipBlocksStride(2) + + // Once the VSC packet has been relayed, x/bank transfers are + // enabled on Neutron and we can fund its account. + // The funds for this are sent from a "faucet" account created + // by interchaintest in the genesis file. + users := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), atom, neutron, stride) + gaiaUser, neutronUser, strideUser := users[0], users[1], users[2] + _, _, _ = gaiaUser, neutronUser, strideUser + + strideAdminMnemonic := "tone cause tribe this switch near host damage idle fragile antique tail soda alien depth write wool they rapid unfold body scan pledge soft" + strideAdmin, _ := ibctest.GetAndFundTestUserWithMnemonic(ctx, "default", strideAdminMnemonic, (100_000_000), cosmosStride) + + hubCovenantParty := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + neutronCovenantParty := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), neutron)[0] + + cosmosStride.SendFunds(ctx, strideUser.KeyName, ibc.WalletAmount{ + Address: strideAdmin.Bech32Address(stride.Config().Bech32Prefix), + Denom: "ustrd", + Amount: 10000000, + }) + + testCtx.SkipBlocksStride(5) + + t.Run("determine ibc channels", func(t *testing.T) { + neutronChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosNeutron.Config().ChainID) + gaiaChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosAtom.Config().ChainID) + strideChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosStride.Config().ChainID) + + // Find all pairwise channels + utils.GetPairwiseTransferChannelIds(testCtx, strideChannelInfo, neutronChannelInfo, strideNeutronIBCConnId, neutronStrideIBCConnId, stride.Config().Name, neutron.Config().Name) + utils.GetPairwiseTransferChannelIds(testCtx, strideChannelInfo, gaiaChannelInfo, strideGaiaIBCConnId, gaiaStrideIBCConnId, stride.Config().Name, cosmosAtom.Config().Name) + utils.GetPairwiseTransferChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronIBCConnId, neutronAtomIBCConnId, cosmosAtom.Config().Name, neutron.Config().Name) + utils.GetPairwiseCCVChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronICSConnectionId, neutronAtomICSConnectionId, cosmosAtom.Config().Name, cosmosNeutron.Config().Name) + }) + + t.Run("determine ibc denoms", func(t *testing.T) { + // We can determine the ibc denoms of: + // 1. ATOM on Neutron + neutronAtomIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + nativeAtomDenom, + ) + // 2. statom on neutron + neutronStatomIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosStride.Config().Name], + nativeStatomDenom, + ) + // 3. atom on stride + strideAtomIbcDenom = testCtx.GetIbcDenom( + testCtx.StrideTransferChannelIds[cosmosAtom.Config().Name], + nativeAtomDenom, + ) + + // hubStAtomIbcDenom = testCtx.GetIbcDenom( + // testCtx.GaiaTransferChannelIds[cosmosStride.Config().Name], + // nativeStatomDenom, + // ) + + // this is a workaround for pfm being enabled in latest stride versions + // stride -> neutron -> hub + hubNeutronStAtomIbcDenom = testCtx.GetMultihopIbcDenom( + []string{ + testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + testCtx.NeutronTransferChannelIds[cosmosStride.Config().Name], + }, + nativeStatomDenom, + ) + }) + + // Stride is a liquid staking platform. We need to register Gaia (ATOM) + // as a host zone in order to redeem stATOM in exchange for ATOM + // stATOM is stride's liquid staked ATOM vouchers. + t.Run("register stride host zone", func(t *testing.T) { + + cmd := []string{"strided", "tx", "stakeibc", "register-host-zone", + strideGaiaIBCConnId, + cosmosAtom.Config().Denom, + cosmosAtom.Config().Bech32Prefix, + strideAtomIbcDenom, + testCtx.StrideTransferChannelIds[cosmosAtom.Config().Name], + "1", + "--from", strideAdmin.KeyName, + "--gas", "auto", + "--gas-adjustment", `1.3`, + "--output", "json", + "--chain-id", cosmosStride.Config().ChainID, + "--node", cosmosStride.GetRPCAddress(), + "--home", cosmosStride.HomeDir(), + "--keyring-backend", keyring.BackendTest, + "-y", + } + + _, _, err = cosmosStride.Exec(ctx, cmd, nil) + require.NoError(t, err, "failed to register host zone on stride") + + testCtx.SkipBlocksStride(8) + }) + // Stride needs validators that it can stake ATOM with to issue us stATOM + t.Run("register gaia validators on stride", func(t *testing.T) { + + valcmd := []string{"gaiad", "query", "tendermint-validator-set", + "50", + "--chain-id", cosmosAtom.Config().ChainID, + "--node", cosmosAtom.GetRPCAddress(), + "--home", cosmosAtom.HomeDir(), + } + resp, _, err := cosmosAtom.Exec(ctx, valcmd, nil) + require.NoError(t, err, "Failed to query valset") + testCtx.SkipBlocksStride(2) + + var addresses []string + var votingPowers []string + + lines := strings.Split(string(resp), "\n") + + for _, line := range lines { + if strings.HasPrefix(line, "- address: ") { + address := strings.TrimPrefix(line, "- address: ") + addresses = append(addresses, address) + } else if strings.HasPrefix(line, " voting_power: ") { + votingPower := strings.TrimPrefix(line, " voting_power: ") + votingPowers = append(votingPowers, votingPower) + } + } + + // Create validators slice + var validators []Validator + + for i := 1; i <= len(addresses); i++ { + votingPowStr := strings.ReplaceAll(votingPowers[i-1], "\"", "") + valWeight, err := strconv.Atoi(votingPowStr) + require.NoError(t, err, "failed to parse voting power") + + validator := Validator{ + Name: fmt.Sprintf("val%d", i), + Address: addresses[i-1], + Weight: valWeight, + } + validators = append(validators, validator) + } + + // Create JSON object + data := map[string][]Validator{ + "validators": validators, + } + + // Convert to JSON + jsonData, err := json.Marshal(data) + require.NoError(t, err, "failed to marshall data") + + fullPath := filepath.Join(cosmosStride.HomeDir(), "vals.json") + bashCommand := "echo '" + string(jsonData) + "' > " + fullPath + fullPathCmd := []string{"/bin/sh", "-c", bashCommand} + + _, _, err = cosmosStride.Exec(ctx, fullPathCmd, nil) + require.NoError(t, err, "failed to create json with gaia LS validator set on stride") + testCtx.SkipBlocksStride(5) + + cmd := []string{"strided", "tx", "stakeibc", "add-validators", + cosmosAtom.Config().ChainID, + fullPath, + "--from", strideAdmin.KeyName, + "--gas", "auto", + "--gas-adjustment", `1.3`, + "--output", "json", + "--chain-id", cosmosStride.Config().ChainID, + "--node", cosmosStride.GetRPCAddress(), + "--home", cosmosStride.HomeDir(), + "--keyring-backend", keyring.BackendTest, + "-y", + } + + _, _, err = cosmosStride.Exec(ctx, cmd, nil) + require.NoError(t, err, "failed to register host zone on stride") + + testCtx.SkipBlocksStride(5) + + queryCmd := []string{"strided", "query", "stakeibc", + "show-validators", + cosmosAtom.Config().ChainID, + "--chain-id", cosmosStride.Config().ChainID, + "--node", cosmosStride.GetRPCAddress(), + "--home", cosmosStride.HomeDir(), + } + + _, _, err = cosmosStride.Exec(ctx, queryCmd, nil) + require.NoError(t, err, "failed to query host validators") + }) + + t.Run("two party pol covenant setup", func(t *testing.T) { + // Wasm code that we need to store on Neutron + const covenantContractPath = "wasms/valence_covenant_single_party_pol.wasm" + const clockContractPath = "wasms/valence_clock.wasm" + const interchainRouterContractPath = "wasms/valence_interchain_router.wasm" + const nativeRouterContractPath = "wasms/valence_native_router.wasm" + const ibcForwarderContractPath = "wasms/valence_ibc_forwarder.wasm" + const holderContractPath = "wasms/valence_single_party_pol_holder.wasm" + const liquidPoolerPath = "wasms/valence_astroport_liquid_pooler.wasm" + const remoteChainSplitterPath = "wasms/valence_remote_chain_splitter.wasm" + const liquidStakerContractPath = "wasms/valence_stride_liquid_staker.wasm" + + // After storing on Neutron, we will receive a code id + // We parse all the subcontracts into uint64 + // The will be required when we instantiate the covenant. + var clockCodeId uint64 + var interchainRouterCodeId uint64 + var nativeRouterCodeId uint64 + var ibcForwarderCodeId uint64 + var holderCodeId uint64 + var lperCodeId uint64 + var liquidStakerCodeId uint64 + var covenantCodeId uint64 + var remoteChainSplitterCodeId uint64 + _, _, _, _, _, _, _, _, _ = clockCodeId, interchainRouterCodeId, nativeRouterCodeId, ibcForwarderCodeId, holderCodeId, lperCodeId, covenantCodeId, remoteChainSplitterCodeId, liquidStakerCodeId + + t.Run("deploy covenant contracts", func(t *testing.T) { + covenantCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + + // store clock and get code id + clockCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, clockContractPath) + + // store routers and get code id + interchainRouterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, interchainRouterContractPath) + nativeRouterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, nativeRouterContractPath) + + // store forwarder and get code id + ibcForwarderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, ibcForwarderContractPath) + + // store lper, get code + lperCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, liquidPoolerPath) + + // store holder and get code id + holderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, holderContractPath) + + liquidStakerCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, liquidStakerContractPath) + // store remote chain splitter and get code id + remoteChainSplitterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, remoteChainSplitterPath) + + testCtx.SkipBlocksStride(5) + }) + + t.Run("deploy astroport contracts", func(t *testing.T) { + stablePairCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_pair_stable.wasm") + factoryCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_factory.wasm") + whitelistCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_whitelist.wasm") + tokenCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_token.wasm") + coinRegistryCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_native_coin_registry.wasm") + + t.Run("astroport token", func(t *testing.T) { + msg := NativeTokenInstantiateMsg{ + Name: "nativetoken", + Symbol: "ntk", + Decimals: 5, + InitialBalances: []Cw20Coin{}, + Mint: nil, + Marketing: nil, + } + str, _ := json.Marshal(msg) + + tokenAddress, err = cosmosNeutron.InstantiateContract( + ctx, neutronUser.KeyName, strconv.FormatUint(tokenCodeId, 10), string(str), true) + require.NoError(t, err, "Failed to instantiate nativetoken") + println("astroport token: ", tokenAddress) + }) + + t.Run("whitelist", func(t *testing.T) { + msg := WhitelistInstantiateMsg{ + Admins: []string{neutronUser.Bech32Address(neutron.Config().Bech32Prefix)}, + Mutable: false, + } + str, _ := json.Marshal(msg) + + whitelistAddress, err = cosmosNeutron.InstantiateContract( + ctx, neutronUser.KeyName, strconv.FormatUint(whitelistCodeId, 10), string(str), true) + require.NoError(t, err, "Failed to instantiate Whitelist") + println("astroport whitelist: ", whitelistAddress) + + }) + + t.Run("native coins registry", func(t *testing.T) { + msg := NativeCoinRegistryInstantiateMsg{ + Owner: neutronUser.Bech32Address(neutron.Config().Bech32Prefix), + } + str, _ := json.Marshal(msg) + + nativeCoinRegistryAddress, err := cosmosNeutron.InstantiateContract( + ctx, neutronUser.KeyName, strconv.FormatUint(coinRegistryCodeId, 10), string(str), true) + require.NoError(t, err, "Failed to instantiate NativeCoinRegistry") + coinRegistryAddress = nativeCoinRegistryAddress + println("astroport native coins registry: ", coinRegistryAddress) + }) + + t.Run("add coins to registry", func(t *testing.T) { + // Add ibc native tokens for statom and uatom to the native coin registry + // each of these tokens has a precision of 6 + addMessage := fmt.Sprintf( + `{"add":{"native_coins":[["%s",6],["%s",6]]}}`, + neutronAtomIbcDenom, + neutronStatomIbcDenom) + _, err = cosmosNeutron.ExecuteContract(ctx, neutronUser.KeyName, coinRegistryAddress, addMessage) + require.NoError(t, err, err) + testCtx.SkipBlocksStride(2) + }) + + t.Run("factory", func(t *testing.T) { + factoryAddress = testCtx.InstantiateAstroportFactory( + stablePairCodeId, tokenCodeId, whitelistCodeId, factoryCodeId, coinRegistryAddress, neutronUser) + println("astroport factory: ", factoryAddress) + testCtx.SkipBlocksStride(2) + }) + + t.Run("create pair on factory", func(t *testing.T) { + testCtx.CreateAstroportFactoryPairStride(3, neutronStatomIbcDenom, neutronAtomIbcDenom, factoryAddress, neutronUser, keyring.BackendTest) + }) + }) + + t.Run("fund stride user with atom to liquidstake", func(t *testing.T) { + + autopilotString := `{"autopilot":{"receiver":"` + strideUser.Bech32Address(stride.Config().Bech32Prefix) + `","stakeibc":{"stride_address":"` + strideUser.Bech32Address(stride.Config().Bech32Prefix) + `","action":"LiquidStake"}}}` + cmd := []string{cosmosAtom.Config().Bin, "tx", "ibc-transfer", "transfer", "transfer", + testCtx.GaiaTransferChannelIds[cosmosStride.Config().Name], autopilotString, + "100000000000uatom", + "--keyring-backend", keyring.BackendTest, + "--node", cosmosAtom.GetRPCAddress(), + "--from", gaiaUser.KeyName, + "--gas", "auto", + "--home", cosmosAtom.HomeDir(), + "--chain-id", cosmosAtom.Config().ChainID, + "-y", + } + _, _, err = cosmosAtom.Exec(ctx, cmd, nil) + require.NoError(t, err) + + testCtx.SkipBlocksStride(10) + + // ibc transfer statom on stride to neutron user + transferStAtomNeutron := ibc.WalletAmount{ + Address: neutronUser.Bech32Address(testCtx.Neutron.Config().Bech32Prefix), + Denom: nativeStatomDenom, + Amount: int64(100000000000), + } + _, err = testCtx.Stride.SendIBCTransfer( + testCtx.Ctx, + testCtx.StrideTransferChannelIds[cosmosNeutron.Config().Name], + strideUser.KeyName, + transferStAtomNeutron, + ibc.TransferOptions{}, + ) + require.NoError(t, err) + + testCtx.SkipBlocksStride(10) + }) + + t.Run("add liquidity to the atom-statom stableswap pool", func(t *testing.T) { + liquidityTokenAddress, stableswapAddress = testCtx.QueryAstroLpTokenAndStableswapAddress( + factoryAddress, neutronStatomIbcDenom, neutronAtomIbcDenom) + // set up the pool with 1:10 ratio of atom/statom + _, err := atom.SendIBCTransfer(ctx, + testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + gaiaUser.KeyName, + ibc.WalletAmount{ + Address: neutronUser.Bech32Address(neutron.Config().Bech32Prefix), + Denom: cosmosAtom.Config().Denom, + Amount: int64(atomContributionAmount), + }, + ibc.TransferOptions{}) + require.NoError(t, err) + + testCtx.SkipBlocksStride(2) + + testCtx.ProvideAstroportLiquidity( + neutronAtomIbcDenom, neutronStatomIbcDenom, atomContributionAmount/2, atomContributionAmount/2, neutronUser, stableswapAddress) + + testCtx.SkipBlocksStride(2) + neutronUserLPTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, neutronUser.Bech32Address(neutron.Config().Bech32Prefix)) + println("neutronUser lp token bal: ", neutronUserLPTokenBal) + }) + + t.Run("init covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "10000", // sec + IbcTransferTimeout: "10000", // sec + } + + contractCodes := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + LiquidStakerCode: liquidStakerCodeId, + RemoteChainSplitterCode: remoteChainSplitterCodeId, + InterchainRouterCode: interchainRouterCodeId, + } + currentHeight := testCtx.GetNeutronHeight() + + lockupBlock := Block(currentHeight + 110) + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + + lsInfo := LsInfo{ + LsDenom: nativeStatomDenom, + LsDenomOnNeutron: neutronStatomIbcDenom, + LsChainToNeutronChannelId: testCtx.StrideTransferChannelIds[testCtx.Neutron.Config().Name], + LsNeutronConnectionId: neutronStrideIBCConnId, + } + + lsContribution := Coin{ + Denom: nativeAtomDenom, + Amount: "2500000000", + } + liquidPoolerContribution := Coin{ + Denom: nativeAtomDenom, + Amount: "2500000000", + } + + lsForwarderConfig := CovenantPartyConfig{ + Interchain: &InterchainCovenantParty{ + Addr: neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronAtomIbcDenom, + RemoteChainDenom: nativeAtomDenom, + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosStride.Config().Name], + HostToPartyChainChannelId: testCtx.StrideTransferChannelIds[cosmosAtom.Config().Name], + PartyReceiverAddr: neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + Contribution: lsContribution, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + }, + } + + liquidPoolerForwarderConfig := CovenantPartyConfig{ + Interchain: &InterchainCovenantParty{ + Addr: neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronAtomIbcDenom, + RemoteChainDenom: nativeAtomDenom, + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyReceiverAddr: neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + Contribution: liquidPoolerContribution, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + }, + } + + pairType := PairType{ + Stable: struct{}{}, + } + + remoteChainSplitterConfig := RemoteChainSplitterConfig{ + ChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + ConnectionId: neutronAtomIBCConnId, + Denom: nativeAtomDenom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + LsShare: "0.5", + NativeShare: "0.5", + } + + partyPfmMap := map[string]PacketForwardMiddlewareConfig{ + // uncomment at some point + // neutronStatomIbcDenom: { + // LocalToHopChainChannelId: testCtx.NeutronTransferChannelIds[testCtx.Stride.Config().Name], + // HopToDestinationChainChannelId: testCtx.StrideTransferChannelIds[testCtx.Hub.Config().Name], + // HopChainReceiverAddress: strideUser.Bech32Address(testCtx.Stride.Config().Bech32Prefix), + // }, + } + + contribution := Coin{ + Denom: nativeAtomDenom, + Amount: "5000000000", + } + covenantPartyConfig := InterchainCovenantParty{ + Addr: neutronCovenantParty.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronAtomIbcDenom, + RemoteChainDenom: cosmosAtom.Config().Denom, + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyReceiverAddr: hubCovenantParty.Bech32Address(cosmosAtom.Config().Bech32Prefix), + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: "300", + Contribution: contribution, + DenomToPfmMap: partyPfmMap, + } + + liquidPoolerConfig := LiquidPoolerConfig{ + Astroport: &AstroportLiquidPoolerConfig{ + PairType: pairType, + PoolAddress: stableswapAddress, + AssetADenom: lsInfo.LsDenomOnNeutron, + AssetBDenom: neutronAtomIbcDenom, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "1000000", + AssetBLimit: "1000000", + }, + }, + } + + covenantInstantiationMsg := CovenantInstantiationMsg{ + Label: "single_party_pol_covenant", + Timeouts: timeouts, + ContractCodeIds: contractCodes, + LockupConfig: lockupConfig, + LsInfo: lsInfo, + LsForwarderConfig: lsForwarderConfig, + LpForwarderConfig: liquidPoolerForwarderConfig, + RemoteChainSplitterConfig: remoteChainSplitterConfig, + CovenantPartyConfig: covenantPartyConfig, + LiquidPoolerConfig: liquidPoolerConfig, + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "1.0", + AcceptablePriceSpread: "0.1", + }, + } + + covenantAddress = testCtx.ManualInstantiateLS(covenantCodeId, covenantInstantiationMsg, neutronUser, keyring.BackendTest) + println("covenant address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + liquidStakerAddress = testCtx.QueryLiquidStakerAddress(covenantAddress) + lsForwarderAddress = testCtx.QueryIbcForwarderTyAddress(covenantAddress, "ls") + liquidPoolerForwarderAddress = testCtx.QueryIbcForwarderTyAddress(covenantAddress, "lp") + remoteChainSplitterAddress = testCtx.QueryRemoteChainSplitterAddress(covenantAddress) + interchainRouterAddress = testCtx.QuerySinglePartyInterchainRouterAddress(covenantAddress) + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + clockAddress, + holderAddress, + liquidPoolerAddress, + liquidStakerAddress, + lsForwarderAddress, + liquidPoolerForwarderAddress, + remoteChainSplitterAddress, + interchainRouterAddress, + } + + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + }) + + t.Run("tick until forwarders create ica", func(t *testing.T) { + for { + testCtx.TickStride(clockAddress, keyring.BackendTest, neutronUser.KeyName) + lsForwarderState := testCtx.QueryContractState(lsForwarderAddress) + println("lsForwarderState: ", lsForwarderState) + + liquidPoolerForwarderState := testCtx.QueryContractState(liquidPoolerForwarderAddress) + println("liquidPoolerForwarderState: ", liquidPoolerForwarderState) + + splitterState := testCtx.QueryContractState(remoteChainSplitterAddress) + println("splitterState: ", splitterState) + + liquidStakerState := testCtx.QueryContractState(liquidStakerAddress) + println("liquidStakerState: ", liquidStakerState) + + if splitterState == "ica_created" && lsForwarderState == "ica_created" && liquidPoolerForwarderState == "ica_created" && liquidStakerState == "ica_created" { + partyDepositAddress = testCtx.QueryDepositAddressSingleParty(covenantAddress) + strideIcaAddress = testCtx.QueryContractICA(liquidStakerAddress) + lsForwarderIcaAddress = testCtx.QueryContractDepositAddress(lsForwarderAddress) + liquidPoolerForwarderIcaAddress = testCtx.QueryContractDepositAddress(liquidPoolerForwarderAddress) + println("ls forwarder ica address: ", lsForwarderIcaAddress) + println("liquid pooler forwarder ica address: ", liquidPoolerForwarderIcaAddress) + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyDepositAddress}, cosmosAtom, hubCovenantParty, int64(atomContributionAmount)) + + testCtx.SkipBlocksStride(3) + }) + + t.Run("tick until splitter splits the funds to ls and lp forwarders", func(t *testing.T) { + for { + testCtx.TickStride(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + lsForwarderIcaAtomBal := testCtx.QueryHubDenomBalance(nativeAtomDenom, lsForwarderIcaAddress) + lpForwarderIcaAtomBal := testCtx.QueryHubDenomBalance(nativeAtomDenom, liquidPoolerForwarderIcaAddress) + splitterAtomBalance := testCtx.QueryHubDenomBalance(nativeAtomDenom, partyDepositAddress) + + println("ls forwarder ica atom balance: ", lsForwarderIcaAtomBal) + println("lp forwarder ica atom balance: ", lpForwarderIcaAtomBal) + println("splitter atom balance: ", splitterAtomBalance) + + if lsForwarderIcaAtomBal != 0 && lpForwarderIcaAtomBal != 0 { + println("liquid pooler received atom & statom") + break + } + } + }) + + getLsPermisionlessTransferMsg := func(amount uint64) []string { + // Construct a transfer message + msg := TransferExecutionMsg{ + Transfer: TransferAmount{ + Amount: amount, + }, + } + transferMsgJson, err := json.Marshal(msg) + require.NoError(t, err) + + // transfer command for permissionless transfer from stride ica to lper + transferCmd := []string{"neutrond", "tx", "wasm", "execute", liquidStakerAddress, + string(transferMsgJson), + "--from", neutronUser.KeyName, + "--gas-prices", "0.0untrn", + "--gas-adjustment", `1.8`, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "42069420", + "--keyring-backend", keyring.BackendTest, + "-y", + } + return transferCmd + } + var strideIcaStatomBal uint64 + + t.Run("tick until liquid staker liquid stakes", func(t *testing.T) { + for { + testCtx.TickStride(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + liquidPoolerStatomBal := testCtx.QueryNeutronDenomBalance(neutronStatomIbcDenom, liquidPoolerAddress) + lperAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, liquidPoolerAddress) + strideIcaStatomBal = testCtx.QueryStrideDenomBalance(nativeStatomDenom, strideIcaAddress) + + println("lper statom balance: ", liquidPoolerStatomBal) + println("lper atom balance: ", lperAtomBal) + println("stride ica statom balance: ", strideIcaStatomBal) + + if strideIcaStatomBal != 0 { + println("stride ICA received statom: ", strideIcaStatomBal) + break + } + } + }) + + t.Run("permisionless forward", func(t *testing.T) { + testCtx.SkipBlocksStride(10) + permisionlessTransferMsg := getLsPermisionlessTransferMsg(strideIcaStatomBal) + txOut, _, _ := testCtx.Neutron.Exec(testCtx.Ctx, permisionlessTransferMsg, nil) + println("permisionless transfer msg tx hash: ", string(txOut)) + testCtx.SkipBlocksStride(10) + }) + + t.Run("tick until liquid pooler provides liquidity", func(t *testing.T) { + for { + liquidPoolerLpTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, liquidPoolerAddress) + holderLpTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, holderAddress) + neutronUserLpTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix)) + println("liquidPoolerLpTokenBal: ", liquidPoolerLpTokenBal) + println("holderLpTokenBal: ", holderLpTokenBal) + println("neutronUserLpTokenBal: ", neutronUserLpTokenBal) + + if neutronUserLpTokenBal == 0 { + testCtx.TickStride(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("user redeems lp tokens for underlying liquidity", func(t *testing.T) { + stAtomBal := testCtx.QueryHubDenomBalance(hubNeutronStAtomIbcDenom, hubCovenantParty.Bech32Address(cosmosAtom.Config().Bech32Prefix)) + atomBal := testCtx.QueryHubDenomBalance(nativeAtomDenom, hubCovenantParty.Bech32Address(cosmosAtom.Config().Bech32Prefix)) + println("covenant party stAtomBal: ", stAtomBal) + println("covenant party atomBal: ", atomBal) + println("claiming...") + + testCtx.SkipBlocksStride(10) + cmd := []string{"neutrond", "tx", "wasm", "execute", holderAddress, + `{"claim":{}}`, + "--from", neutronCovenantParty.GetKeyName(), + "--gas-prices", "0.0untrn", + "--gas-adjustment", `1.5`, + "--output", "json", + "--node", testCtx.Neutron.GetRPCAddress(), + "--home", testCtx.Neutron.HomeDir(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "42069420", + "--keyring-backend", keyring.BackendTest, + "-y", + } + + resp, _, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + require.NoError(testCtx.T, err, "claim failed") + println("claim response: ", string(resp)) + testCtx.SkipBlocksStride(5) + + for { + // finalStAtomBal := testCtx.QueryHubDenomBalance(hubStAtomIbcDenom, hubCovenantParty.Bech32Address(cosmosAtom.Config().Bech32Prefix)) + finalStAtomBal := testCtx.QueryHubDenomBalance(hubNeutronStAtomIbcDenom, hubCovenantParty.Bech32Address(cosmosAtom.Config().Bech32Prefix)) + finalAtomBal := testCtx.QueryHubDenomBalance(nativeAtomDenom, hubCovenantParty.Bech32Address(cosmosAtom.Config().Bech32Prefix)) + lpAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, liquidPoolerAddress) + lpStAtomBal := testCtx.QueryNeutronDenomBalance(neutronStatomIbcDenom, liquidPoolerAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderStAtomBal := testCtx.QueryNeutronDenomBalance(neutronStatomIbcDenom, holderAddress) + routerAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, interchainRouterAddress) + routerStAtomBal := testCtx.QueryNeutronDenomBalance(neutronStatomIbcDenom, interchainRouterAddress) + + println("hub user stAtomBal: ", finalStAtomBal) + println("hub user atomBal: ", finalAtomBal) + println("lp stAtomBal: ", lpStAtomBal) + println("lp atomBal: ", lpAtomBal) + println("holder stAtomBal: ", holderStAtomBal) + println("holder atomBal: ", holderAtomBal) + println("router stAtomBal: ", routerStAtomBal) + println("router atomBal: ", routerAtomBal) + + if finalStAtomBal != 0 && finalAtomBal != 0 { + println("covenant party received the funds") + break + } else { + testCtx.TickStride(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + }) +} diff --git a/interchaintest/single-party-pol/types.go b/interchaintest/single-party-pol/types.go new file mode 100644 index 00000000..71ae439e --- /dev/null +++ b/interchaintest/single-party-pol/types.go @@ -0,0 +1,426 @@ +package covenant_single_party_pol + +type Validator struct { + Name string `json:"name"` + Address string `json:"address"` + Weight int `json:"weight"` +} + +type Data struct { + BlockHeight string `json:"block_height"` + Total string `json:"total"` + Validators []Validator `json:"validators"` +} + +// astroport stableswap +type StableswapInstantiateMsg struct { + TokenCodeId uint64 `json:"token_code_id"` + FactoryAddr string `json:"factory_addr"` + AssetInfos []AssetInfo `json:"asset_infos"` + InitParams []byte `json:"init_params"` +} + +type AssetInfo struct { + Token *Token `json:"token,omitempty"` + NativeToken *NativeToken `json:"native_token,omitempty"` +} + +type StablePoolParams struct { + Amp uint64 `json:"amp"` + Owner *string `json:"owner"` +} + +type Token struct { + ContractAddr string `json:"contract_addr"` +} + +type NativeToken struct { + Denom string `json:"denom"` +} + +type CwCoin struct { + Denom string `json:"denom"` + Amount string `json:"amount"` +} + +// astroport factory +type FactoryInstantiateMsg struct { + PairConfigs []PairConfig `json:"pair_configs"` + TokenCodeId uint64 `json:"token_code_id"` + FeeAddress *string `json:"fee_address"` + GeneratorAddress *string `json:"generator_address"` + Owner string `json:"owner"` + WhitelistCodeId uint64 `json:"whitelist_code_id"` + CoinRegistryAddress string `json:"coin_registry_address"` +} + +type PairConfig struct { + CodeId uint64 `json:"code_id"` + PairType PairType `json:"pair_type"` + TotalFeeBps uint64 `json:"total_fee_bps"` + MakerFeeBps uint64 `json:"maker_fee_bps"` + IsDisabled bool `json:"is_disabled"` + IsGeneratorDisabled bool `json:"is_generator_disabled"` +} + +type PairType struct { + // Xyk struct{} `json:"xyk,omitempty"` + Stable struct{} `json:"stable,omitempty"` + // Custom struct{} `json:"custom,omitempty"` +} + +// astroport native coin registry + +type NativeCoinRegistryInstantiateMsg struct { + Owner string `json:"owner"` +} + +type AddExecuteMsg struct { + Add Add `json:"add"` +} + +type Add struct { + NativeCoins []NativeCoin `json:"native_coins"` +} + +type NativeCoin struct { + Name string `json:"name"` + Value uint8 `json:"value"` +} + +// Add { native_coins: Vec<(String, u8)> }, + +// astroport native token +type NativeTokenInstantiateMsg struct { + Name string `json:"name"` + Symbol string `json:"symbol"` + Decimals uint8 `json:"decimals"` + InitialBalances []Cw20Coin `json:"initial_balances"` + Mint *MinterResponse `json:"mint"` + Marketing *InstantiateMarketingInfo `json:"marketing"` +} + +type Cw20Coin struct { + Address string `json:"address"` + Amount uint64 `json:"amount"` +} + +type MinterResponse struct { + Minter string `json:"minter"` + Cap *uint64 `json:"cap,omitempty"` +} + +type InstantiateMarketingInfo struct { + Project string `json:"project"` + Description string `json:"description"` + Marketing string `json:"marketing"` + Logo Logo `json:"logo"` +} + +type Logo struct { + Url string `json:"url"` +} + +// astroport whitelist +type WhitelistInstantiateMsg struct { + Admins []string `json:"admins"` + Mutable bool `json:"mutable"` +} + +type ProvideLiqudityMsg struct { + ProvideLiquidity ProvideLiquidityStruct `json:"provide_liquidity"` +} + +type ProvideLiquidityStruct struct { + Assets []AstroportAsset `json:"assets"` + SlippageTolerance string `json:"slippage_tolerance"` + AutoStake bool `json:"auto_stake"` + Receiver string `json:"receiver"` +} + +// factory + +type FactoryPairResponse struct { + Data PairInfo `json:"data"` +} + +type LpPositionQueryResponse struct { + Data string `json:"data"` +} + +type AstroportAsset struct { + Info AssetInfo `json:"info"` + Amount string `json:"amount"` +} + +type LpPositionQuery struct{} + +type PairInfo struct { + LiquidityToken string `json:"liquidity_token"` + ContractAddr string `json:"contract_addr"` + PairType PairType `json:"pair_type"` + AssetInfos []AssetInfo `json:"asset_infos"` +} + +type LPPositionQuery struct { + LpPosition LpPositionQuery `json:"lp_position"` +} + +type Pair struct { + AssetInfos []AssetInfo `json:"asset_infos"` +} + +type PairQuery struct { + Pair Pair `json:"pair"` +} + +type CreatePair struct { + PairType PairType `json:"pair_type"` + AssetInfos []AssetInfo `json:"asset_infos"` + InitParams []byte `json:"init_params"` +} + +type CreatePairMsg struct { + CreatePair CreatePair `json:"create_pair"` +} + +type BalanceResponse struct { + Balance string `json:"balance"` +} + +type Cw20BalanceResponse struct { + Data BalanceResponse `json:"data"` +} + +type AllAccountsResponse struct { + Data []string `json:"all_accounts_response"` +} + +type Cw20QueryMsg struct { + Balance Balance `json:"balance"` + // AllAccounts *AllAccounts `json:"all_accounts"` +} + +type AllAccounts struct { +} + +type Balance struct { + Address string `json:"address"` +} + +type NativeBalQueryResponse struct { + Amount string `json:"amount"` + Denom string `json:"denom"` +} + +// single party POL types + +type CovenantInstantiationMsg struct { + Label string `json:"label"` + Timeouts Timeouts `json:"timeouts"` + ContractCodeIds ContractCodeIds `json:"contract_codes"` + TickMaxGas string `json:"clock_tick_max_gas,omitempty"` + LockupConfig Expiration `json:"lockup_period"` + LsInfo LsInfo `json:"ls_info"` + LsForwarderConfig CovenantPartyConfig `json:"ls_forwarder_config"` + LpForwarderConfig CovenantPartyConfig `json:"lp_forwarder_config"` + RemoteChainSplitterConfig RemoteChainSplitterConfig `json:"remote_chain_splitter_config"` + CovenantPartyConfig InterchainCovenantParty `json:"covenant_party_config"` + LiquidPoolerConfig LiquidPoolerConfig `json:"liquid_pooler_config"` + PoolPriceConfig PoolPriceConfig `json:"pool_price_config"` +} + +type PoolPriceConfig struct { + ExpectedSpotPrice string `json:"expected_spot_price"` + AcceptablePriceSpread string `json:"acceptable_price_spread"` +} + +type LiquidPoolerConfig struct { + Astroport *AstroportLiquidPoolerConfig `json:"astroport,omitempty"` + Osmosis *OsmosisLiquidPoolerConfig `json:"osmosis,omitempty"` +} + +type OsmosisLiquidPoolerConfig struct { + NoteAddress string `json:"note_address"` + PoolId string `json:"pool_id"` + OsmoIbcTimeout string `json:"osmo_ibc_timeout"` + OsmoOutpost string `json:"osmo_outpost"` + Party1ChainInfo PartyChainInfo `json:"party_1_chain_info"` + Party2ChainInfo PartyChainInfo `json:"party_2_chain_info"` + LpTokenDenom string `json:"lp_token_denom"` + OsmoToNeutronChannelId string `json:"osmo_to_neutron_channel_id"` + Party1DenomInfo PartyDenomInfo `json:"party_1_denom_info"` + Party2DenomInfo PartyDenomInfo `json:"party_2_denom_info"` + FundingDuration Duration `json:"funding_duration"` + SingleSideLpLimits SingleSideLpLimits `json:"single_side_lp_limits"` +} + +type SingleSideLpLimits struct { + AssetALimit string `json:"asset_a_limit"` + AssetBLimit string `json:"asset_b_limit"` +} + +type PartyDenomInfo struct { + OsmosisCoin Coin `json:"osmosis_coin"` + LocalDenom string `json:"local_denom"` + SingleSideLpLimit string `json:"single_side_lp_limit"` +} + +type PartyChainInfo struct { + NeutronToPartyChainChannel string `json:"neutron_to_party_chain_channel"` + PartyChainToNeutronChannel string `json:"party_chain_to_neutron_channel"` + InwardsPfm *ForwardMetadata `json:"inwards_pfm,omitempty"` + OutwardsPfm *ForwardMetadata `json:"outwards_pfm,omitempty"` + IbcTimeout string `json:"ibc_timeout"` +} + +type PacketMetadata struct { + ForwardMetadata *ForwardMetadata `json:"forward,omitempty"` +} + +type ForwardMetadata struct { + Receiver string `json:"receiver"` + Port string `json:"port"` + Channel string `json:"channel"` + // Timeout string `json:"timeout,omitempty"` + // Retries uint8 `json:"retries,omitempty"` +} + +type AstroportLiquidPoolerConfig struct { + PairType PairType `json:"pool_pair_type"` + PoolAddress string `json:"pool_address"` + AssetADenom string `json:"asset_a_denom"` + AssetBDenom string `json:"asset_b_denom"` + SingleSideLpLimits SingleSideLpLimits `json:"single_side_lp_limits"` +} + +type PfmUnwindingConfig struct { + PartyPfmMap map[string]PacketForwardMiddlewareConfig `json:"party_pfm_map"` +} + +type PacketForwardMiddlewareConfig struct { + LocalToHopChainChannelId string `json:"local_to_hop_chain_channel_id"` + HopToDestinationChainChannelId string `json:"hop_to_destination_chain_channel_id"` + HopChainReceiverAddress string `json:"hop_chain_receiver_address"` +} + +type RemoteChainSplitterConfig struct { + ChannelId string `json:"channel_id"` + ConnectionId string `json:"connection_id"` + Denom string `json:"denom"` + Amount string `json:"amount"` + LsShare string `json:"ls_share"` + NativeShare string `json:"native_share"` +} + +type CovenantPartyConfig struct { + Interchain *InterchainCovenantParty `json:"interchain,omitempty"` + Native *NativeCovenantParty `json:"native,omitempty"` +} + +type Coin struct { + Denom string `json:"denom"` + Amount string `json:"amount"` +} + +type InterchainCovenantParty struct { + Addr string `json:"addr"` + NativeDenom string `json:"native_denom"` + RemoteChainDenom string `json:"remote_chain_denom"` + PartyToHostChainChannelId string `json:"party_to_host_chain_channel_id"` + HostToPartyChainChannelId string `json:"host_to_party_chain_channel_id"` + PartyReceiverAddr string `json:"party_receiver_addr"` + PartyChainConnectionId string `json:"party_chain_connection_id"` + IbcTransferTimeout string `json:"ibc_transfer_timeout"` + Contribution Coin `json:"contribution"` + DenomToPfmMap map[string]PacketForwardMiddlewareConfig `json:"denom_to_pfm_map"` +} + +type NativeCovenantParty struct { + Addr string `json:"addr"` + NativeDenom string `json:"native_denom"` + PartyReceiverAddr string `json:"party_receiver_addr"` + Contribution Coin `json:"contribution"` +} + +type LsInfo struct { + LsDenom string `json:"ls_denom"` + LsDenomOnNeutron string `json:"ls_denom_on_neutron"` + LsChainToNeutronChannelId string `json:"ls_chain_to_neutron_channel_id"` + LsNeutronConnectionId string `json:"ls_neutron_connection_id"` +} + +type Timeouts struct { + IcaTimeout string `json:"ica_timeout"` + IbcTransferTimeout string `json:"ibc_transfer_timeout"` +} +type Timestamp string +type Block uint64 + +type Expiration struct { + Never string `json:"none,omitempty"` + AtHeight *Block `json:"at_height,omitempty"` + AtTime *Timestamp `json:"at_time,omitempty"` +} + +type Duration struct { + Height *uint64 `json:"height,omitempty"` + Time *uint64 `json:"time,omitempty"` +} + +type ContractCodeIds struct { + IbcForwarderCode uint64 `json:"ibc_forwarder_code"` + ClockCode uint64 `json:"clock_code"` + HolderCode uint64 `json:"holder_code"` + LiquidPoolerCode uint64 `json:"liquid_pooler_code"` + LiquidStakerCode uint64 `json:"liquid_staker_code"` + RemoteChainSplitterCode uint64 `json:"remote_chain_splitter_code"` + InterchainRouterCode uint64 `json:"interchain_router_code"` +} + +type SplitConfig struct { + Receivers map[string]string `json:"receivers"` +} + +type LiquidStakerInstantiateMsg struct { + ClockAddress string `json:"clock_address"` + StrideNeutronIbcTransferChannelID string `json:"stride_neutron_ibc_transfer_channel_id"` + NeutronStrideIbcConnectionID string `json:"neutron_stride_ibc_connection_id"` + NextContract string `json:"next_contract"` + LsDenom string `json:"ls_denom"` + IbcFee IbcFee `json:"ibc_fee"` // Assuming IbcFee is defined elsewhere + IcaTimeout string `json:"ica_timeout"` + IbcTransferTimeout string `json:"ibc_transfer_timeout"` + AutopilotFormat string `json:"autopilot_format"` +} + +type IbcFee struct { + RecvFee []CwCoin `json:"recv_fee"` + AckFee []CwCoin `json:"ack_fee"` + TimeoutFee []CwCoin `json:"timeout_fee"` +} + +////////////////////////////////////////////// +///// Ls contract +////////////////////////////////////////////// + +// Execute +type TransferExecutionMsg struct { + Transfer TransferAmount `json:"transfer"` +} + +// Rust type here is Uint128 which can't safely be serialized +// to json int. It needs to go as a string over the wire. +type TransferAmount struct { + Amount uint64 `json:"amount,string"` +} + +// Queries +type LsIcaQuery struct { + StrideIca StrideIcaQuery `json:"stride_i_c_a"` +} +type StrideIcaQuery struct{} + +type StrideIcaQueryResponse struct { + Addr string `json:"data"` +} diff --git a/interchaintest/swap/README.md b/interchaintest/swap/README.md new file mode 100644 index 00000000..4f54e29d --- /dev/null +++ b/interchaintest/swap/README.md @@ -0,0 +1,3 @@ +# Swap covenant + +Trust minimized way of doing token swaps diff --git a/interchaintest/swap/tokenswap_test.go b/interchaintest/swap/tokenswap_test.go new file mode 100644 index 00000000..0064a259 --- /dev/null +++ b/interchaintest/swap/tokenswap_test.go @@ -0,0 +1,568 @@ +package covenant_swap + +import ( + "context" + "fmt" + "strconv" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + ibctest "github.com/strangelove-ventures/interchaintest/v4" + "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v4/ibc" + "github.com/strangelove-ventures/interchaintest/v4/relayer" + "github.com/strangelove-ventures/interchaintest/v4/testreporter" + "github.com/strangelove-ventures/interchaintest/v4/testutil" + "github.com/stretchr/testify/require" + utils "github.com/timewave-computer/covenants/interchaintest/utils" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +const gaiaNeutronICSPath = "gn-ics-path" +const gaiaNeutronIBCPath = "gn-ibc-path" +const gaiaOsmosisIBCPath = "go-ibc-path" +const neutronOsmosisIBCPath = "no-ibc-path" +const nativeAtomDenom = "uatom" +const nativeOsmoDenom = "uosmo" +const nativeNtrnDenom = "untrn" + +var covenantAddress string +var clockAddress string +var splitterAddress string +var partyARouterAddress, partyBRouterAddress string +var partyAIbcForwarderAddress, partyBIbcForwarderAddress string +var partyADepositAddress, partyBDepositAddress string +var holderAddress string +var neutronAtomIbcDenom, neutronOsmoIbcDenom string +var atomNeutronICSConnectionId, neutronAtomICSConnectionId string +var neutronOsmosisIBCConnId, osmosisNeutronIBCConnId string +var atomNeutronIBCConnId, neutronAtomIBCConnId string +var gaiaOsmosisIBCConnId, osmosisGaiaIBCConnId string +var hubNeutronIbcDenom string + +// PARTY_A +const neutronContributionAmount uint64 = 100_000_000_000 // in untrn + +// PARTY_B +const atomContributionAmount uint64 = 5_000_000_000 // in uatom + +// sets up and tests a tokenswap between hub and osmo facilitated by neutron +func TestTokenSwap(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + ctx := context.Background() + + // Modify the the timeout_commit in the config.toml node files + // to reduce the block commit times. This speeds up the tests + // by about 35% + configFileOverrides := make(map[string]any) + configTomlOverrides := make(testutil.Toml) + consensus := make(testutil.Toml) + consensus["timeout_commit"] = "1s" + configTomlOverrides["consensus"] = consensus + configFileOverrides["config/config.toml"] = configTomlOverrides + + // Chain Factory + cf := ibctest.NewBuiltinChainFactory(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)), []*ibctest.ChainSpec{ + { + Name: "gaia", + Version: "v9.1.0", + ChainConfig: ibc.ChainConfig{ + GasAdjustment: 1.3, + GasPrices: "0.0atom", + ModifyGenesis: utils.SetupGaiaGenesis(utils.GetDefaultInterchainGenesisMessages()), + ConfigFileOverrides: configFileOverrides, + }, + }, + { + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Name: "neutron", + ChainID: "neutron-2", + Images: []ibc.DockerImage{ + { + Repository: "ghcr.io/strangelove-ventures/heighliner/neutron", + Version: "v2.0.0", + UidGid: "1025:1025", + }, + }, + Bin: "neutrond", + Bech32Prefix: "neutron", + Denom: nativeNtrnDenom, + GasPrices: "0.0untrn,0.0uatom", + GasAdjustment: 1.3, + TrustingPeriod: "1197504s", + NoHostMount: false, + ModifyGenesis: utils.SetupNeutronGenesis( + "0.05", + []string{nativeNtrnDenom}, + []string{nativeAtomDenom}, + utils.GetDefaultNeutronInterchainGenesisMessages(), + ), + ConfigFileOverrides: configFileOverrides, + }, + }, + { + Name: "osmosis", + Version: "v14.0.0", + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Bin: "osmosisd", + Bech32Prefix: "osmo", + Denom: nativeOsmoDenom, + ModifyGenesis: utils.SetupOsmoGenesis( + append(utils.GetDefaultInterchainGenesisMessages(), "/ibc.applications.interchain_accounts.v1.InterchainAccount"), + ), + GasPrices: "0.0uosmo", + GasAdjustment: 1.3, + Images: []ibc.DockerImage{ + { + Repository: "ghcr.io/strangelove-ventures/heighliner/osmosis", + Version: "v14.0.0", + UidGid: "1025:1025", + }, + }, + TrustingPeriod: "336h", + NoHostMount: false, + ConfigFileOverrides: configFileOverrides, + }, + }, + }) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + + // We have three chains + atom, neutron, osmosis := chains[0], chains[1], chains[2] + cosmosAtom, cosmosNeutron, cosmosOsmosis := atom.(*cosmos.CosmosChain), neutron.(*cosmos.CosmosChain), osmosis.(*cosmos.CosmosChain) + + // Relayer Factory + client, network := ibctest.DockerSetup(t) + r := ibctest.NewBuiltinRelayerFactory( + ibc.CosmosRly, + zaptest.NewLogger(t, zaptest.Level(zap.InfoLevel)), + relayer.CustomDockerImage("ghcr.io/cosmos/relayer", "v2.4.0", "1000:1000"), + relayer.RelayerOptionExtraStartFlags{Flags: []string{"-p", "events", "-b", "100", "-d", "--log-format", "console"}}, + ).Build(t, client, network) + + // Prep Interchain + ic := ibctest.NewInterchain(). + AddChain(cosmosAtom). + AddChain(cosmosNeutron). + AddChain(cosmosOsmosis). + AddRelayer(r, "relayer"). + AddProviderConsumerLink(ibctest.ProviderConsumerLink{ + Provider: cosmosAtom, + Consumer: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronICSPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronIBCPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosNeutron, + Chain2: cosmosOsmosis, + Relayer: r, + Path: neutronOsmosisIBCPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosOsmosis, + Relayer: r, + Path: gaiaOsmosisIBCPath, + }) + + // Log location + f, err := ibctest.CreateLogFile(fmt.Sprintf("%d.json", time.Now().Unix())) + require.NoError(t, err) + // Reporter/logs + rep := testreporter.NewReporter(f) + eRep := rep.RelayerExecReporter(t) + + // Build interchain + require.NoError( + t, + ic.Build(ctx, eRep, ibctest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + BlockDatabaseFile: ibctest.DefaultBlockDatabaseFilepath(), + SkipPathCreation: true, + }), + "failed to build interchain") + + err = testutil.WaitForBlocks(ctx, 10, atom, neutron, osmosis) + require.NoError(t, err, "failed to wait for blocks") + + testCtx := &utils.TestContext{ + Neutron: cosmosNeutron, + Hub: cosmosAtom, + Osmosis: cosmosOsmosis, + OsmoClients: []*ibc.ClientOutput{}, + GaiaClients: []*ibc.ClientOutput{}, + NeutronClients: []*ibc.ClientOutput{}, + OsmoConnections: []*ibc.ConnectionOutput{}, + GaiaConnections: []*ibc.ConnectionOutput{}, + NeutronConnections: []*ibc.ConnectionOutput{}, + NeutronTransferChannelIds: make(map[string]string), + GaiaTransferChannelIds: make(map[string]string), + OsmoTransferChannelIds: make(map[string]string), + GaiaIcsChannelIds: make(map[string]string), + NeutronIcsChannelIds: make(map[string]string), + T: t, + Ctx: ctx, + } + + t.Run("generate IBC paths", func(t *testing.T) { + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosNeutron.Config().ChainID, gaiaNeutronIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosOsmosis.Config().ChainID, gaiaOsmosisIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosOsmosis.Config().ChainID, neutronOsmosisIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosAtom.Config().ChainID, gaiaNeutronICSPath) + }) + + t.Run("setup neutron-gaia ICS", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + neutronClients := testCtx.GetChainClients(cosmosNeutron.Config().Name) + atomClients := testCtx.GetChainClients(cosmosAtom.Config().Name) + + require.NoError(t, + r.UpdatePath(ctx, eRep, gaiaNeutronICSPath, ibc.PathUpdateOptions{ + SrcClientID: &neutronClients[0].ClientID, + DstClientID: &atomClients[0].ClientID, + }), + ) + + atomNeutronICSConnectionId, neutronAtomICSConnectionId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + utils.GenerateICSChannel(t, ctx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + utils.CreateValidator(t, ctx, r, eRep, atom, neutron) + testCtx.SkipBlocks(3) + }) + + t.Run("setup IBC interchain clients, connections, and links", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, neutronOsmosisIBCPath, cosmosNeutron, cosmosOsmosis) + neutronOsmosisIBCConnId, osmosisNeutronIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, neutronOsmosisIBCPath, cosmosNeutron, cosmosOsmosis) + utils.LinkPath(t, ctx, r, eRep, cosmosNeutron, cosmosOsmosis, neutronOsmosisIBCPath) + + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaOsmosisIBCPath, cosmosAtom, cosmosOsmosis) + gaiaOsmosisIBCConnId, osmosisGaiaIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaOsmosisIBCPath, cosmosAtom, cosmosOsmosis) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosOsmosis, gaiaOsmosisIBCPath) + + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + atomNeutronIBCConnId, neutronAtomIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosNeutron, gaiaNeutronIBCPath) + }) + + // Start the relayer and clean it up when the test ends. + err = r.StartRelayer(ctx, eRep, gaiaNeutronICSPath, gaiaNeutronIBCPath, gaiaOsmosisIBCPath, neutronOsmosisIBCPath) + require.NoError(t, err, "failed to start relayer with given paths") + t.Cleanup(func() { + err = r.StopRelayer(ctx, eRep) + if err != nil { + t.Logf("failed to stop relayer: %s", err) + } + }) + + testCtx.SkipBlocks(2) + + // Once the VSC packet has been relayed, x/bank transfers are + // enabled on Neutron and we can fund its account. + // The funds for this are sent from a "faucet" account created + // by interchaintest in the genesis file. + users := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), atom, neutron, osmosis) + gaiaUser, neutronUser, osmoUser := users[0], users[1], users[2] + _, _, _ = gaiaUser, neutronUser, osmoUser + + hubNeutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), neutron)[0] + neutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(1), neutron)[0] + + var neutronReceiverAddr string + var hubReceiverAddr string + + testCtx.SkipBlocks(10) + + t.Run("determine ibc channels", func(t *testing.T) { + neutronChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosNeutron.Config().ChainID) + gaiaChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosAtom.Config().ChainID) + osmoChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosOsmosis.Config().ChainID) + + // Find all pairwise channels + utils.GetPairwiseTransferChannelIds(testCtx, osmoChannelInfo, neutronChannelInfo, osmosisNeutronIBCConnId, neutronOsmosisIBCConnId, osmosis.Config().Name, neutron.Config().Name) + utils.GetPairwiseTransferChannelIds(testCtx, osmoChannelInfo, gaiaChannelInfo, osmosisGaiaIBCConnId, gaiaOsmosisIBCConnId, osmosis.Config().Name, cosmosAtom.Config().Name) + utils.GetPairwiseTransferChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronIBCConnId, neutronAtomIBCConnId, cosmosAtom.Config().Name, neutron.Config().Name) + utils.GetPairwiseCCVChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronICSConnectionId, neutronAtomICSConnectionId, cosmosAtom.Config().Name, cosmosNeutron.Config().Name) + }) + + t.Run("determine ibc denoms", func(t *testing.T) { + neutronAtomIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + nativeAtomDenom, + ) + neutronOsmoIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + nativeOsmoDenom, + ) + hubNeutronIbcDenom = testCtx.GetIbcDenom( + testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + cosmosNeutron.Config().Denom, + ) + }) + + t.Run("tokenswap covenant setup", func(t *testing.T) { + // Wasm code that we need to store on Neutron + const covenantContractPath = "wasms/valence_covenant_swap.wasm" + const clockContractPath = "wasms/valence_clock.wasm" + const interchainRouterContractPath = "wasms/valence_interchain_router.wasm" + const nativeRouterContractPath = "wasms/valence_native_router.wasm" + const splitterContractPath = "wasms/valence_native_splitter.wasm" + const ibcForwarderContractPath = "wasms/valence_ibc_forwarder.wasm" + const swapHolderContractPath = "wasms/valence_swap_holder.wasm" + + // After storing on Neutron, we will receive a code id + // We parse all the subcontracts into uint64 + // The will be required when we instantiate the covenant. + var clockCodeId uint64 + var interchainRouterCodeId uint64 + var nativeRouterCodeId uint64 + var splitterCodeId uint64 + var ibcForwarderCodeId uint64 + var swapHolderCodeId uint64 + var covenantCodeId uint64 + + t.Run("deploy covenant contracts", func(t *testing.T) { + covenantCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + clockCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, clockContractPath) + interchainRouterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, interchainRouterContractPath) + nativeRouterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, nativeRouterContractPath) + splitterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, splitterContractPath) + ibcForwarderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, ibcForwarderContractPath) + swapHolderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, swapHolderContractPath) + }) + + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "10000", // sec + IbcTransferTimeout: "10000", // sec + } + + partyACoin := Coin{ + Denom: nativeAtomDenom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + partyBCoin := Coin{ + Denom: cosmosNeutron.Config().Denom, + Amount: strconv.FormatUint(neutronContributionAmount, 10), + } + + currentHeight, err := cosmosNeutron.Height(ctx) + require.NoError(t, err, "failed to get neutron height") + depositBlock := Block(currentHeight + 350) + lockupConfig := Expiration{ + AtHeight: &depositBlock, + } + + neutronReceiverAddr = neutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix) + hubReceiverAddr = gaiaUser.Bech32Address(cosmosAtom.Config().Bech32Prefix) + + splits := map[string]SplitConfig{ + neutronAtomIbcDenom: { + Receivers: map[string]string{ + neutronReceiverAddr: "1.0", + hubReceiverAddr: "0.0", + }, + }, + cosmosNeutron.Config().Denom: { + Receivers: map[string]string{ + neutronReceiverAddr: "0.0", + hubReceiverAddr: "1.0", + }, + }, + } + + denomToPfmMap := map[string]PacketForwardMiddlewareConfig{} + + partyAConfig := InterchainCovenantParty{ + Addr: hubNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronAtomIbcDenom, + RemoteChainDenom: nativeAtomDenom, + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyReceiverAddr: hubReceiverAddr, + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + DenomToPfmMap: denomToPfmMap, + Contribution: partyACoin, + } + partyBConfig := NativeCovenantParty{ + Addr: neutronReceiverAddr, + NativeDenom: cosmosNeutron.Config().Denom, + PartyReceiverAddr: neutronReceiverAddr, + Contribution: partyBCoin, + } + codeIds := SwapCovenantContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + InterchainSplitterCode: splitterCodeId, + ClockCode: clockCodeId, + HolderCode: swapHolderCodeId, + } + + covenantMsg := CovenantInstantiateMsg{ + Label: "swap-covenant", + Timeouts: timeouts, + SwapCovenantContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{ + Interchain: &partyAConfig, + }, + PartyBConfig: CovenantPartyConfig{ + Native: &partyBConfig, + }, + Splits: splits, + } + + covenantAddress = testCtx.ManualInstantiate(covenantCodeId, covenantMsg, neutronUser, keyring.BackendTest) + println("covenant address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + splitterAddress = testCtx.QueryInterchainSplitterAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + splitterAddress, + } + if partyAIbcForwarderAddress != "" { + addrs = append(addrs, partyAIbcForwarderAddress) + } + if partyBIbcForwarderAddress != "" { + addrs = append(addrs, partyBIbcForwarderAddress) + } + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + testCtx.SkipBlocks(2) + }) + }) + + t.Run("tokenswap run", func(t *testing.T) { + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + + if forwarderAState == "ica_created" { + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + require.NoError(t, + cosmosNeutron.SendFunds(ctx, neutronUser.KeyName, ibc.WalletAmount{ + Address: partyBDepositAddress, + Denom: cosmosNeutron.Config().Denom, + Amount: int64(neutronContributionAmount), + }), + "failed to deposit neutron", + ) + + require.NoError(t, + cosmosAtom.SendFunds(ctx, gaiaUser.KeyName, ibc.WalletAmount{ + Address: partyADepositAddress, + Denom: nativeAtomDenom, + Amount: int64(atomContributionAmount), + }), + "failed to fund gaia forwarder", + ) + testCtx.SkipBlocks(5) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + holderNeutronBal := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + + if holderAtomBal >= atomContributionAmount && holderNeutronBal >= neutronContributionAmount { + println("holder atom bal: ", holderAtomBal) + println("holder neutron bal: ", holderNeutronBal) + break + } else if holderState == "complete" { + println("holder state: ", holderState) + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until holder sends the funds to splitter", func(t *testing.T) { + for { + splitterNeutronBal := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, splitterAddress) + splitterAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, splitterAddress) + println("splitterNeutronBal: ", splitterNeutronBal) + println("splitterAtomBal: ", splitterAtomBal) + if splitterAtomBal >= atomContributionAmount && splitterNeutronBal >= neutronContributionAmount { + println("splitter received contributions") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until splitter sends the funds to routers", func(t *testing.T) { + for { + partyARouterNeutronBal := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyARouterAddress) + partyBRouterAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + println("partyARouterNeutronBal: ", partyARouterNeutronBal) + println("partyBRouterAtomBal: ", partyBRouterAtomBal) + + if partyARouterNeutronBal >= neutronContributionAmount && partyBRouterAtomBal >= atomContributionAmount { + println("both routers received contributions") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until routers route the funds to final receivers", func(t *testing.T) { + println("hub receiver address: ", hubReceiverAddr) + println("neutron receiver address: ", neutronReceiverAddr) + for { + neutronBal := testCtx.QueryHubDenomBalance(hubNeutronIbcDenom, hubReceiverAddr) + atomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, neutronReceiverAddr) + println("gaia user neutron bal: ", neutronBal) + println("neutron user atom bal: ", atomBal) + if neutronBal >= neutronContributionAmount && atomBal >= atomContributionAmount { + println("complete") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + }) +} diff --git a/interchaintest/swap/types.go b/interchaintest/swap/types.go new file mode 100644 index 00000000..8beb976a --- /dev/null +++ b/interchaintest/swap/types.go @@ -0,0 +1,176 @@ +package covenant_swap + +////////////////////////////////////////////// +///// Covenant contracts +////////////////////////////////////////////// + +// ----- Covenant Instantiation ------ +type CovenantInstantiateMsg struct { + Label string `json:"label"` + Timeouts Timeouts `json:"timeouts"` + SwapCovenantContractCodeIds SwapCovenantContractCodeIds `json:"contract_codes"` + TickMaxGas string `json:"clock_tick_max_gas,omitempty"` + LockupConfig Expiration `json:"lockup_config"` + PartyAConfig CovenantPartyConfig `json:"party_a_config"` + PartyBConfig CovenantPartyConfig `json:"party_b_config"` + Splits map[string]SplitConfig `json:"splits"` + FallbackSplit *SplitConfig `json:"fallback_split,omitempty"` +} + +type CovenantPartyConfig struct { + Interchain *InterchainCovenantParty `json:"interchain,omitempty"` + Native *NativeCovenantParty `json:"native,omitempty"` +} + +type InterchainCovenantParty struct { + Addr string `json:"addr"` + NativeDenom string `json:"native_denom"` + RemoteChainDenom string `json:"remote_chain_denom"` + PartyToHostChainChannelId string `json:"party_to_host_chain_channel_id"` + HostToPartyChainChannelId string `json:"host_to_party_chain_channel_id"` + PartyReceiverAddr string `json:"party_receiver_addr"` + PartyChainConnectionId string `json:"party_chain_connection_id"` + IbcTransferTimeout string `json:"ibc_transfer_timeout"` + Contribution Coin `json:"contribution"` + DenomToPfmMap map[string]PacketForwardMiddlewareConfig `json:"denom_to_pfm_map"` +} + +type Coin struct { + Denom string `json:"denom"` + Amount string `json:"amount"` +} + +type PacketForwardMiddlewareConfig struct { + LocalToHopChainChannelId string `json:"local_to_hop_chain_channel_id"` + HopToDestinationChainChannelId string `json:"hop_to_destination_chain_channel_id"` + HopChainReceiverAddress string `json:"hop_chain_receiver_address"` +} + +type NativeCovenantParty struct { + Addr string `json:"addr"` + NativeDenom string `json:"native_denom"` + PartyReceiverAddr string `json:"party_receiver_addr"` + Contribution Coin `json:"contribution"` +} + +type SwapCovenantContractCodeIds struct { + IbcForwarderCode uint64 `json:"ibc_forwarder_code"` + InterchainRouterCode uint64 `json:"interchain_router_code"` + NativeRouterCode uint64 `json:"native_router_code"` + InterchainSplitterCode uint64 `json:"splitter_code"` + ClockCode uint64 `json:"clock_code"` + HolderCode uint64 `json:"holder_code"` +} + +type Receiver struct { + Address string `json:"addr"` + Share string `json:"share"` +} + +type SplitConfig struct { + Receivers map[string]string `json:"receivers"` +} + +type Timeouts struct { + IcaTimeout string `json:"ica_timeout"` + IbcTransferTimeout string `json:"ibc_transfer_timeout"` +} + +type PresetClockFields struct { + TickMaxGas string `json:"tick_max_gas,omitempty"` + ClockCode uint64 `json:"clock_code"` + Label string `json:"label"` + Whitelist []string `json:"whitelist"` +} + +type PresetSwapHolderFields struct { + LockupConfig Expiration `json:"lockup_config"` + CovenantPartiesConfig CovenantPartiesConfig `json:"parties_config"` + CovenantTerms CovenantTerms `json:"covenant_terms"` + CodeId uint64 `json:"code_id"` + Label string `json:"label"` +} + +type Timestamp string +type Block uint64 + +type Expiration struct { + Never string `json:"none,omitempty"` + AtHeight *Block `json:"at_height,omitempty"` + AtTime *Timestamp `json:"at_time,omitempty"` +} + +type Duration struct { + Height *uint64 `json:"height,omitempty"` + Time *uint64 `json:"time,omitempty"` +} + +type CovenantPartiesConfig struct { + PartyA CovenantParty `json:"party_a"` + PartyB CovenantParty `json:"party_b"` +} + +type CovenantParty struct { + Addr string `json:"addr"` + IbcDenom string `json:"ibc_denom"` + ReceiverConfig ReceiverConfig `json:"receiver_config"` +} + +// type SwapPartyConfig struct { +// Addr string `json:"addr"` +// NativeDenom string `json:"native_denom"` +// IbcDenom string `json:"ibc_denom"` +// PartyToHostChainChannelId string `json:"party_to_host_chain_channel_id"` +// HostToPartyChainChannelId string `json:"host_to_party_chain_channel_id"` +// PartyReceiverAddr string `json:"party_receiver_addr"` +// PartyChainConnectionId string `json:"party_chain_connection_id"` +// IbcTransferTimeout string `json:"ibc_transfer_timeout"` +// } + +type ReceiverConfig struct { + Native string `json:"native"` +} + +type SwapCovenantTerms struct { + PartyAAmount string `json:"party_a_amount"` + PartyBAmount string `json:"party_b_amount"` +} + +type CovenantTerms struct { + TokenSwap SwapCovenantTerms `json:"token_swap,omitempty"` +} + +// ----- Covenant Queries ------ +type ClockAddress struct{} +type ClockAddressQuery struct { + ClockAddress ClockAddress `json:"clock_address"` +} + +type HolderAddress struct{} +type HolderAddressQuery struct { + HolderAddress HolderAddress `json:"holder_address"` +} + +type SplitterAddress struct{} +type SplitterAddressQuery struct { + SplitterAddress SplitterAddress `json:"splitter_address"` +} + +type CovenantParties struct{} +type CovenantPartiesQuery struct { + CovenantParties CovenantParties `json:"covenant_parties"` +} + +type Party struct { + Party string `json:"party"` +} +type InterchainRouterQuery struct { + Party Party `json:"interchain_router_address"` +} +type IbcForwarderQuery struct { + Party Party `json:"ibc_forwarder_address"` +} + +type CovenantAddressQueryResponse struct { + Data string `json:"data"` +} diff --git a/interchaintest/two-party-pol/README.md b/interchaintest/two-party-pol/README.md new file mode 100644 index 00000000..4b366ce0 --- /dev/null +++ b/interchaintest/two-party-pol/README.md @@ -0,0 +1,3 @@ +# Two party POL covenant + +Trust minimized way of doing two party POL diff --git a/interchaintest/two-party-pol/osmo_lper_test.go b/interchaintest/two-party-pol/osmo_lper_test.go new file mode 100644 index 00000000..75f6162a --- /dev/null +++ b/interchaintest/two-party-pol/osmo_lper_test.go @@ -0,0 +1,774 @@ +package covenant_two_party_pol + +import ( + "context" + "encoding/json" + "fmt" + "path/filepath" + "strconv" + "testing" + "time" + + cw "github.com/CosmWasm/wasmvm/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + ibctest "github.com/strangelove-ventures/interchaintest/v4" + "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v4/ibc" + "github.com/strangelove-ventures/interchaintest/v4/relayer" + "github.com/strangelove-ventures/interchaintest/v4/testreporter" + "github.com/strangelove-ventures/interchaintest/v4/testutil" + "github.com/stretchr/testify/require" + utils "github.com/timewave-computer/covenants/interchaintest/utils" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +// sets up and tests a two party pol between hub and osmo facilitated by neutron +func TestTwoPartyOsmoLP(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + ctx := context.Background() + + // Modify the the timeout_commit in the config.toml node files + // to reduce the block commit times. This speeds up the tests + // by about 35% + configFileOverrides := make(map[string]any) + configTomlOverrides := make(testutil.Toml) + consensus := make(testutil.Toml) + consensus["timeout_commit"] = "1s" + configTomlOverrides["consensus"] = consensus + configFileOverrides["config/config.toml"] = configTomlOverrides + + // Chain Factory + cf := ibctest.NewBuiltinChainFactory(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)), []*ibctest.ChainSpec{ + {Name: "gaia", Version: "v9.1.0", ChainConfig: ibc.ChainConfig{ + GasAdjustment: 1.3, + GasPrices: "0.0atom", + ModifyGenesis: utils.SetupGaiaGenesis(utils.GetDefaultInterchainGenesisMessages()), + ConfigFileOverrides: configFileOverrides, + }}, + { + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Name: "neutron", + ChainID: "neutron-2", + Images: []ibc.DockerImage{ + { + Repository: "ghcr.io/strangelove-ventures/heighliner/neutron", + Version: "v2.0.0", + UidGid: "1025:1025", + }, + }, + Bin: "neutrond", + Bech32Prefix: "neutron", + Denom: nativeNtrnDenom, + GasPrices: "0.0untrn,0.0uatom", + GasAdjustment: 2.0, + TrustingPeriod: "1197504s", + NoHostMount: false, + ModifyGenesis: utils.SetupNeutronGenesis( + "0.05", + []string{nativeNtrnDenom}, + []string{nativeAtomDenom}, + utils.GetDefaultNeutronInterchainGenesisMessages(), + ), + ConfigFileOverrides: configFileOverrides, + }, + }, + { + Name: "osmosis", + Version: "v17.0.0", + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Bin: "osmosisd", + Bech32Prefix: "osmo", + Denom: nativeOsmoDenom, + ModifyGenesis: utils.SetupOsmoGenesis( + append(utils.GetDefaultInterchainGenesisMessages(), "/ibc.applications.interchain_accounts.v1.InterchainAccount"), + ), + GasPrices: "0.005uosmo", + GasAdjustment: 2.0, + TrustingPeriod: "336h", + NoHostMount: false, + ConfigFileOverrides: configFileOverrides, + }, + }, + }) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + + // We have three chains + atom, neutron, osmosis := chains[0], chains[1], chains[2] + cosmosAtom, cosmosNeutron, cosmosOsmosis := atom.(*cosmos.CosmosChain), neutron.(*cosmos.CosmosChain), osmosis.(*cosmos.CosmosChain) + + // Relayer Factory + client, network := ibctest.DockerSetup(t) + r := ibctest.NewBuiltinRelayerFactory( + ibc.CosmosRly, + zaptest.NewLogger(t, zaptest.Level(zap.InfoLevel)), + relayer.CustomDockerImage("ghcr.io/cosmos/relayer", "v2.4.0", "1000:1000"), + relayer.RelayerOptionExtraStartFlags{Flags: []string{"-p", "events", "-b", "100", "-d", "--log-format", "console"}}, + ).Build(t, client, network) + + // Prep Interchain + ic := ibctest.NewInterchain(). + AddChain(cosmosAtom). + AddChain(cosmosNeutron). + AddChain(cosmosOsmosis). + AddRelayer(r, "relayer"). + AddProviderConsumerLink(ibctest.ProviderConsumerLink{ + Provider: cosmosAtom, + Consumer: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronICSPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronIBCPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosNeutron, + Chain2: cosmosOsmosis, + Relayer: r, + Path: neutronOsmosisIBCPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosOsmosis, + Relayer: r, + Path: gaiaOsmosisIBCPath, + }) + + // Log location + f, err := ibctest.CreateLogFile(fmt.Sprintf("%d.json", time.Now().Unix())) + require.NoError(t, err) + // Reporter/logs + rep := testreporter.NewReporter(f) + eRep := rep.RelayerExecReporter(t) + + // Build interchain + require.NoError( + t, + ic.Build(ctx, eRep, ibctest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + BlockDatabaseFile: ibctest.DefaultBlockDatabaseFilepath(), + SkipPathCreation: true, + }), + "failed to build interchain") + + testCtx := &utils.TestContext{ + Neutron: cosmosNeutron, + Hub: cosmosAtom, + Osmosis: cosmosOsmosis, + OsmoClients: []*ibc.ClientOutput{}, + GaiaClients: []*ibc.ClientOutput{}, + NeutronClients: []*ibc.ClientOutput{}, + OsmoConnections: []*ibc.ConnectionOutput{}, + GaiaConnections: []*ibc.ConnectionOutput{}, + NeutronConnections: []*ibc.ConnectionOutput{}, + NeutronTransferChannelIds: make(map[string]string), + GaiaTransferChannelIds: make(map[string]string), + OsmoTransferChannelIds: make(map[string]string), + GaiaIcsChannelIds: make(map[string]string), + NeutronIcsChannelIds: make(map[string]string), + T: t, + Ctx: ctx, + } + + var noteAddress string + var voiceAddress string + var proxyAddress string + var osmoLiquidPoolerAddress string + var osmoOutpost string + + testCtx.SkipBlocks(5) + + t.Run("generate IBC paths", func(t *testing.T) { + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosNeutron.Config().ChainID, gaiaNeutronIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosOsmosis.Config().ChainID, gaiaOsmosisIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosOsmosis.Config().ChainID, neutronOsmosisIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosAtom.Config().ChainID, gaiaNeutronICSPath) + }) + + t.Run("setup neutron-gaia ICS", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + neutronClients := testCtx.GetChainClients(cosmosNeutron.Config().Name) + atomClients := testCtx.GetChainClients(cosmosAtom.Config().Name) + + err = r.UpdatePath(ctx, eRep, gaiaNeutronICSPath, ibc.PathUpdateOptions{ + SrcClientID: &neutronClients[0].ClientID, + DstClientID: &atomClients[0].ClientID, + }) + require.NoError(t, err) + + atomNeutronICSConnectionId, neutronAtomICSConnectionId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + + utils.GenerateICSChannel(t, ctx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + + utils.CreateValidator(t, ctx, r, eRep, atom, neutron) + testCtx.SkipBlocks(2) + }) + + t.Run("setup IBC interchain clients, connections, and links", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, neutronOsmosisIBCPath, cosmosNeutron, cosmosOsmosis) + neutronOsmosisIBCConnId, osmosisNeutronIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, neutronOsmosisIBCPath, cosmosNeutron, cosmosOsmosis) + utils.LinkPath(t, ctx, r, eRep, cosmosNeutron, cosmosOsmosis, neutronOsmosisIBCPath) + + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaOsmosisIBCPath, cosmosAtom, cosmosOsmosis) + gaiaOsmosisIBCConnId, osmosisGaiaIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaOsmosisIBCPath, cosmosAtom, cosmosOsmosis) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosOsmosis, gaiaOsmosisIBCPath) + + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + atomNeutronIBCConnId, neutronAtomIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosNeutron, gaiaNeutronIBCPath) + }) + + // Start the relayer and clean it up when the test ends. + err = r.StartRelayer(ctx, eRep, gaiaNeutronICSPath, gaiaNeutronIBCPath, gaiaOsmosisIBCPath, neutronOsmosisIBCPath) + require.NoError(t, err, "failed to start relayer with given paths") + t.Cleanup(func() { + err = r.StopRelayer(ctx, eRep) + if err != nil { + t.Logf("failed to stop relayer: %s", err) + } + }) + testCtx.SkipBlocks(2) + + // Once the VSC packet has been relayed, x/bank transfers are + // enabled on Neutron and we can fund its account. + // The funds for this are sent from a "faucet" account created + // by interchaintest in the genesis file. + users := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), atom, neutron, osmosis) + gaiaUser, neutronUser, osmoUser := users[0], users[1], users[2] + + // initialPoolOsmoAmount := int64(600_000_000_000) + initialPoolAtomAmount := int64(60_000_000_000) + osmoHelperAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(999_000_000_000), osmosis)[0] + // hubHelperAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(999_000_000_000), atom)[0] + + // holderAddress := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(100), neutron)[0].Bech32Address(cosmosNeutron.Config().Bech32Prefix) + + // hubNeutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), neutron)[0] + // osmoNeutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), neutron)[0] + + // rqCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + // rqCaseOsmoAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(osmoContributionAmount), osmosis)[0] + + // sideBasedRqCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + // sideBasedRqCaseOsmoAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(osmoContributionAmount), osmosis)[0] + + // happyCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + // happyCaseOsmoAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(osmoContributionAmount), osmosis)[0] + + // sideBasedHappyCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + // sideBasedHappyCaseOsmoAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(osmoContributionAmount), osmosis)[0] + + testCtx.SkipBlocks(5) + + t.Run("determine ibc channels", func(t *testing.T) { + neutronChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosNeutron.Config().ChainID) + gaiaChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosAtom.Config().ChainID) + osmoChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosOsmosis.Config().ChainID) + + // Find all pairwise channels + utils.GetPairwiseTransferChannelIds(testCtx, osmoChannelInfo, neutronChannelInfo, osmosisNeutronIBCConnId, neutronOsmosisIBCConnId, osmosis.Config().Name, neutron.Config().Name) + utils.GetPairwiseTransferChannelIds(testCtx, osmoChannelInfo, gaiaChannelInfo, osmosisGaiaIBCConnId, gaiaOsmosisIBCConnId, osmosis.Config().Name, cosmosAtom.Config().Name) + utils.GetPairwiseTransferChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronIBCConnId, neutronAtomIBCConnId, cosmosAtom.Config().Name, neutron.Config().Name) + utils.GetPairwiseCCVChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronICSConnectionId, neutronAtomICSConnectionId, cosmosAtom.Config().Name, cosmosNeutron.Config().Name) + }) + + t.Run("determine ibc denoms", func(t *testing.T) { + // We can determine the ibc denoms of: + // 1. ATOM on Neutron + neutronAtomIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + nativeAtomDenom, + ) + // 2. Osmo on neutron + neutronOsmoIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + nativeOsmoDenom, + ) + // 3. hub atom => neutron => osmosis + osmoNeutronAtomIbcDenom = testCtx.GetMultihopIbcDenom( + []string{ + testCtx.OsmoTransferChannelIds[cosmosNeutron.Config().Name], + testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + }, + nativeAtomDenom, + ) + // 4. osmosis osmo => neutron => hub + gaiaNeutronOsmoIbcDenom = testCtx.GetMultihopIbcDenom( + []string{ + testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + }, + nativeOsmoDenom, + ) + // 5. hub atom => osmosis + osmosisAtomIbcDenom = testCtx.GetIbcDenom( + testCtx.OsmoTransferChannelIds[cosmosAtom.Config().Name], + nativeAtomDenom, + ) + }) + + t.Run("two party pol covenant setup", func(t *testing.T) { + // Wasm code that we need to store on Neutron + const covenantContractPath = "wasms/valence_covenant_two_party_pol.wasm" + const clockContractPath = "wasms/valence_clock.wasm" + const routerContractPath = "wasms/valence_interchain_router.wasm" + const ibcForwarderContractPath = "wasms/valence_ibc_forwarder.wasm" + const holderContractPath = "wasms/valence_two_party_pol_holder.wasm" + const liquidPoolerPath = "wasms/valence_osmo_liquid_pooler.wasm" + const osmoOutpostPath = "wasms/valence_outpost_osmo_liquid_pooler.wasm" + + // After storing on Neutron, we will receive a code id + // We parse all the subcontracts into uint64 + // The will be required when we instantiate the covenant. + var clockCodeId uint64 + var routerCodeId uint64 + var ibcForwarderCodeId uint64 + var holderCodeId uint64 + var lperCodeId uint64 + var covenantCodeId uint64 + var covenantRqCodeId uint64 + var covenantSideBasedRqCodeId uint64 + var noteCodeId uint64 + var voiceCodeId uint64 + var proxyCodeId uint64 + var osmoOutpostCodeId uint64 + + _, _, _, _, _ = clockCodeId, routerCodeId, ibcForwarderCodeId, holderCodeId, lperCodeId + _, _, _ = covenantCodeId, covenantRqCodeId, covenantSideBasedRqCodeId + + t.Run("deploy covenant contracts", func(t *testing.T) { + // something was going wrong with instantiating the same code twice, + // hence this weird workaround + covenantCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + covenantRqCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + covenantSideBasedRqCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + + // store clock and get code id + clockCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, clockContractPath) + + // store router and get code id + routerCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, routerContractPath) + + // store forwarder and get code id + ibcForwarderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, ibcForwarderContractPath) + + // store lper, get code + lperCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, liquidPoolerPath) + + // store holder and get code id + holderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, holderContractPath) + + testCtx.SkipBlocks(5) + }) + + t.Run("store polytone", func(t *testing.T) { + const polytoneNotePath = "wasms/polytone_note.wasm" + const polytoneVoicePath = "wasms/polytone_voice.wasm" + const polytoneProxyPath = "wasms/polytone_proxy.wasm" + + noteCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, polytoneNotePath) + voiceCodeId = testCtx.StoreContract(cosmosOsmosis, osmoUser, polytoneVoicePath) + proxyCodeId = testCtx.StoreContract(cosmosOsmosis, osmoUser, polytoneProxyPath) + + // store lper, get code + osmoOutpostCodeId = testCtx.StoreContract(cosmosOsmosis, osmoUser, osmoOutpostPath) + + println("noteCodeId: ", noteCodeId) + println("voiceCodeId: ", voiceCodeId) + println("proxyCodeId: ", proxyCodeId) + println("osmoOutpostCodeId: ", osmoOutpostCodeId) + }) + + t.Run("add liquidity to osmo-atom pool", func(t *testing.T) { + + // fund an address on osmosis that will provide liquidity + // at 1:10 ratio of atom/osmo + _, err := testCtx.Hub.SendIBCTransfer( + testCtx.Ctx, + testCtx.GaiaTransferChannelIds[cosmosOsmosis.Config().Name], + gaiaUser.KeyName, + ibc.WalletAmount{ + Address: osmoHelperAccount.Bech32Address(testCtx.Osmosis.Config().Bech32Prefix), + Denom: testCtx.Hub.Config().Denom, + Amount: initialPoolAtomAmount, + }, + ibc.TransferOptions{}) + require.NoError(testCtx.T, err, err) + + testCtx.SkipBlocks(10) + + osmoBal, _ := testCtx.Osmosis.GetBalance( + testCtx.Ctx, + osmoHelperAccount.Bech32Address(testCtx.Osmosis.Config().Bech32Prefix), + "uosmo", + ) + atomBal, _ := testCtx.Osmosis.GetBalance( + testCtx.Ctx, + osmoHelperAccount.Bech32Address(testCtx.Osmosis.Config().Bech32Prefix), + osmosisAtomIbcDenom, + ) + println("osmo helper account atom balance: ", atomBal) + println("osmo helper account osmo balance: ", osmoBal) + + // pool initialized with 0.105~ ratio + osmosisPoolInitConfig := cosmos.OsmosisPoolParams{ + Weights: fmt.Sprintf("50%s,50%s", osmosisAtomIbcDenom, osmosis.Config().Denom), + InitialDeposit: fmt.Sprintf("40000000000%s,330000000000%s", osmosisAtomIbcDenom, osmosis.Config().Denom), + SwapFee: "0.003", + ExitFee: "0.00", + FutureGovernor: "", + } + + // this fails because of wrong gas being set in interchaintest + // underlying `ExecTx` call. we call this just to write the + // config file to the node. + _, err = cosmos.OsmosisCreatePool( + cosmosOsmosis, + ctx, + osmoHelperAccount.KeyName, + osmosisPoolInitConfig, + ) + require.NoError(t, err, err) + testCtx.SkipBlocks(10) + + manualPoolCreationCmd := []string{ + "osmosisd", "tx", "gamm", "create-pool", + "--pool-file", filepath.Join(cosmosOsmosis.HomeDir(), "pool.json"), + "--from", osmoHelperAccount.KeyName, + "--gas", "3502650", + "--keyring-backend", keyring.BackendTest, + "--output", "json", + "--chain-id", cosmosOsmosis.Config().ChainID, + "--node", cosmosOsmosis.GetRPCAddress(), + "--home", cosmosOsmosis.HomeDir(), + "--fees", "50000uosmo", + "-y", + } + _, _, err = cosmosOsmosis.Exec(ctx, manualPoolCreationCmd, nil) + require.NoError(testCtx.T, err, err) + testCtx.SkipBlocks(5) + + queryPoolCmd := []string{"osmosisd", "q", "gamm", "num-pools", + "--node", cosmosOsmosis.GetRPCAddress(), + "--home", cosmosOsmosis.HomeDir(), + "--output", "json", + "--chain-id", cosmosOsmosis.Config().ChainID, + } + numPoolsQueryStdout, _, err := cosmosOsmosis.Exec(testCtx.Ctx, queryPoolCmd, nil) + require.NoError(testCtx.T, err, err) + var res map[string]string + err = json.Unmarshal(numPoolsQueryStdout, &res) + require.NoError(testCtx.T, err, err) + poolId := res["num_pools"] + println("pool id: ", poolId) + newOsmoBal, _ := testCtx.Osmosis.GetBalance( + testCtx.Ctx, + osmoHelperAccount.Bech32Address(testCtx.Osmosis.Config().Bech32Prefix), + "uosmo", + ) + newAtomBal, _ := testCtx.Osmosis.GetBalance( + testCtx.Ctx, + osmoHelperAccount.Bech32Address(testCtx.Osmosis.Config().Bech32Prefix), + osmosisAtomIbcDenom, + ) + + println("deposited osmo: ", osmoBal-newOsmoBal) + println("deposited atom: ", atomBal-newAtomBal) + testCtx.SkipBlocks(5) + }) + + t.Run("instantiate osmosis outpost", func(t *testing.T) { + osmoOutpost = testCtx.InstantiateOsmoOutpost(osmoOutpostCodeId, osmoUser) + println(osmoOutpost) + }) + + t.Run("instantiate polytone note and listener on neutron", func(t *testing.T) { + noteInstantiateMsg := NoteInstantiate{ + BlockMaxGas: "3010000", + } + + noteAddress = testCtx.InstantiateCmdExecNeutron(noteCodeId, "note", noteInstantiateMsg, neutronUser, keyring.BackendTest) + println("note address: ", noteAddress) + }) + + t.Run("instantiate polytone voice on osmosis", func(t *testing.T) { + + voiceInstantiateMsg := VoiceInstantiate{ + ProxyCodeId: proxyCodeId, + BlockMaxGas: 3010000, + } + + voiceAddress = testCtx.InstantiateCmdExecOsmo(voiceCodeId, "voice", voiceInstantiateMsg, osmoUser, keyring.BackendTest) + println("voice address: ", voiceAddress) + }) + + t.Run("create polytone channel", func(t *testing.T) { + err = r.CreateChannel( + ctx, + eRep, + neutronOsmosisIBCPath, + ibc.CreateChannelOptions{ + SourcePortName: fmt.Sprintf("wasm.%s", noteAddress), + DestPortName: fmt.Sprintf("wasm.%s", voiceAddress), + Order: ibc.Unordered, + Version: "polytone-1", + Override: true, + }, + ) + require.NoError(t, err, err) + testCtx.SkipBlocks(10) + }) + + t.Run("create osmo liquid pooler", func(t *testing.T) { + outwardsPfm := ForwardMetadata{ + Receiver: gaiaUser.Bech32Address(testCtx.Hub.Config().Bech32Prefix), + Port: "transfer", + Channel: testCtx.GaiaTransferChannelIds[testCtx.Osmosis.Config().Name], + } + + inwardsPfm := ForwardMetadata{ + Receiver: gaiaUser.Bech32Address(testCtx.Hub.Config().Bech32Prefix), + Port: "transfer", + Channel: testCtx.OsmoTransferChannelIds[testCtx.Hub.Config().Name], + } + + duration := Duration{ + Time: new(uint64), + } + *duration.Time = 50 + + instantiateMsg := OsmoLiquidPoolerInstantiateMsg{ + ClockAddress: neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + HolderAddress: neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NoteAddress: noteAddress, + PoolId: "1", + OsmoIbcTimeout: "200", + Party1ChainInfo: PartyChainInfo{ + NeutronToPartyChainChannel: testCtx.NeutronTransferChannelIds[testCtx.Hub.Config().Name], + PartyChainToNeutronChannel: testCtx.GaiaTransferChannelIds[testCtx.Neutron.Config().Name], + OutwardsPfm: &outwardsPfm, + InwardsPfm: &inwardsPfm, + IbcTimeout: "200", + }, + Party2ChainInfo: PartyChainInfo{ + NeutronToPartyChainChannel: testCtx.NeutronTransferChannelIds[testCtx.Osmosis.Config().Name], + PartyChainToNeutronChannel: testCtx.OsmoTransferChannelIds[testCtx.Neutron.Config().Name], + IbcTimeout: "200", + }, + OsmoToNeutronChannelId: testCtx.OsmoTransferChannelIds[testCtx.Neutron.Config().Name], + Party1DenomInfo: PartyDenomInfo{ + OsmosisCoin: cw.Coin{Denom: osmosisAtomIbcDenom, Amount: strconv.FormatUint(atomContributionAmount, 10)}, + LocalDenom: neutronAtomIbcDenom, + }, + Party2DenomInfo: PartyDenomInfo{ + OsmosisCoin: cw.Coin{Denom: testCtx.Osmosis.Config().Denom, Amount: strconv.FormatUint(osmoContributionAmount, 10)}, + LocalDenom: neutronOsmoIbcDenom, + }, + OsmoOutpost: osmoOutpost, + LpTokenDenom: "gamm/pool/1", + SlippageTolerance: "0.001", + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.10", + AcceptablePriceSpread: "0.04", + }, + FundingDuration: duration, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "10000", + AssetBLimit: "975000004", + }, + } + + osmoLiquidPoolerAddress = testCtx.ManualInstantiate(lperCodeId, instantiateMsg, neutronUser, keyring.BackendTest) + println("liquid pooler address: ", osmoLiquidPoolerAddress) + }) + + t.Run("fund the liquid pooler", func(t *testing.T) { + + err := testCtx.Neutron.SendFunds( + testCtx.Ctx, + neutronUser.KeyName, + ibc.WalletAmount{ + Address: osmoLiquidPoolerAddress, + Denom: "untrn", + Amount: 5000000, + }) + require.NoError(testCtx.T, err, err) + testCtx.SkipBlocks(5) + + _, err = testCtx.Hub.SendIBCTransfer( + testCtx.Ctx, + testCtx.GaiaTransferChannelIds[testCtx.Neutron.Config().Name], + gaiaUser.KeyName, + ibc.WalletAmount{ + Address: osmoLiquidPoolerAddress, + Denom: "uatom", + Amount: int64(atomContributionAmount), + }, + ibc.TransferOptions{}, + ) + require.NoError(testCtx.T, err, err) + testCtx.SkipBlocks(10) + + _, err = testCtx.Osmosis.SendIBCTransfer( + testCtx.Ctx, + testCtx.OsmoTransferChannelIds[testCtx.Neutron.Config().Name], + osmoUser.KeyName, + ibc.WalletAmount{ + Address: osmoLiquidPoolerAddress, + Denom: "uosmo", + Amount: int64(osmoContributionAmount), + }, + ibc.TransferOptions{}, + ) + require.NoError(testCtx.T, err, err) + testCtx.SkipBlocks(10) + + for { + liquidPoolerAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, osmoLiquidPoolerAddress) + liquidPoolerOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, osmoLiquidPoolerAddress) + println("liquidPooler atom bal: ", liquidPoolerAtomBal) + println("liquidPooler osmo bal: ", liquidPoolerOsmoBal) + if liquidPoolerAtomBal >= atomContributionAmount && liquidPoolerOsmoBal >= osmoContributionAmount { + break + } else { + testCtx.SkipBlocks(2) + } + } + }) + + t.Run("tick liquid pooler until proxy is created", func(t *testing.T) { + for { + lperState := testCtx.QueryContractState(osmoLiquidPoolerAddress) + println("osmo liquid pooler state: ", lperState) + if lperState == "proxy_created" { + proxyAddress = testCtx.QueryProxyAddress(osmoLiquidPoolerAddress) + println("proxy address: ", proxyAddress) + break + } else { + testCtx.Tick(osmoLiquidPoolerAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until proxy is funded", func(t *testing.T) { + for { + proxyAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, proxyAddress) + proxyOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, proxyAddress) + println("proxy atom bal: ", proxyAtomBal) + println("proxy osmo bal: ", proxyOsmoBal) + if proxyAtomBal != 0 && proxyOsmoBal != 0 { + break + } else { + testCtx.Tick(osmoLiquidPoolerAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("perform withdrawal", func(t *testing.T) { + response, err := testCtx.Neutron.ExecuteContract( + testCtx.Ctx, + keyring.BackendTest, + osmoLiquidPoolerAddress, + `{"withdraw":{"percentage":"0.5"}}`, + ) + + println("withdraw response: ", string(response)) + println("withdraw error: ", err) + }) + + t.Run("tick until liquidity is provided and proxy receives gamm tokens", func(t *testing.T) { + neutronGammDenom := testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + "gamm/pool/1", + ) + + for { + osmoLiquidPoolerGammBalance := testCtx.QueryNeutronDenomBalance(neutronGammDenom, osmoLiquidPoolerAddress) + proxyGammBalance := testCtx.QueryOsmoDenomBalance("gamm/pool/1", proxyAddress) + proxyAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, proxyAddress) + proxyOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, proxyAddress) + outpostAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, osmoOutpost) + outpostOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, osmoOutpost) + outpostGammBalance := testCtx.QueryOsmoDenomBalance("gamm/pool/1", osmoOutpost) + + println("proxy atom bal: ", proxyAtomBal) + println("proxy osmo bal: ", proxyOsmoBal) + println("outpost atom bal: ", outpostAtomBal) + println("outpost osmo bal: ", outpostOsmoBal) + println("outpost gamm token balance: ", outpostGammBalance) + println("proxy gamm token balance: ", proxyGammBalance) + println("osmo liquid pooler gamm token balance: ", osmoLiquidPoolerGammBalance) + + if proxyGammBalance != 0 { + break + } else { + testCtx.Tick(osmoLiquidPoolerAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until we are able to withdraw", func(t *testing.T) { + claimSuccess := false + for { + testCtx.Tick(osmoLiquidPoolerAddress, keyring.BackendTest, neutronUser.KeyName) + if !claimSuccess { + cmd := []string{"neutrond", "tx", "wasm", "execute", osmoLiquidPoolerAddress, + `{"withdraw":{"percentage":"0.5"}}`, + "--from", neutronUser.GetKeyName(), + "--gas-prices", "0.0untrn", + "--gas-adjustment", `1.5`, + "--output", "json", + "--node", testCtx.Neutron.GetRPCAddress(), + "--home", testCtx.Neutron.HomeDir(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "42069420", + "--keyring-backend", keyring.BackendTest, + "-y", + } + + resp, _, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + if err != nil { + println("claim failed", err) + } else { + claimSuccess = true + } + println("claim response: ", string(resp)) + } + + proxyGammBalance := testCtx.QueryOsmoDenomBalance("gamm/pool/1", proxyAddress) + proxyAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, proxyAddress) + proxyOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, proxyAddress) + neutronUserOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix)) + neutronUserAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix)) + lperAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, osmoLiquidPoolerAddress) + lperOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, osmoLiquidPoolerAddress) + + println("neutron user osmo bal: ", neutronUserOsmoBal) + println("neutron user atom bal: ", neutronUserAtomBal) + + println("proxy atom bal: ", proxyAtomBal) + println("proxy osmo bal: ", proxyOsmoBal) + println("proxy gamm token balance: ", proxyGammBalance) + + println("liquid pooler osmo bal: ", lperOsmoBal) + println("liquid pooler atom bal: ", lperAtomBal) + } + }) + + t.Run("holder exits pool", func(t *testing.T) { + + }) + }) +} diff --git a/interchaintest/two-party-pol/two_party_osmo_pol_test.go b/interchaintest/two-party-pol/two_party_osmo_pol_test.go new file mode 100644 index 00000000..01f6e00f --- /dev/null +++ b/interchaintest/two-party-pol/two_party_osmo_pol_test.go @@ -0,0 +1,1285 @@ +package covenant_two_party_pol + +import ( + "context" + "encoding/json" + "fmt" + "path/filepath" + "strconv" + "testing" + "time" + + cw "github.com/CosmWasm/wasmvm/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + ibctest "github.com/strangelove-ventures/interchaintest/v4" + "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v4/ibc" + "github.com/strangelove-ventures/interchaintest/v4/relayer" + "github.com/strangelove-ventures/interchaintest/v4/testreporter" + "github.com/strangelove-ventures/interchaintest/v4/testutil" + "github.com/stretchr/testify/require" + utils "github.com/timewave-computer/covenants/interchaintest/utils" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +// sets up and tests a two party pol between hub and osmo facilitated by neutron +func TestTwoPartyOsmoPol(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + ctx := context.Background() + + // Modify the the timeout_commit in the config.toml node files + // to reduce the block commit times. This speeds up the tests + // by about 35% + configFileOverrides := make(map[string]any) + configTomlOverrides := make(testutil.Toml) + consensus := make(testutil.Toml) + consensus["timeout_commit"] = "1s" + configTomlOverrides["consensus"] = consensus + configFileOverrides["config/config.toml"] = configTomlOverrides + + // Chain Factory + cf := ibctest.NewBuiltinChainFactory(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)), []*ibctest.ChainSpec{ + {Name: "gaia", Version: "v9.1.0", ChainConfig: ibc.ChainConfig{ + GasAdjustment: 1.3, + GasPrices: "0.0atom", + ModifyGenesis: utils.SetupGaiaGenesis(utils.GetDefaultInterchainGenesisMessages()), + ConfigFileOverrides: configFileOverrides, + }}, + { + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Name: "neutron", + ChainID: "neutron-2", + Images: []ibc.DockerImage{ + { + Repository: "ghcr.io/strangelove-ventures/heighliner/neutron", + Version: "v2.0.0", + UidGid: "1025:1025", + }, + }, + Bin: "neutrond", + Bech32Prefix: "neutron", + Denom: nativeNtrnDenom, + GasPrices: "0.0untrn,0.0uatom", + GasAdjustment: 2.0, + TrustingPeriod: "1197504s", + NoHostMount: false, + ModifyGenesis: utils.SetupNeutronGenesis( + "0.05", + []string{nativeNtrnDenom}, + []string{nativeAtomDenom}, + utils.GetDefaultNeutronInterchainGenesisMessages(), + ), + ConfigFileOverrides: configFileOverrides, + }, + }, + { + Name: "osmosis", + Version: "v17.0.0", + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Bin: "osmosisd", + Bech32Prefix: "osmo", + Denom: nativeOsmoDenom, + ModifyGenesis: utils.SetupOsmoGenesis( + append(utils.GetDefaultInterchainGenesisMessages(), "/ibc.applications.interchain_accounts.v1.InterchainAccount"), + ), + GasPrices: "0.005uosmo", + GasAdjustment: 2.0, + TrustingPeriod: "336h", + NoHostMount: false, + ConfigFileOverrides: configFileOverrides, + }, + }, + }) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + + // We have three chains + atom, neutron, osmosis := chains[0], chains[1], chains[2] + cosmosAtom, cosmosNeutron, cosmosOsmosis := atom.(*cosmos.CosmosChain), neutron.(*cosmos.CosmosChain), osmosis.(*cosmos.CosmosChain) + + // Relayer Factory + client, network := ibctest.DockerSetup(t) + r := ibctest.NewBuiltinRelayerFactory( + ibc.CosmosRly, + zaptest.NewLogger(t, zaptest.Level(zap.InfoLevel)), + relayer.CustomDockerImage("ghcr.io/cosmos/relayer", "v2.4.0", "1000:1000"), + relayer.RelayerOptionExtraStartFlags{Flags: []string{"-p", "events", "-b", "100", "-d", "--log-format", "console"}}, + ).Build(t, client, network) + + // Prep Interchain + ic := ibctest.NewInterchain(). + AddChain(cosmosAtom). + AddChain(cosmosNeutron). + AddChain(cosmosOsmosis). + AddRelayer(r, "relayer"). + AddProviderConsumerLink(ibctest.ProviderConsumerLink{ + Provider: cosmosAtom, + Consumer: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronICSPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronIBCPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosNeutron, + Chain2: cosmosOsmosis, + Relayer: r, + Path: neutronOsmosisIBCPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosOsmosis, + Relayer: r, + Path: gaiaOsmosisIBCPath, + }) + + // Log location + f, err := ibctest.CreateLogFile(fmt.Sprintf("%d.json", time.Now().Unix())) + require.NoError(t, err) + // Reporter/logs + rep := testreporter.NewReporter(f) + eRep := rep.RelayerExecReporter(t) + + // Build interchain + require.NoError( + t, + ic.Build(ctx, eRep, ibctest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + BlockDatabaseFile: ibctest.DefaultBlockDatabaseFilepath(), + SkipPathCreation: true, + }), + "failed to build interchain") + + testCtx := &utils.TestContext{ + Neutron: cosmosNeutron, + Hub: cosmosAtom, + Osmosis: cosmosOsmosis, + OsmoClients: []*ibc.ClientOutput{}, + GaiaClients: []*ibc.ClientOutput{}, + NeutronClients: []*ibc.ClientOutput{}, + OsmoConnections: []*ibc.ConnectionOutput{}, + GaiaConnections: []*ibc.ConnectionOutput{}, + NeutronConnections: []*ibc.ConnectionOutput{}, + NeutronTransferChannelIds: make(map[string]string), + GaiaTransferChannelIds: make(map[string]string), + OsmoTransferChannelIds: make(map[string]string), + GaiaIcsChannelIds: make(map[string]string), + NeutronIcsChannelIds: make(map[string]string), + T: t, + Ctx: ctx, + } + + var noteAddress string + var voiceAddress string + var proxyAddress string + var osmoOutpost string + + testCtx.SkipBlocks(5) + + t.Run("generate IBC paths", func(t *testing.T) { + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosNeutron.Config().ChainID, gaiaNeutronIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosOsmosis.Config().ChainID, gaiaOsmosisIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosOsmosis.Config().ChainID, neutronOsmosisIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosAtom.Config().ChainID, gaiaNeutronICSPath) + }) + + t.Run("setup neutron-gaia ICS", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + neutronClients := testCtx.GetChainClients(cosmosNeutron.Config().Name) + atomClients := testCtx.GetChainClients(cosmosAtom.Config().Name) + + err = r.UpdatePath(ctx, eRep, gaiaNeutronICSPath, ibc.PathUpdateOptions{ + SrcClientID: &neutronClients[0].ClientID, + DstClientID: &atomClients[0].ClientID, + }) + require.NoError(t, err) + + atomNeutronICSConnectionId, neutronAtomICSConnectionId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + + utils.GenerateICSChannel(t, ctx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + + utils.CreateValidator(t, ctx, r, eRep, atom, neutron) + testCtx.SkipBlocks(2) + }) + + t.Run("setup IBC interchain clients, connections, and links", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, neutronOsmosisIBCPath, cosmosNeutron, cosmosOsmosis) + neutronOsmosisIBCConnId, osmosisNeutronIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, neutronOsmosisIBCPath, cosmosNeutron, cosmosOsmosis) + utils.LinkPath(t, ctx, r, eRep, cosmosNeutron, cosmosOsmosis, neutronOsmosisIBCPath) + + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaOsmosisIBCPath, cosmosAtom, cosmosOsmosis) + gaiaOsmosisIBCConnId, osmosisGaiaIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaOsmosisIBCPath, cosmosAtom, cosmosOsmosis) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosOsmosis, gaiaOsmosisIBCPath) + + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + atomNeutronIBCConnId, neutronAtomIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosNeutron, gaiaNeutronIBCPath) + }) + + // Start the relayer and clean it up when the test ends. + err = r.StartRelayer(ctx, eRep, gaiaNeutronICSPath, gaiaNeutronIBCPath, gaiaOsmosisIBCPath, neutronOsmosisIBCPath) + require.NoError(t, err, "failed to start relayer with given paths") + t.Cleanup(func() { + err = r.StopRelayer(ctx, eRep) + if err != nil { + t.Logf("failed to stop relayer: %s", err) + } + }) + testCtx.SkipBlocks(2) + + // Once the VSC packet has been relayed, x/bank transfers are + // enabled on Neutron and we can fund its account. + // The funds for this are sent from a "faucet" account created + // by interchaintest in the genesis file. + users := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), atom, neutron, osmosis) + gaiaUser, neutronUser, osmoUser := users[0], users[1], users[2] + + initialPoolAtomAmount := int64(60_000_000_000) + osmoHelperAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(999_000_000_000), osmosis)[0] + + happyCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + happyCaseOsmoAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", 5*int64(osmoContributionAmount), osmosis)[0] + + sadCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + sadCaseOsmoAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", 5*int64(osmoContributionAmount), osmosis)[0] + + osmoPartyNeutronAddr := ibctest.GetAndFundTestUsers(t, ctx, "default", 100000000, neutron)[0] + hubPartyNeutronAddr := ibctest.GetAndFundTestUsers(t, ctx, "default", 100000000, neutron)[0] + + testCtx.SkipBlocks(5) + + t.Run("determine ibc channels", func(t *testing.T) { + neutronChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosNeutron.Config().ChainID) + gaiaChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosAtom.Config().ChainID) + osmoChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosOsmosis.Config().ChainID) + + // Find all pairwise channels + utils.GetPairwiseTransferChannelIds(testCtx, osmoChannelInfo, neutronChannelInfo, osmosisNeutronIBCConnId, neutronOsmosisIBCConnId, osmosis.Config().Name, neutron.Config().Name) + utils.GetPairwiseTransferChannelIds(testCtx, osmoChannelInfo, gaiaChannelInfo, osmosisGaiaIBCConnId, gaiaOsmosisIBCConnId, osmosis.Config().Name, cosmosAtom.Config().Name) + utils.GetPairwiseTransferChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronIBCConnId, neutronAtomIBCConnId, cosmosAtom.Config().Name, neutron.Config().Name) + utils.GetPairwiseCCVChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronICSConnectionId, neutronAtomICSConnectionId, cosmosAtom.Config().Name, cosmosNeutron.Config().Name) + }) + + t.Run("determine ibc denoms", func(t *testing.T) { + neutronAtomIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + nativeAtomDenom, + ) + neutronOsmoIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + nativeOsmoDenom, + ) + osmosisAtomIbcDenom = testCtx.GetIbcDenom( + testCtx.OsmoTransferChannelIds[cosmosAtom.Config().Name], + nativeAtomDenom, + ) + hubOsmoIbcDenom = testCtx.GetIbcDenom( + testCtx.GaiaTransferChannelIds[cosmosOsmosis.Config().Name], + nativeOsmoDenom, + ) + }) + + t.Run("two party pol covenant setup", func(t *testing.T) { + // Wasm code that we need to store on Neutron + const covenantContractPath = "wasms/valence_covenant_two_party_pol.wasm" + const clockContractPath = "wasms/valence_clock.wasm" + const interchainRouterContractPath = "wasms/valence_interchain_router.wasm" + const ibcForwarderContractPath = "wasms/valence_ibc_forwarder.wasm" + const holderContractPath = "wasms/valence_two_party_pol_holder.wasm" + const liquidPoolerPath = "wasms/valence_osmo_liquid_pooler.wasm" + const osmoOutpostPath = "wasms/valence_outpost_osmo_liquid_pooler.wasm" + const nativeRouterContractPath = "wasms/valence_native_router.wasm" + + // After storing on Neutron, we will receive a code id + // We parse all the subcontracts into uint64 + // The will be required when we instantiate the covenant. + var clockCodeId uint64 + var nativeRouterCodeId uint64 + var interchainRouterCodeId uint64 + var ibcForwarderCodeId uint64 + var holderCodeId uint64 + var lperCodeId uint64 + var covenantCodeId uint64 + var covenantRqCodeId uint64 + var covenantSideBasedRqCodeId uint64 + var noteCodeId uint64 + var voiceCodeId uint64 + var proxyCodeId uint64 + var osmoOutpostCodeId uint64 + + _, _, _, _, _, _ = clockCodeId, nativeRouterCodeId, interchainRouterCodeId, ibcForwarderCodeId, holderCodeId, lperCodeId + _, _, _ = covenantCodeId, covenantRqCodeId, covenantSideBasedRqCodeId + + t.Run("deploy covenant contracts", func(t *testing.T) { + // something was going wrong with instantiating the same code twice, + // hence this weird workaround + covenantCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + covenantRqCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + covenantSideBasedRqCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + + // store clock and get code id + clockCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, clockContractPath) + + // store router and get code id + nativeRouterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, nativeRouterContractPath) + + // store router and get code id + interchainRouterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, interchainRouterContractPath) + + // store forwarder and get code id + ibcForwarderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, ibcForwarderContractPath) + + // store lper, get code + lperCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, liquidPoolerPath) + + // store holder and get code id + holderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, holderContractPath) + + testCtx.SkipBlocks(5) + }) + + t.Run("store polytone", func(t *testing.T) { + const polytoneNotePath = "wasms/polytone_note.wasm" + const polytoneVoicePath = "wasms/polytone_voice.wasm" + const polytoneProxyPath = "wasms/polytone_proxy.wasm" + + noteCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, polytoneNotePath) + voiceCodeId = testCtx.StoreContract(cosmosOsmosis, osmoUser, polytoneVoicePath) + proxyCodeId = testCtx.StoreContract(cosmosOsmosis, osmoUser, polytoneProxyPath) + + // store lper, get code + osmoOutpostCodeId = testCtx.StoreContract(cosmosOsmosis, osmoUser, osmoOutpostPath) + + println("noteCodeId: ", noteCodeId) + println("voiceCodeId: ", voiceCodeId) + println("proxyCodeId: ", proxyCodeId) + println("osmoOutpostCodeId: ", osmoOutpostCodeId) + }) + + t.Run("add liquidity to osmo-atom pool", func(t *testing.T) { + + // fund an address on osmosis that will provide liquidity + // at 1:10 ratio of atom/osmo + _, err := testCtx.Hub.SendIBCTransfer( + testCtx.Ctx, + testCtx.GaiaTransferChannelIds[cosmosOsmosis.Config().Name], + gaiaUser.KeyName, + ibc.WalletAmount{ + Address: osmoHelperAccount.Bech32Address(testCtx.Osmosis.Config().Bech32Prefix), + Denom: testCtx.Hub.Config().Denom, + Amount: initialPoolAtomAmount, + }, + ibc.TransferOptions{}) + require.NoError(testCtx.T, err, err) + + testCtx.SkipBlocks(10) + + osmoBal := testCtx.QueryOsmoDenomBalance("uosmo", osmoHelperAccount.Bech32Address(testCtx.Osmosis.Config().Bech32Prefix)) + atomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, osmoHelperAccount.Bech32Address(testCtx.Osmosis.Config().Bech32Prefix)) + println("osmo helper account atom balance: ", atomBal) + println("osmo helper account osmo balance: ", osmoBal) + + // pool initialized with 0.105~ ratio + osmosisPoolInitConfig := cosmos.OsmosisPoolParams{ + Weights: fmt.Sprintf("50%s,50%s", osmosisAtomIbcDenom, osmosis.Config().Denom), + InitialDeposit: fmt.Sprintf("40000000000%s,330000000000%s", osmosisAtomIbcDenom, osmosis.Config().Denom), + SwapFee: "0.003", + ExitFee: "0.00", + FutureGovernor: "", + } + + // this fails because of wrong gas being set in interchaintest + // underlying `ExecTx` call. we call this just to write the + // config file to the node. + _, err = cosmos.OsmosisCreatePool( + testCtx.Osmosis, + testCtx.Ctx, + osmoHelperAccount.KeyName, + osmosisPoolInitConfig, + ) + require.NoError(testCtx.T, err, err) + testCtx.SkipBlocks(10) + + manualPoolCreationCmd := []string{ + "osmosisd", "tx", "gamm", "create-pool", + "--pool-file", filepath.Join(testCtx.Osmosis.HomeDir(), "pool.json"), + "--from", osmoHelperAccount.KeyName, + "--gas", "3502650", + "--keyring-backend", keyring.BackendTest, + "--output", "json", + "--chain-id", testCtx.Osmosis.Config().ChainID, + "--node", testCtx.Osmosis.GetRPCAddress(), + "--home", testCtx.Osmosis.HomeDir(), + "--fees", "50000uosmo", + "-y", + } + _, _, err = testCtx.Osmosis.Exec(testCtx.Ctx, manualPoolCreationCmd, nil) + require.NoError(testCtx.T, err, err) + testCtx.SkipBlocks(5) + + queryPoolCmd := []string{"osmosisd", "q", "gamm", "num-pools", + "--node", testCtx.Osmosis.GetRPCAddress(), + "--home", testCtx.Osmosis.HomeDir(), + "--output", "json", + "--chain-id", testCtx.Osmosis.Config().ChainID, + } + numPoolsQueryStdout, _, err := testCtx.Osmosis.Exec(testCtx.Ctx, queryPoolCmd, nil) + require.NoError(testCtx.T, err, err) + var res map[string]string + err = json.Unmarshal(numPoolsQueryStdout, &res) + require.NoError(testCtx.T, err, err) + poolId := res["num_pools"] + println("pool id: ", poolId) + newOsmoBal := testCtx.QueryOsmoDenomBalance("uosmo", osmoHelperAccount.Bech32Address(testCtx.Osmosis.Config().Bech32Prefix)) + newAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, osmoHelperAccount.Bech32Address(testCtx.Osmosis.Config().Bech32Prefix)) + + println("deposited osmo: ", uint64(osmoBal)-newOsmoBal) + println("deposited atom: ", uint64(atomBal)-newAtomBal) + testCtx.SkipBlocks(5) + }) + + t.Run("instantiate osmosis outpost", func(t *testing.T) { + osmoOutpost = testCtx.InstantiateOsmoOutpost(osmoOutpostCodeId, osmoUser) + println(osmoOutpost) + }) + + t.Run("instantiate polytone note and listener on neutron", func(t *testing.T) { + noteInstantiateMsg := NoteInstantiate{ + BlockMaxGas: "3010000", + } + + noteAddress = testCtx.InstantiateCmdExecNeutron(noteCodeId, "note", noteInstantiateMsg, neutronUser, keyring.BackendTest) + println("note address: ", noteAddress) + }) + + t.Run("instantiate polytone voice on osmosis", func(t *testing.T) { + + voiceInstantiateMsg := VoiceInstantiate{ + ProxyCodeId: proxyCodeId, + BlockMaxGas: 3010000, + } + + voiceAddress = testCtx.InstantiateCmdExecOsmo(voiceCodeId, "voice", voiceInstantiateMsg, osmoUser, keyring.BackendTest) + println("voice address: ", voiceAddress) + }) + + t.Run("create polytone channel", func(t *testing.T) { + err = r.CreateChannel( + ctx, + eRep, + neutronOsmosisIBCPath, + ibc.CreateChannelOptions{ + SourcePortName: fmt.Sprintf("wasm.%s", noteAddress), + DestPortName: fmt.Sprintf("wasm.%s", voiceAddress), + Order: ibc.Unordered, + Version: "polytone-1", + Override: true, + }, + ) + require.NoError(t, err, err) + testCtx.SkipBlocks(10) + }) + + t.Run("two party POL withdraw pre-lp tokens", func(t *testing.T) { + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "100", // sec + IbcTransferTimeout: "100", // sec + } + + currentHeight := testCtx.GetNeutronHeight() + depositBlock := Block(currentHeight + 200) + lockupBlock := Block(currentHeight + 210) + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + depositDeadline := Expiration{ + AtHeight: &depositBlock, + } + + hubReceiverAddr := sadCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix) + osmoReceiverAddr := sadCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix) + + atomCoin := Coin{ + Denom: cosmosAtom.Config().Denom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + + osmoCoin := Coin{ + Denom: cosmosOsmosis.Config().Denom, + Amount: strconv.FormatUint(osmoContributionAmount, 10), + } + + outwardsPfm := ForwardMetadata{ + Receiver: gaiaUser.Bech32Address(testCtx.Hub.Config().Bech32Prefix), + Port: "transfer", + Channel: testCtx.GaiaTransferChannelIds[testCtx.Osmosis.Config().Name], + } + + inwardsPfm := ForwardMetadata{ + Receiver: gaiaUser.Bech32Address(testCtx.Hub.Config().Bech32Prefix), + Port: "transfer", + Channel: testCtx.OsmoTransferChannelIds[testCtx.Hub.Config().Name], + } + + codeIds := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + } + + denomSplits := map[string]SplitConfig{ + neutronAtomIbcDenom: { + Receivers: map[string]string{ + hubReceiverAddr: "0.5", + osmoReceiverAddr: "0.5", + }, + }, + neutronOsmoIbcDenom: { + Receivers: map[string]string{ + hubReceiverAddr: "0.5", + osmoReceiverAddr: "0.5", + }, + }, + } + + // for party 1 (hub), we need to route osmosis correctly - neutron->osmosis->hub + party1PfmMap := map[string]PacketForwardMiddlewareConfig{ + neutronOsmoIbcDenom: { + LocalToHopChainChannelId: testCtx.NeutronTransferChannelIds[testCtx.Osmosis.Config().Name], + HopToDestinationChainChannelId: testCtx.OsmoTransferChannelIds[testCtx.Hub.Config().Name], + HopChainReceiverAddress: osmoUser.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), + }, + } + + partyAConfig := InterchainCovenantParty{ + Addr: hubPartyNeutronAddr.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronAtomIbcDenom, + RemoteChainDenom: "uatom", + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyReceiverAddr: hubReceiverAddr, + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + Contribution: atomCoin, + DenomToPfmMap: party1PfmMap, + } + // for party 2 (osmosis), we need to route atom correctly - neutron->hub->osmosis + party2PfmMap := map[string]PacketForwardMiddlewareConfig{ + neutronAtomIbcDenom: { + LocalToHopChainChannelId: testCtx.NeutronTransferChannelIds[testCtx.Hub.Config().Name], + HopToDestinationChainChannelId: testCtx.GaiaTransferChannelIds[testCtx.Osmosis.Config().Name], + HopChainReceiverAddress: gaiaUser.Bech32Address(cosmosAtom.Config().Bech32Prefix), + }, + } + partyBConfig := InterchainCovenantParty{ + Addr: osmoPartyNeutronAddr.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronOsmoIbcDenom, + RemoteChainDenom: "uosmo", + PartyToHostChainChannelId: testCtx.OsmoTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + PartyReceiverAddr: osmoReceiverAddr, + PartyChainConnectionId: neutronOsmosisIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + Contribution: osmoCoin, + DenomToPfmMap: party2PfmMap, + } + fundingDuration := Duration{ + Time: new(uint64), + } + *fundingDuration.Time = 300 + + liquidPoolerConfig := LiquidPoolerConfig{ + Osmosis: &OsmosisLiquidPoolerConfig{ + NoteAddress: noteAddress, + PoolId: "1", + OsmoIbcTimeout: "300", + Party1ChainInfo: PartyChainInfo{ + PartyChainToNeutronChannel: testCtx.GaiaTransferChannelIds[testCtx.Neutron.Config().Name], + NeutronToPartyChainChannel: testCtx.NeutronTransferChannelIds[testCtx.Hub.Config().Name], + InwardsPfm: &inwardsPfm, + OutwardsPfm: &outwardsPfm, + IbcTimeout: "300", + }, + Party2ChainInfo: PartyChainInfo{ + NeutronToPartyChainChannel: testCtx.NeutronTransferChannelIds[testCtx.Osmosis.Config().Name], + PartyChainToNeutronChannel: testCtx.OsmoTransferChannelIds[testCtx.Neutron.Config().Name], + IbcTimeout: "300", + }, + OsmoToNeutronChannelId: testCtx.OsmoTransferChannelIds[testCtx.Neutron.Config().Name], + Party1DenomInfo: PartyDenomInfo{ + OsmosisCoin: cw.Coin{Denom: osmosisAtomIbcDenom, Amount: strconv.FormatUint(atomContributionAmount, 10)}, + LocalDenom: neutronAtomIbcDenom, + }, + Party2DenomInfo: PartyDenomInfo{ + OsmosisCoin: cw.Coin{Denom: testCtx.Osmosis.Config().Denom, Amount: strconv.FormatUint(osmoContributionAmount, 10)}, + LocalDenom: neutronOsmoIbcDenom, + }, + LpTokenDenom: "gamm/pool/1", + OsmoOutpost: osmoOutpost, + FundingDuration: fundingDuration, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "10000", + AssetBLimit: "975000004", + }, + }, + } + + covenantInstantiateMsg := CovenantInstantiateMsg{ + Label: "covenant-osmo", + Timeouts: timeouts, + ContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{Interchain: &partyAConfig}, + PartyBConfig: CovenantPartyConfig{Interchain: &partyBConfig}, + DepositDeadline: depositDeadline, + CovenantType: "share", + PartyAShare: "0.5", + PartyBShare: "0.5", + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.51", + AcceptablePriceSpread: "0.09", + }, + Splits: denomSplits, + FallbackSplit: nil, + EmergencyCommittee: neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + LiquidPoolerConfig: liquidPoolerConfig, + } + + covenantAddress = testCtx.ManualInstantiate(covenantRqCodeId, covenantInstantiateMsg, neutronUser, keyring.BackendTest) + println("covenantAddress address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + partyAIbcForwarderAddress, + partyBIbcForwarderAddress, + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + liquidPoolerAddress, + } + println("funding addresses with 5000000000untrn") + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + }) + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + testCtx.SkipBlocks(5) + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + forwarderBState := testCtx.QueryContractState(partyBIbcForwarderAddress) + + if forwarderAState == forwarderBState && forwarderBState == "ica_created" { + testCtx.SkipBlocks(3) + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyBDepositAddress}, cosmosOsmosis, sadCaseOsmoAccount, int64(osmoContributionAmount)) + testCtx.FundChainAddrs([]string{partyADepositAddress}, cosmosAtom, sadCaseHubAccount, int64(atomContributionAmount)) + + testCtx.SkipBlocks(5) + + osmoBal := testCtx.QueryOsmoDenomBalance(cosmosOsmosis.Config().Denom, partyBDepositAddress) + atomBal := testCtx.QueryHubDenomBalance(nativeAtomDenom, partyADepositAddress) + println("covenant party deposits") + println(partyADepositAddress, " balance: ", atomBal, nativeAtomDenom) + println(partyBDepositAddress, " balance: ", osmoBal, cosmosOsmosis.Config().Denom) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + holderOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + + println("holder atom bal: ", holderAtomBal) + println("holder osmo bal: ", holderOsmoBal) + println("holder state: ", holderState) + + if holderAtomBal == atomContributionAmount && holderOsmoBal == osmoContributionAmount { + println("holder received atom & osmo") + break + } else if holderState == "active" { + println("holder is active") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until holder sends funds to LP", func(t *testing.T) { + for { + liquidPoolerAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, liquidPoolerAddress) + liquidPoolerOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, liquidPoolerAddress) + + if liquidPoolerAtomBal == 0 && liquidPoolerOsmoBal != 0 { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("tick until liquid pooler proxy is created", func(t *testing.T) { + for { + lperState := testCtx.QueryContractState(liquidPoolerAddress) + println("osmo liquid pooler state: ", lperState) + if lperState == "proxy_created" { + proxyAddress = testCtx.QueryProxyAddress(liquidPoolerAddress) + println("proxy address: ", proxyAddress) + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until proxy is funded", func(t *testing.T) { + for { + proxyAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, proxyAddress) + proxyOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, proxyAddress) + println("proxy atom bal: ", proxyAtomBal) + println("proxy osmo bal: ", proxyOsmoBal) + if proxyAtomBal != 0 && proxyOsmoBal != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("perform withdrawal", func(t *testing.T) { + liquidPoolerState := testCtx.QueryContractState(liquidPoolerAddress) + println("liquid pooler state: ", liquidPoolerState) + cmd := []string{"neutrond", "tx", "wasm", "execute", holderAddress, + `{"emergency_withdraw":{}}`, + "--from", neutronUser.GetKeyName(), + "--gas-prices", "0.0untrn", + "--gas-adjustment", `1.5`, + "--output", "json", + "--node", testCtx.Neutron.GetRPCAddress(), + "--home", testCtx.Neutron.HomeDir(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "42069420", + "--keyring-backend", keyring.BackendTest, + "-y", + } + + stdout, stderr, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + require.NoError(testCtx.T, err, err) + + println("withdraw stdout: ", string(stdout)) + println("withdraw stderr: ", string(stderr)) + + for { + proxyAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, proxyAddress) + proxyOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, proxyAddress) + println("proxy atom bal: ", proxyAtomBal) + println("proxy osmo bal: ", proxyOsmoBal) + + lperAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, liquidPoolerAddress) + lperOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, liquidPoolerAddress) + + println("liquid pooler atom bal: ", lperAtomBal) + println("liquid pooler osmo bal: ", lperOsmoBal) + + p1RouterAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + p1RouterOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, partyARouterAddress) + + p2RouterAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + p2RouterOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, partyBRouterAddress) + println("p1RouterAtomBal: ", p1RouterAtomBal) + println("p1RouterOsmoBal: ", p1RouterOsmoBal) + println("p2RouterAtomBal: ", p2RouterAtomBal) + println("p2RouterOsmoBal: ", p2RouterOsmoBal) + + if p1RouterAtomBal != 0 || p1RouterOsmoBal != 0 || p2RouterAtomBal != 0 || p2RouterOsmoBal != 0 { + println("withdraw success!") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until parties get their funds", func(t *testing.T) { + for { + hubPartyReceiverAddrAtomBal := testCtx.QueryHubDenomBalance( + "uatom", + sadCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), + ) + hubPartyReceiverAddrOsmoBal := testCtx.QueryHubDenomBalance( + hubOsmoIbcDenom, + sadCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), + ) + osmoPartyReceiverAddrOsmoBal := testCtx.QueryOsmoDenomBalance( + "uosmo", + sadCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), + ) + osmoPartyReceiverAddrAtomBal := testCtx.QueryOsmoDenomBalance( + osmosisAtomIbcDenom, + sadCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), + ) + + println("hubPartyReceiverAddrAtomBal", hubPartyReceiverAddrAtomBal) + println("hubPartyReceiverAddrOsmoBal", hubPartyReceiverAddrOsmoBal) + println("osmoPartyReceiverAddrOsmoBal", osmoPartyReceiverAddrOsmoBal) + println("osmoPartyReceiverAddrAtomBal", osmoPartyReceiverAddrAtomBal) + + if osmoPartyReceiverAddrOsmoBal != 0 && osmoPartyReceiverAddrAtomBal != 0 && hubPartyReceiverAddrAtomBal != 0 && hubPartyReceiverAddrOsmoBal != 0 { + println("parties received the funds") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + }) + + t.Run("two party POL full path", func(t *testing.T) { + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "100", // sec + IbcTransferTimeout: "100", // sec + } + + currentHeight := testCtx.GetNeutronHeight() + depositBlock := Block(currentHeight + 250) + lockupBlock := Block(currentHeight + 300) + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + depositDeadline := Expiration{ + AtHeight: &depositBlock, + } + + hubReceiverAddr := happyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix) + osmoReceiverAddr := happyCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix) + + atomCoin := Coin{ + Denom: cosmosAtom.Config().Denom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + + osmoCoin := Coin{ + Denom: cosmosOsmosis.Config().Denom, + Amount: strconv.FormatUint(osmoContributionAmount, 10), + } + + outwardsPfm := ForwardMetadata{ + Receiver: gaiaUser.Bech32Address(testCtx.Hub.Config().Bech32Prefix), + Port: "transfer", + Channel: testCtx.GaiaTransferChannelIds[testCtx.Osmosis.Config().Name], + } + + inwardsPfm := ForwardMetadata{ + Receiver: gaiaUser.Bech32Address(testCtx.Hub.Config().Bech32Prefix), + Port: "transfer", + Channel: testCtx.OsmoTransferChannelIds[testCtx.Hub.Config().Name], + } + + codeIds := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + } + + denomSplits := map[string]SplitConfig{ + neutronAtomIbcDenom: { + Receivers: map[string]string{ + hubReceiverAddr: "0.5", + osmoReceiverAddr: "0.5", + }, + }, + neutronOsmoIbcDenom: { + Receivers: map[string]string{ + hubReceiverAddr: "0.5", + osmoReceiverAddr: "0.5", + }, + }, + } + + // for party 1 (hub), we need to route osmosis correctly - neutron->osmosis->hub + party1PfmMap := map[string]PacketForwardMiddlewareConfig{ + neutronOsmoIbcDenom: { + LocalToHopChainChannelId: testCtx.NeutronTransferChannelIds[testCtx.Osmosis.Config().Name], + HopToDestinationChainChannelId: testCtx.OsmoTransferChannelIds[testCtx.Hub.Config().Name], + HopChainReceiverAddress: osmoUser.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), + }, + } + + partyAConfig := InterchainCovenantParty{ + Addr: hubPartyNeutronAddr.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronAtomIbcDenom, + RemoteChainDenom: "uatom", + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyReceiverAddr: hubReceiverAddr, + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + Contribution: atomCoin, + DenomToPfmMap: party1PfmMap, + } + // for party 2 (osmosis), we need to route atom correctly - neutron->hub->osmosis + party2PfmMap := map[string]PacketForwardMiddlewareConfig{ + neutronAtomIbcDenom: { + LocalToHopChainChannelId: testCtx.NeutronTransferChannelIds[testCtx.Hub.Config().Name], + HopToDestinationChainChannelId: testCtx.GaiaTransferChannelIds[testCtx.Osmosis.Config().Name], + HopChainReceiverAddress: gaiaUser.Bech32Address(cosmosAtom.Config().Bech32Prefix), + }, + } + partyBConfig := InterchainCovenantParty{ + Addr: osmoPartyNeutronAddr.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronOsmoIbcDenom, + RemoteChainDenom: "uosmo", + PartyToHostChainChannelId: testCtx.OsmoTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + PartyReceiverAddr: osmoReceiverAddr, + PartyChainConnectionId: neutronOsmosisIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + Contribution: osmoCoin, + DenomToPfmMap: party2PfmMap, + } + fundingDuration := Duration{ + Time: new(uint64), + } + *fundingDuration.Time = 400 + + liquidPoolerConfig := LiquidPoolerConfig{ + Osmosis: &OsmosisLiquidPoolerConfig{ + NoteAddress: noteAddress, + PoolId: "1", + OsmoIbcTimeout: "300", + Party1ChainInfo: PartyChainInfo{ + PartyChainToNeutronChannel: testCtx.GaiaTransferChannelIds[testCtx.Neutron.Config().Name], + NeutronToPartyChainChannel: testCtx.NeutronTransferChannelIds[testCtx.Hub.Config().Name], + InwardsPfm: &inwardsPfm, + OutwardsPfm: &outwardsPfm, + IbcTimeout: "300", + }, + Party2ChainInfo: PartyChainInfo{ + NeutronToPartyChainChannel: testCtx.NeutronTransferChannelIds[testCtx.Osmosis.Config().Name], + PartyChainToNeutronChannel: testCtx.OsmoTransferChannelIds[testCtx.Neutron.Config().Name], + IbcTimeout: "300", + }, + OsmoToNeutronChannelId: testCtx.OsmoTransferChannelIds[testCtx.Neutron.Config().Name], + Party1DenomInfo: PartyDenomInfo{ + OsmosisCoin: cw.Coin{Denom: osmosisAtomIbcDenom, Amount: strconv.FormatUint(atomContributionAmount, 10)}, + LocalDenom: neutronAtomIbcDenom, + }, + Party2DenomInfo: PartyDenomInfo{ + OsmosisCoin: cw.Coin{Denom: testCtx.Osmosis.Config().Denom, Amount: strconv.FormatUint(osmoContributionAmount, 10)}, + LocalDenom: neutronOsmoIbcDenom, + }, + LpTokenDenom: "gamm/pool/1", + OsmoOutpost: osmoOutpost, + FundingDuration: fundingDuration, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "10000", + AssetBLimit: "975000004", + }, + }, + } + + covenantInstantiateMsg := CovenantInstantiateMsg{ + Label: "covenant-osmo", + Timeouts: timeouts, + ContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{Interchain: &partyAConfig}, + PartyBConfig: CovenantPartyConfig{Interchain: &partyBConfig}, + DepositDeadline: depositDeadline, + CovenantType: "share", + PartyAShare: "0.5", + PartyBShare: "0.5", + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.1", + AcceptablePriceSpread: "0.09", + }, + Splits: denomSplits, + FallbackSplit: nil, + EmergencyCommittee: neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + LiquidPoolerConfig: liquidPoolerConfig, + } + + covenantAddress = testCtx.ManualInstantiate(covenantCodeId, covenantInstantiateMsg, neutronUser, keyring.BackendTest) + println("covenantAddress address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + partyAIbcForwarderAddress, + partyBIbcForwarderAddress, + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + liquidPoolerAddress, + } + println("funding addresses with 5000000000untrn") + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + }) + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + testCtx.SkipBlocks(5) + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + forwarderBState := testCtx.QueryContractState(partyBIbcForwarderAddress) + + if forwarderAState == forwarderBState && forwarderBState == "ica_created" { + testCtx.SkipBlocks(3) + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyBDepositAddress}, cosmosOsmosis, happyCaseOsmoAccount, int64(osmoContributionAmount)) + testCtx.FundChainAddrs([]string{partyADepositAddress}, cosmosAtom, happyCaseHubAccount, int64(atomContributionAmount)) + + testCtx.SkipBlocks(5) + + osmoBal := testCtx.QueryOsmoDenomBalance(cosmosOsmosis.Config().Denom, partyBDepositAddress) + atomBal := testCtx.QueryHubDenomBalance(nativeAtomDenom, partyADepositAddress) + println("covenant party deposits") + println(partyADepositAddress, " balance: ", atomBal, nativeAtomDenom) + println(partyBDepositAddress, " balance: ", osmoBal, cosmosOsmosis.Config().Denom) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + holderOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + + println("holder atom bal: ", holderAtomBal) + println("holder osmo bal: ", holderOsmoBal) + println("holder state: ", holderState) + + if holderAtomBal == atomContributionAmount && holderOsmoBal == osmoContributionAmount { + println("holder received atom & osmo") + break + } else if holderState == "active" { + println("holder is active") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until holder sends funds to LP", func(t *testing.T) { + for { + liquidPoolerAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, liquidPoolerAddress) + liquidPoolerOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, liquidPoolerAddress) + + if liquidPoolerAtomBal == 0 && liquidPoolerOsmoBal != 0 { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("tick until liquid pooler proxy is created", func(t *testing.T) { + for { + lperState := testCtx.QueryContractState(liquidPoolerAddress) + println("osmo liquid pooler state: ", lperState) + if lperState == "proxy_created" { + proxyAddress = testCtx.QueryProxyAddress(liquidPoolerAddress) + println("proxy address: ", proxyAddress) + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until proxy is funded", func(t *testing.T) { + for { + proxyAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, proxyAddress) + proxyOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, proxyAddress) + println("proxy atom bal: ", proxyAtomBal) + println("proxy osmo bal: ", proxyOsmoBal) + if proxyAtomBal != 0 && proxyOsmoBal != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until liquidity is provided and proxy receives gamm tokens", func(t *testing.T) { + neutronGammDenom := testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + "gamm/pool/1", + ) + + for { + osmoLiquidPoolerGammBalance := testCtx.QueryNeutronDenomBalance(neutronGammDenom, liquidPoolerAddress) + proxyGammBalance := testCtx.QueryOsmoDenomBalance("gamm/pool/1", proxyAddress) + proxyAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, proxyAddress) + proxyOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, proxyAddress) + outpostAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, osmoOutpost) + outpostOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, osmoOutpost) + outpostGammBalance := testCtx.QueryOsmoDenomBalance("gamm/pool/1", osmoOutpost) + + println("proxy atom bal: ", proxyAtomBal) + println("proxy osmo bal: ", proxyOsmoBal) + println("outpost atom bal: ", outpostAtomBal) + println("outpost osmo bal: ", outpostOsmoBal) + println("outpost gamm token balance: ", outpostGammBalance) + println("proxy gamm token balance: ", proxyGammBalance) + println("osmo liquid pooler gamm token balance: ", osmoLiquidPoolerGammBalance) + + if proxyGammBalance != 0 && proxyAtomBal == 0 && proxyOsmoBal == 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + testCtx.SkipBlocks(2) + } + } + }) + + t.Run("osmo party claims", func(t *testing.T) { + // try to withdraw until lp tokens are gone from proxy + testCtx.SkipBlocks(5) + testCtx.HolderClaim(holderAddress, osmoPartyNeutronAddr, keyring.BackendTest) + + for { + testCtx.SkipBlocks(5) + + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + proxyGammBalance := testCtx.QueryOsmoDenomBalance("gamm/pool/1", proxyAddress) + proxyAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, proxyAddress) + proxyOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, proxyAddress) + holderOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + lperAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, liquidPoolerAddress) + lperOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, liquidPoolerAddress) + osmoPartyReceiverAddrOsmoBal := testCtx.QueryOsmoDenomBalance("uosmo", happyCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix)) + osmoPartyReceiverAddrAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, happyCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix)) + + println("holder osmo bal: ", holderOsmoBal) + println("holder atom bal: ", holderAtomBal) + + println("proxy atom bal: ", proxyAtomBal) + println("proxy osmo bal: ", proxyOsmoBal) + println("proxy gamm token balance: ", proxyGammBalance) + + println("liquid pooler osmo bal: ", lperOsmoBal) + println("liquid pooler atom bal: ", lperAtomBal) + + println("osmoPartyReceiverAddrOsmoBal", osmoPartyReceiverAddrOsmoBal) + println("osmoPartyReceiverAddrAtomBal", osmoPartyReceiverAddrAtomBal) + + if osmoPartyReceiverAddrOsmoBal != 0 && osmoPartyReceiverAddrAtomBal != 0 { + println("claiming party received the funds") + break + } + } + }) + + t.Run("tick until we are able to withdraw", func(t *testing.T) { + testCtx.SkipBlocks(10) + tickCount := 0 + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + testCtx.SkipBlocks(2) + tickCount = tickCount + 1 + if tickCount == 6 { + break + } + } + + testCtx.HolderClaim(holderAddress, hubPartyNeutronAddr, keyring.BackendTest) + testCtx.SkipBlocks(10) + + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + testCtx.SkipBlocks(5) + proxyGammBalance := testCtx.QueryOsmoDenomBalance("gamm/pool/1", proxyAddress) + proxyAtomBal := testCtx.QueryOsmoDenomBalance(osmosisAtomIbcDenom, proxyAddress) + proxyOsmoBal := testCtx.QueryOsmoDenomBalance(testCtx.Osmosis.Config().Denom, proxyAddress) + holderOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + lperAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, liquidPoolerAddress) + lperOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, liquidPoolerAddress) + + println("holder osmo bal: ", holderOsmoBal) + println("holder atom bal: ", holderAtomBal) + + println("proxy atom bal: ", proxyAtomBal) + println("proxy osmo bal: ", proxyOsmoBal) + println("proxy gamm token balance: ", proxyGammBalance) + + println("liquid pooler osmo bal: ", lperOsmoBal) + println("liquid pooler atom bal: ", lperAtomBal) + + hubPartyReceiverAddrAtomBal := testCtx.QueryHubDenomBalance("uatom", happyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix)) + hubPartyReceiverAddrOsmoBal := testCtx.QueryHubDenomBalance(hubOsmoIbcDenom, happyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix)) + + println("hubPartyReceiverAddrAtomBal", hubPartyReceiverAddrAtomBal) + println("hubPartyReceiverAddrOsmoBal", hubPartyReceiverAddrOsmoBal) + + if hubPartyReceiverAddrAtomBal != 0 && hubPartyReceiverAddrOsmoBal != 0 { + println("claiming party received the funds") + break + } + } + }) + }) + }) +} diff --git a/interchaintest/two-party-pol/two_party_pol_native_test.go b/interchaintest/two-party-pol/two_party_pol_native_test.go new file mode 100644 index 00000000..c02ebfdf --- /dev/null +++ b/interchaintest/two-party-pol/two_party_pol_native_test.go @@ -0,0 +1,1584 @@ +package covenant_two_party_pol + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + ibctest "github.com/strangelove-ventures/interchaintest/v4" + "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v4/ibc" + "github.com/strangelove-ventures/interchaintest/v4/relayer" + "github.com/strangelove-ventures/interchaintest/v4/testreporter" + "github.com/strangelove-ventures/interchaintest/v4/testutil" + "github.com/stretchr/testify/require" + utils "github.com/timewave-computer/covenants/interchaintest/utils" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +// PARTY_B +const neutronContributionAmount uint64 = 50_000_000_000 // in untrn +var hubNeutronIbcDenom string + +// sets up and tests a two party pol between hub and osmo facilitated by neutron +func TestTwoPartyNativePartyPol(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + ctx := context.Background() + + // Modify the the timeout_commit in the config.toml node files + // to reduce the block commit times. This speeds up the tests + // by about 35% + configFileOverrides := make(map[string]any) + configTomlOverrides := make(testutil.Toml) + consensus := make(testutil.Toml) + consensus["timeout_commit"] = "1s" + configTomlOverrides["consensus"] = consensus + configFileOverrides["config/config.toml"] = configTomlOverrides + + // Chain Factory + cf := ibctest.NewBuiltinChainFactory(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)), []*ibctest.ChainSpec{ + {Name: "gaia", Version: "v9.1.0", ChainConfig: ibc.ChainConfig{ + GasAdjustment: 1.3, + GasPrices: "0.0atom", + ModifyGenesis: utils.SetupGaiaGenesis(utils.GetDefaultInterchainGenesisMessages()), + ConfigFileOverrides: configFileOverrides, + }}, + { + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Name: "neutron", + ChainID: "neutron-2", + Images: []ibc.DockerImage{ + { + Repository: "ghcr.io/strangelove-ventures/heighliner/neutron", + Version: "v2.0.0", + UidGid: "1025:1025", + }, + }, + Bin: "neutrond", + Bech32Prefix: "neutron", + Denom: nativeNtrnDenom, + GasPrices: "0.0untrn,0.0uatom", + GasAdjustment: 1.3, + TrustingPeriod: "1197504s", + NoHostMount: false, + ModifyGenesis: utils.SetupNeutronGenesis( + "0.05", + []string{nativeNtrnDenom}, + []string{nativeAtomDenom}, + utils.GetDefaultNeutronInterchainGenesisMessages(), + ), + ConfigFileOverrides: configFileOverrides, + }, + }, + { + Name: "osmosis", + Version: "v14.0.0", + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Bin: "osmosisd", + Bech32Prefix: "osmo", + Denom: nativeOsmoDenom, + ModifyGenesis: utils.SetupOsmoGenesis( + append(utils.GetDefaultInterchainGenesisMessages(), "/ibc.applications.interchain_accounts.v1.InterchainAccount"), + ), + GasPrices: "0.0uosmo", + GasAdjustment: 1.3, + Images: []ibc.DockerImage{ + { + Repository: "ghcr.io/strangelove-ventures/heighliner/osmosis", + Version: "v14.0.0", + UidGid: "1025:1025", + }, + }, + TrustingPeriod: "336h", + NoHostMount: false, + ConfigFileOverrides: configFileOverrides, + }, + }, + }) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + + // We have three chains + atom, neutron, osmosis := chains[0], chains[1], chains[2] + cosmosAtom, cosmosNeutron, cosmosOsmosis := atom.(*cosmos.CosmosChain), neutron.(*cosmos.CosmosChain), osmosis.(*cosmos.CosmosChain) + + // Relayer Factory + client, network := ibctest.DockerSetup(t) + r := ibctest.NewBuiltinRelayerFactory( + ibc.CosmosRly, + zaptest.NewLogger(t, zaptest.Level(zap.InfoLevel)), + relayer.CustomDockerImage("ghcr.io/cosmos/relayer", "v2.4.0", "1000:1000"), + relayer.RelayerOptionExtraStartFlags{Flags: []string{"-p", "events", "-b", "100", "-d", "--log-format", "console"}}, + ).Build(t, client, network) + + // Prep Interchain + ic := ibctest.NewInterchain(). + AddChain(cosmosAtom). + AddChain(cosmosNeutron). + AddChain(cosmosOsmosis). + AddRelayer(r, "relayer"). + AddProviderConsumerLink(ibctest.ProviderConsumerLink{ + Provider: cosmosAtom, + Consumer: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronICSPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronIBCPath, + }) + + // Log location + f, err := ibctest.CreateLogFile(fmt.Sprintf("%d.json", time.Now().Unix())) + require.NoError(t, err) + // Reporter/logs + rep := testreporter.NewReporter(f) + eRep := rep.RelayerExecReporter(t) + + // Build interchain + require.NoError( + t, + ic.Build(ctx, eRep, ibctest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + BlockDatabaseFile: ibctest.DefaultBlockDatabaseFilepath(), + SkipPathCreation: true, + }), + "failed to build interchain") + + testCtx := &utils.TestContext{ + Neutron: cosmosNeutron, + Hub: cosmosAtom, + Osmosis: cosmosOsmosis, + OsmoClients: []*ibc.ClientOutput{}, + GaiaClients: []*ibc.ClientOutput{}, + NeutronClients: []*ibc.ClientOutput{}, + GaiaConnections: []*ibc.ConnectionOutput{}, + NeutronConnections: []*ibc.ConnectionOutput{}, + NeutronTransferChannelIds: make(map[string]string), + GaiaTransferChannelIds: make(map[string]string), + GaiaIcsChannelIds: make(map[string]string), + NeutronIcsChannelIds: make(map[string]string), + T: t, + Ctx: ctx, + } + + testCtx.SkipBlocks(5) + + t.Run("generate IBC paths", func(t *testing.T) { + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosNeutron.Config().ChainID, gaiaNeutronIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosAtom.Config().ChainID, gaiaNeutronICSPath) + }) + + t.Run("setup neutron-gaia ICS", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + neutronClients := testCtx.GetChainClients(cosmosNeutron.Config().Name) + atomClients := testCtx.GetChainClients(cosmosAtom.Config().Name) + + err = r.UpdatePath(ctx, eRep, gaiaNeutronICSPath, ibc.PathUpdateOptions{ + SrcClientID: &neutronClients[0].ClientID, + DstClientID: &atomClients[0].ClientID, + }) + require.NoError(t, err) + + atomNeutronICSConnectionId, neutronAtomICSConnectionId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + + utils.GenerateICSChannel(t, ctx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + + utils.CreateValidator(t, ctx, r, eRep, atom, neutron) + testCtx.SkipBlocks(2) + }) + + t.Run("setup IBC interchain clients, connections, and links", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + atomNeutronIBCConnId, neutronAtomIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosNeutron, gaiaNeutronIBCPath) + testCtx.SkipBlocks(10) + }) + + // Start the relayer and clean it up when the test ends. + err = r.StartRelayer(ctx, eRep, gaiaNeutronICSPath, gaiaNeutronIBCPath) + require.NoError(t, err, "failed to start relayer with given paths") + t.Cleanup(func() { + err = r.StopRelayer(ctx, eRep) + if err != nil { + t.Logf("failed to stop relayer: %s", err) + } + }) + testCtx.SkipBlocks(2) + + // Once the VSC packet has been relayed, x/bank transfers are + // enabled on Neutron and we can fund its account. + // The funds for this are sent from a "faucet" account created + // by interchaintest in the genesis file. + users := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), atom, neutron) + gaiaUser, neutronUser := users[0], users[1] + _, _ = gaiaUser, neutronUser + hubNeutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), neutron)[0] + + rqCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + + sideBasedRqCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + + happyCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + + sideBasedHappyCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + + rqCaseNeutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(neutronContributionAmount), neutron)[0] + + sideBasedRqCaseNeutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(neutronContributionAmount), neutron)[0] + + happyCaseNeutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(neutronContributionAmount), neutron)[0] + + sideBasedHappyCaseNeutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(neutronContributionAmount), neutron)[0] + + testCtx.SkipBlocks(5) + + t.Run("determine ibc channels", func(t *testing.T) { + neutronChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosNeutron.Config().ChainID) + gaiaChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosAtom.Config().ChainID) + + // Find all pairwise channels + utils.GetPairwiseTransferChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronIBCConnId, neutronAtomIBCConnId, cosmosAtom.Config().Name, neutron.Config().Name) + utils.GetPairwiseCCVChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronICSConnectionId, neutronAtomICSConnectionId, cosmosAtom.Config().Name, cosmosNeutron.Config().Name) + }) + + t.Run("determine ibc denoms", func(t *testing.T) { + // We can determine the ibc denoms of: + // 1. ATOM on Neutron + neutronAtomIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + nativeAtomDenom, + ) + // 2. neutron => hub + hubNeutronIbcDenom = testCtx.GetIbcDenom( + testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + cosmosNeutron.Config().Denom, + ) + println("hub neutron ibc denom: ", hubNeutronIbcDenom) + println("neutron atom ibc denom: ", neutronAtomIbcDenom) + println("atom denom: ", nativeAtomDenom) + println("neutron denom: ", cosmosNeutron.Config().Denom) + }) + + t.Run("two party pol covenant setup", func(t *testing.T) { + // Wasm code that we need to store on Neutron + const covenantContractPath = "wasms/valence_covenant_two_party_pol.wasm" + const clockContractPath = "wasms/valence_clock.wasm" + const interchainRouterContractPath = "wasms/valence_interchain_router.wasm" + const nativeRouterContractPath = "wasms/valence_native_router.wasm" + const ibcForwarderContractPath = "wasms/valence_ibc_forwarder.wasm" + const holderContractPath = "wasms/valence_two_party_pol_holder.wasm" + const liquidPoolerPath = "wasms/valence_astroport_liquid_pooler.wasm" + + // After storing on Neutron, we will receive a code id + // We parse all the subcontracts into uint64 + // The will be required when we instantiate the covenant. + var clockCodeId uint64 + var interchainRouterCodeId uint64 + var nativeRouterCodeId uint64 + var ibcForwarderCodeId uint64 + var holderCodeId uint64 + var lperCodeId uint64 + var covenantCodeId uint64 + var covenantRqCodeId uint64 + var covenantSideBasedRqCodeId uint64 + + t.Run("deploy covenant contracts", func(t *testing.T) { + // something was going wrong with instantiating the same code twice, + // hence this weird workaround + covenantCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + covenantRqCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + covenantSideBasedRqCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + + // store clock and get code id + clockCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, clockContractPath) + + // store routers and get code id + interchainRouterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, interchainRouterContractPath) + nativeRouterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, nativeRouterContractPath) + + // store forwarder and get code id + ibcForwarderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, ibcForwarderContractPath) + + // store lper, get code + lperCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, liquidPoolerPath) + + // store holder and get code id + holderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, holderContractPath) + + testCtx.SkipBlocks(5) + }) + + t.Run("deploy astroport contracts", func(t *testing.T) { + stablePairCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_pair_stable.wasm") + factoryCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_factory.wasm") + whitelistCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_whitelist.wasm") + tokenCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_token.wasm") + coinRegistryCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_native_coin_registry.wasm") + + t.Run("astroport token", func(t *testing.T) { + msg := NativeTokenInstantiateMsg{ + Name: "nativetoken", + Symbol: "ntk", + Decimals: 5, + InitialBalances: []Cw20Coin{}, + Mint: nil, + Marketing: nil, + } + str, _ := json.Marshal(msg) + + tokenAddress, err = cosmosNeutron.InstantiateContract( + ctx, neutronUser.KeyName, strconv.FormatUint(tokenCodeId, 10), string(str), true) + require.NoError(t, err, "Failed to instantiate nativetoken") + println("astroport token: ", tokenAddress) + }) + + t.Run("whitelist", func(t *testing.T) { + msg := WhitelistInstantiateMsg{ + Admins: []string{neutronUser.Bech32Address(neutron.Config().Bech32Prefix)}, + Mutable: false, + } + str, _ := json.Marshal(msg) + + whitelistAddress, err = cosmosNeutron.InstantiateContract( + ctx, neutronUser.KeyName, strconv.FormatUint(whitelistCodeId, 10), string(str), true) + require.NoError(t, err, "Failed to instantiate Whitelist") + println("astroport whitelist: ", whitelistAddress) + + }) + + t.Run("native coins registry", func(t *testing.T) { + msg := NativeCoinRegistryInstantiateMsg{ + Owner: neutronUser.Bech32Address(neutron.Config().Bech32Prefix), + } + str, _ := json.Marshal(msg) + + nativeCoinRegistryAddress, err := cosmosNeutron.InstantiateContract( + ctx, neutronUser.KeyName, strconv.FormatUint(coinRegistryCodeId, 10), string(str), true) + require.NoError(t, err, "Failed to instantiate NativeCoinRegistry") + coinRegistryAddress = nativeCoinRegistryAddress + println("astroport native coins registry: ", coinRegistryAddress) + }) + + t.Run("add coins to registry", func(t *testing.T) { + // Add ibc native tokens for uosmo and uatom to the native coin registry + // each of these tokens has a precision of 6 + addMessage := fmt.Sprintf( + `{"add":{"native_coins":[["%s",6],["%s",6]]}}`, + neutronAtomIbcDenom, + cosmosNeutron.Config().Denom) + _, err = cosmosNeutron.ExecuteContract(ctx, neutronUser.KeyName, coinRegistryAddress, addMessage) + require.NoError(t, err, err) + testCtx.SkipBlocks(2) + }) + + t.Run("factory", func(t *testing.T) { + factoryAddress = testCtx.InstantiateAstroportFactory( + stablePairCodeId, tokenCodeId, whitelistCodeId, factoryCodeId, coinRegistryAddress, neutronUser) + println("astroport factory: ", factoryAddress) + testCtx.SkipBlocks(2) + }) + t.Run("create pair on factory", func(t *testing.T) { + testCtx.CreateAstroportFactoryPair(3, cosmosNeutron.Config().Denom, neutronAtomIbcDenom, factoryAddress, neutronUser, keyring.BackendTest) + }) + }) + + t.Run("add liquidity to the atom-neutron stableswap pool", func(t *testing.T) { + liquidityTokenAddress, stableswapAddress = testCtx.QueryAstroLpTokenAndStableswapAddress( + factoryAddress, cosmosNeutron.Config().Denom, neutronAtomIbcDenom) + // set up the pool with 1:10 ratio of atom/osmo + _, err := atom.SendIBCTransfer(ctx, + testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + gaiaUser.KeyName, + ibc.WalletAmount{ + Address: neutronUser.Bech32Address(neutron.Config().Bech32Prefix), + Denom: cosmosAtom.Config().Denom, + Amount: int64(atomContributionAmount), + }, + ibc.TransferOptions{}) + require.NoError(t, err) + + testCtx.SkipBlocks(2) + + testCtx.ProvideAstroportLiquidity( + neutronAtomIbcDenom, cosmosNeutron.Config().Denom, atomContributionAmount, neutronContributionAmount, neutronUser, stableswapAddress) + + testCtx.SkipBlocks(2) + testCtx.QueryLpTokenBalance(liquidityTokenAddress, neutronUser.Bech32Address(neutron.Config().Bech32Prefix)) + }) + + t.Run("two party POL happy path", func(t *testing.T) { + var depositBlock Block + var lockupBlock Block + var hubReceiverAddr string + var neutronReceiverAddr string + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "10000", // sec + IbcTransferTimeout: "10000", // sec + } + + currentHeight := testCtx.GetNeutronHeight() + depositBlock = Block(currentHeight + 110) + lockupBlock = Block(currentHeight + 130) + + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + depositDeadline := Expiration{ + AtHeight: &depositBlock, + } + + atomCoin := Coin{ + Denom: cosmosAtom.Config().Denom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + + neutronCoin := Coin{ + Denom: cosmosNeutron.Config().Denom, + Amount: strconv.FormatUint(neutronContributionAmount, 10), + } + + hubReceiverAddr = happyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix) + neutronReceiverAddr = happyCaseNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix) + + println("hub receiver address: ", hubReceiverAddr) + + partyAConfig := InterchainCovenantParty{ + Addr: hubNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronAtomIbcDenom, + RemoteChainDenom: "uatom", + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyReceiverAddr: hubReceiverAddr, + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + Contribution: atomCoin, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + partyBConfig := NativeCovenantParty{ + Addr: neutronReceiverAddr, + NativeDenom: cosmosNeutron.Config().Denom, + PartyReceiverAddr: neutronReceiverAddr, + Contribution: neutronCoin, + } + codeIds := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + } + + ragequitTerms := RagequitTerms{ + Penalty: "0.1", + } + + ragequitConfig := RagequitConfig{ + Enabled: &ragequitTerms, + } + + poolAddress := stableswapAddress + pairType := PairType{ + Stable: struct{}{}, + } + + denomSplits := map[string]SplitConfig{ + neutronAtomIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "0.5", + neutronReceiverAddr: "0.5", + }, + }, + cosmosNeutron.Config().Denom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "0.5", + neutronReceiverAddr: "0.5", + }, + }, + } + + liquidPoolerConfig := LiquidPoolerConfig{ + Astroport: &AstroportLiquidPoolerConfig{ + PairType: pairType, + PoolAddress: poolAddress, + AssetADenom: neutronAtomIbcDenom, + AssetBDenom: cosmosNeutron.Config().Denom, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "100000", + AssetBLimit: "100000", + }, + }, + } + + fundingDuration := Duration{ + Time: new(uint64), + } + *fundingDuration.Time = 300 + + covenantMsg := CovenantInstantiateMsg{ + Label: "two-party-pol-covenant-happy", + Timeouts: timeouts, + ContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{ + Interchain: &partyAConfig, + }, + PartyBConfig: CovenantPartyConfig{ + Native: &partyBConfig, + }, + RagequitConfig: &ragequitConfig, + DepositDeadline: depositDeadline, + PartyAShare: "0.5", + PartyBShare: "0.5", + CovenantType: "share", + Splits: denomSplits, + FallbackSplit: nil, + LiquidPoolerConfig: liquidPoolerConfig, + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.1", + AcceptablePriceSpread: "0.09", + }, + } + + covenantAddress = testCtx.ManualInstantiate(covenantCodeId, covenantMsg, neutronUser, keyring.BackendTest) + + println("covenant address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + liquidPoolerAddress, + } + if partyAIbcForwarderAddress != "" { + addrs = append(addrs, partyAIbcForwarderAddress) + } + if partyBIbcForwarderAddress != "" { + addrs = append(addrs, partyBIbcForwarderAddress) + } + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + }) + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + println("forwarderAState: ", forwarderAState) + if forwarderAState == "ica_created" { + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyBDepositAddress}, cosmosNeutron, happyCaseNeutronAccount, int64(neutronContributionAmount)) + testCtx.FundChainAddrs([]string{partyADepositAddress}, cosmosAtom, happyCaseHubAccount, int64(atomContributionAmount)) + + testCtx.SkipBlocks(3) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + holderNeutronBal := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + println("holder ibc atom balance: ", holderAtomBal) + println("holder neutron balance: ", holderNeutronBal) + println("holder state: ", holderState) + + if holderAtomBal == atomContributionAmount && holderNeutronBal == neutronContributionAmount { + println("holder received atom & neutron") + break + } else if holderState == "active" { + println("holder: active") + break + } + } + }) + + t.Run("tick until holder sends funds to LiquidPooler and LPer receives LP tokens", func(t *testing.T) { + for { + if testCtx.QueryLpTokenBalance(liquidityTokenAddress, liquidPoolerAddress) == 0 { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("tick until holder expires", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + holderState := testCtx.QueryContractState(holderAddress) + println("holder state: ", holderState) + + if holderState == "expired" { + break + } + } + }) + + t.Run("party A claims and router receives the funds", func(t *testing.T) { + routerNeutronBalA := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyARouterAddress) + routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + println("routerAtomBalA: ", routerAtomBalA) + println("routerNeutronBalA: ", routerNeutronBalA) + routerNeutronBalB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyBRouterAddress) + routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + println("routerAtomBalB: ", routerAtomBalB) + println("routerNeutronBalB: ", routerNeutronBalB) + holderNtrnBal := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + println("holderNtrnBal: ", holderNtrnBal) + println("holderAtomBal: ", holderAtomBal) + + testCtx.SkipBlocks(10) + testCtx.HolderClaim(holderAddress, hubNeutronAccount, keyring.BackendTest) + testCtx.SkipBlocks(5) + + routerNeutronBalA = testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyARouterAddress) + routerAtomBalA = testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + println("routerAtomBalA: ", routerAtomBalA) + println("routerNeutronBalA: ", routerNeutronBalA) + routerNeutronBalB = testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyBRouterAddress) + routerAtomBalB = testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + println("routerAtomBalB: ", routerAtomBalB) + println("routerNeutronBalB: ", routerNeutronBalB) + holderNtrnBal = testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, holderAddress) + holderAtomBal = testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + println("holderNtrnBal: ", holderNtrnBal) + println("holderAtomBal: ", holderAtomBal) + + // for { + // routerNeutronBalA := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyARouterAddress) + // routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + // println("routerAtomBalA: ", routerAtomBalA) + // println("routerNeutronBalA: ", routerNeutronBalA) + // if routerAtomBalA != 0 && routerNeutronBalA != 0 { + // break + // } else { + // testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + // } + // } + }) + + t.Run("tick until party A claim is distributed", func(t *testing.T) { + println("hub receiver addr: ", hubReceiverAddr) + for { + routerNeutronBalA := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyARouterAddress) + routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + println("routerAtomBalA: ", routerAtomBalA) + println("routerNeutronBalA: ", routerNeutronBalA) + routerNeutronBalB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyBRouterAddress) + routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + println("routerAtomBalB: ", routerAtomBalB) + println("routerNeutronBalB: ", routerNeutronBalB) + holderNtrnBal := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + println("holderNtrnBal: ", holderNtrnBal) + println("holderAtomBal: ", holderAtomBal) + + // routerNeutronBalA := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyARouterAddress) + // routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + atomBalPartyA := testCtx.QueryHubDenomBalance(cosmosAtom.Config().Denom, hubReceiverAddr) + neutronBalPartyA := testCtx.QueryHubDenomBalance(hubNeutronIbcDenom, hubReceiverAddr) + + println("party A router atom bal: ", routerAtomBalA) + println("party A router neutron bal: ", routerNeutronBalA) + + atomBalPartyB := testCtx.QueryNeutronDenomBalance(cosmosAtom.Config().Denom, neutronReceiverAddr) + neutronBalPartyB := testCtx.QueryNeutronDenomBalance(hubNeutronIbcDenom, neutronReceiverAddr) + + println("party B router atom bal: ", atomBalPartyB) + println("party B router neutron bal: ", neutronBalPartyB) + + if atomBalPartyA != 0 && neutronBalPartyA != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("party B claims and router receives the funds", func(t *testing.T) { + routerNeutronBalB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyBRouterAddress) + routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + println("routerAtomBalB: ", routerAtomBalB) + println("routerNeutronBalB: ", routerNeutronBalB) + testCtx.SkipBlocks(5) + testCtx.HolderClaim(holderAddress, happyCaseNeutronAccount, keyring.BackendTest) + testCtx.SkipBlocks(5) + // for { + // routerNeutronBalB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyBRouterAddress) + // routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + // println("routerAtomBalB: ", routerAtomBalB) + // println("routerNeutronBalB: ", routerNeutronBalB) + // if routerAtomBalB != 0 && routerNeutronBalB != 0 { + // break + // } else { + // testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + // } + // } + }) + + t.Run("tick routers until both parties receive their funds", func(t *testing.T) { + for { + atomBalPartyA := testCtx.QueryHubDenomBalance(cosmosAtom.Config().Denom, hubReceiverAddr) + neutronBalPartyA := testCtx.QueryHubDenomBalance(hubNeutronIbcDenom, hubReceiverAddr) + neutronBalPartyB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, neutronReceiverAddr) + atomBalPartyB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, neutronReceiverAddr) + + println("party A neutron bal: ", neutronBalPartyA) + println("party A atom bal: ", atomBalPartyA) + println("party B neutron bal: ", neutronBalPartyB) + println("party B atom bal: ", atomBalPartyB) + + if atomBalPartyB != 0 && neutronBalPartyB != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + }) + + t.Run("two party share based POL ragequit path", func(t *testing.T) { + var hubReceiverAddr string + var neutronReceiverAddr string + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "100", // sec + IbcTransferTimeout: "100", // sec + } + + currentHeight := testCtx.GetNeutronHeight() + depositBlock := Block(currentHeight + 200) + lockupBlock := Block(currentHeight + 300) + + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + depositDeadline := Expiration{ + AtHeight: &depositBlock, + } + + atomCoin := Coin{ + Denom: cosmosAtom.Config().Denom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + + neutronCoin := Coin{ + Denom: cosmosNeutron.Config().Denom, + Amount: strconv.FormatUint(neutronContributionAmount, 10), + } + hubReceiverAddr = rqCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix) + neutronReceiverAddr = rqCaseNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix) + + partyAConfig := InterchainCovenantParty{ + RemoteChainDenom: "uatom", + PartyReceiverAddr: hubReceiverAddr, + Addr: hubNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + Contribution: atomCoin, + NativeDenom: neutronAtomIbcDenom, + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + partyBConfig := NativeCovenantParty{ + PartyReceiverAddr: neutronReceiverAddr, + Addr: neutronReceiverAddr, + Contribution: neutronCoin, + NativeDenom: cosmosNeutron.Config().Denom, + } + codeIds := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + } + + ragequitTerms := RagequitTerms{ + Penalty: "0.1", + } + + ragequitConfig := RagequitConfig{ + Enabled: &ragequitTerms, + } + + poolAddress := stableswapAddress + pairType := PairType{ + Stable: struct{}{}, + } + + liquidPoolerConfig := LiquidPoolerConfig{ + Astroport: &AstroportLiquidPoolerConfig{ + PairType: pairType, + PoolAddress: poolAddress, + AssetADenom: neutronAtomIbcDenom, + AssetBDenom: cosmosNeutron.Config().Denom, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "100000", + AssetBLimit: "100000", + }, + }, + } + + covenantMsg := CovenantInstantiateMsg{ + Label: "two-party-pol-covenant-ragequit", + Timeouts: timeouts, + ContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{Interchain: &partyAConfig}, + PartyBConfig: CovenantPartyConfig{Native: &partyBConfig}, + RagequitConfig: &ragequitConfig, + DepositDeadline: depositDeadline, + CovenantType: "share", + PartyAShare: "0.5", + PartyBShare: "0.5", + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.1", + AcceptablePriceSpread: "0.09", + }, + Splits: map[string]SplitConfig{ + neutronAtomIbcDenom: SplitConfig{Receivers: map[string]string{hubReceiverAddr: "0.5", neutronReceiverAddr: "0.5"}}, + cosmosNeutron.Config().Denom: SplitConfig{Receivers: map[string]string{hubReceiverAddr: "0.5", neutronReceiverAddr: "0.5"}}, + }, + FallbackSplit: nil, + EmergencyCommittee: neutronUser.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + LiquidPoolerConfig: liquidPoolerConfig, + } + + covenantAddress = testCtx.ManualInstantiate(covenantRqCodeId, covenantMsg, neutronUser, keyring.BackendTest) + println("covenant address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + liquidPoolerAddress, + } + if partyAIbcForwarderAddress != "" { + addrs = append(addrs, partyAIbcForwarderAddress) + } + if partyBIbcForwarderAddress != "" { + addrs = append(addrs, partyBIbcForwarderAddress) + } + println("funding addresses with 5000000000untrn") + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + }) + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + testCtx.SkipBlocks(5) + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + + if forwarderAState == "ica_created" { + testCtx.SkipBlocks(3) + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyBDepositAddress}, cosmosNeutron, rqCaseNeutronAccount, int64(neutronContributionAmount)) + testCtx.FundChainAddrs([]string{partyADepositAddress}, cosmosAtom, rqCaseHubAccount, int64(atomContributionAmount)) + + testCtx.SkipBlocks(3) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + holderNeutronBal := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + + println("holder atom bal: ", holderAtomBal) + println("holder neutron bal: ", holderNeutronBal) + println("holder state: ", holderState) + + if holderAtomBal == atomContributionAmount && holderNeutronBal == neutronContributionAmount { + println("holder received atom & neutron") + break + } else if holderState == "active" { + println("holder is active") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until holder sends funds to LPer and receives LP tokens in return", func(t *testing.T) { + for { + holderLpTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, liquidPoolerAddress) + + if holderLpTokenBal == 0 { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("party A ragequits", func(t *testing.T) { + testCtx.SkipBlocks(10) + testCtx.HolderRagequit(holderAddress, hubNeutronAccount, keyring.BackendTest) + testCtx.SkipBlocks(5) + for { + routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + routerNeutronBalB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyBRouterAddress) + + println("routerAtomBalA: ", routerAtomBalA) + println("routerNeutronBalB: ", routerNeutronBalB) + + if routerAtomBalA != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until party A ragequit is distributed", func(t *testing.T) { + for { + + neutronBalPartyA := testCtx.QueryHubDenomBalance(hubNeutronIbcDenom, hubReceiverAddr) + neutronBalPartyB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, neutronReceiverAddr) + atomBalPartyA := testCtx.QueryHubDenomBalance(cosmosAtom.Config().Denom, hubReceiverAddr) + atomBalPartyB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, neutronReceiverAddr) + + println("party A osmo bal: ", neutronBalPartyA) + println("party A atom bal: ", atomBalPartyA) + println("party B osmo bal: ", neutronBalPartyB) + println("party B atom bal: ", atomBalPartyB) + + if atomBalPartyA != 0 && neutronBalPartyA != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("party B claims and router receives the funds", func(t *testing.T) { + testCtx.SkipBlocks(5) + testCtx.HolderClaim(holderAddress, rqCaseNeutronAccount, keyring.BackendTest) + testCtx.SkipBlocks(5) + + for { + routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + routerNeutronBalB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyBRouterAddress) + + println("routerAtomBalB: ", routerAtomBalB) + println("routerNeutronBalB: ", routerNeutronBalB) + + if routerNeutronBalB != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick routers until both parties receive their funds", func(t *testing.T) { + for { + neutronBalPartyB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, neutronReceiverAddr) + atomBalPartyA := testCtx.QueryHubDenomBalance(cosmosAtom.Config().Denom, hubReceiverAddr) + atomBalPartyB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, neutronReceiverAddr) + + println("party A atom bal: ", atomBalPartyA) + println("party B neutron bal: ", neutronBalPartyB) + println("party B atom bal: ", atomBalPartyB) + + if atomBalPartyA != 0 && neutronBalPartyB != 0 && atomBalPartyB != 0 { + println("nice") + break + } + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + }) + }) + + t.Run("two party POL side-based ragequit path", func(t *testing.T) { + var hubReceiverAddr string + var neutronReceiverAddr string + + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "100", // sec + IbcTransferTimeout: "100", // sec + } + + currentHeight := testCtx.GetNeutronHeight() + depositBlock := Block(currentHeight + 200) + lockupBlock := Block(currentHeight + 300) + + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + depositDeadline := Expiration{ + AtHeight: &depositBlock, + } + + atomCoin := Coin{ + Denom: cosmosAtom.Config().Denom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + + neutronCoin := Coin{ + Denom: cosmosNeutron.Config().Denom, + Amount: strconv.FormatUint(neutronContributionAmount, 10), + } + hubReceiverAddr = sideBasedRqCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix) + neutronReceiverAddr = sideBasedRqCaseNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix) + partyAConfig := InterchainCovenantParty{ + RemoteChainDenom: "uatom", + PartyReceiverAddr: hubReceiverAddr, + Addr: hubNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + Contribution: atomCoin, + NativeDenom: neutronAtomIbcDenom, + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + partyBConfig := NativeCovenantParty{ + PartyReceiverAddr: neutronReceiverAddr, + Addr: neutronReceiverAddr, + Contribution: neutronCoin, + NativeDenom: cosmosNeutron.Config().Denom, + } + codeIds := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + } + + ragequitTerms := RagequitTerms{ + Penalty: "0.1", + } + + ragequitConfig := RagequitConfig{ + Enabled: &ragequitTerms, + } + + poolAddress := stableswapAddress + pairType := PairType{ + Stable: struct{}{}, + } + + liquidPoolerConfig := LiquidPoolerConfig{ + Astroport: &AstroportLiquidPoolerConfig{ + PairType: pairType, + PoolAddress: poolAddress, + AssetADenom: neutronAtomIbcDenom, + AssetBDenom: cosmosNeutron.Config().Denom, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "100000", + AssetBLimit: "100000", + }, + }, + } + + covenantMsg := CovenantInstantiateMsg{ + Label: "two-party-pol-covenant-side-ragequit", + Timeouts: timeouts, + ContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{Interchain: &partyAConfig}, + PartyBConfig: CovenantPartyConfig{Native: &partyBConfig}, + RagequitConfig: &ragequitConfig, + DepositDeadline: depositDeadline, + PartyAShare: "0.5", + PartyBShare: "0.5", + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.1", + AcceptablePriceSpread: "0.09", + }, + CovenantType: "side", + Splits: map[string]SplitConfig{ + neutronAtomIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "1.0", + neutronReceiverAddr: "0.0", + }, + }, + cosmosNeutron.Config().Denom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "0.0", + neutronReceiverAddr: "1.0", + }, + }, + }, + FallbackSplit: nil, + LiquidPoolerConfig: liquidPoolerConfig, + } + + covenantAddress = testCtx.ManualInstantiate(covenantSideBasedRqCodeId, covenantMsg, neutronUser, keyring.BackendTest) + println("covenant address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + liquidPoolerAddress, + } + if partyAIbcForwarderAddress != "" { + addrs = append(addrs, partyAIbcForwarderAddress) + } + if partyBIbcForwarderAddress != "" { + addrs = append(addrs, partyBIbcForwarderAddress) + } + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + + testCtx.SkipBlocks(2) + }) + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + + if forwarderAState == "ica_created" { + testCtx.SkipBlocks(5) + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyBDepositAddress}, cosmosNeutron, sideBasedRqCaseNeutronAccount, int64(neutronContributionAmount)) + testCtx.FundChainAddrs([]string{partyADepositAddress}, cosmosAtom, sideBasedRqCaseHubAccount, int64(atomContributionAmount)) + + testCtx.SkipBlocks(3) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + holderNeutronBal := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + + println("holder atom bal: ", holderAtomBal) + println("holder neutron bal: ", holderNeutronBal) + + if holderAtomBal == atomContributionAmount && holderNeutronBal == neutronContributionAmount { + println("holder received atom & neutron") + break + } else if holderState == "active" { + println("holder state: ", holderState) + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until holder sends the funds to LPer and receives LP tokens in return", func(t *testing.T) { + for { + holderLpTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, liquidPoolerAddress) + println("holder lp token balance: ", holderLpTokenBal) + + if holderLpTokenBal == 0 { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("party A ragequits", func(t *testing.T) { + testCtx.SkipBlocks(10) + testCtx.HolderRagequit(holderAddress, hubNeutronAccount, keyring.BackendTest) + testCtx.SkipBlocks(5) + for { + routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + routerNeutronBalB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyBRouterAddress) + routerNeutronBalA := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyARouterAddress) + + println("routerAtomBalA: ", routerAtomBalA) + println("routerAtomBalB: ", routerAtomBalB) + println("routerNeutronBalB: ", routerNeutronBalB) + println("routerNeutronBalA: ", routerNeutronBalA) + + if routerAtomBalA != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick routers until both parties receive their funds", func(t *testing.T) { + for { + routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + routerNeutronBalB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyBRouterAddress) + routerNeutronBalA := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyARouterAddress) + neutronBalPartyB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, neutronReceiverAddr) + neutronBalPartyA := testCtx.QueryHubDenomBalance(hubNeutronIbcDenom, hubReceiverAddr) + atomBalPartyA := testCtx.QueryHubDenomBalance(cosmosAtom.Config().Denom, hubReceiverAddr) + atomBalPartyB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, neutronReceiverAddr) + + println("routerAtomBalA: ", routerAtomBalA) + println("routerAtomBalB: ", routerAtomBalB) + println("routerNeutronBalB: ", routerNeutronBalB) + println("routerNeutronBalA: ", routerNeutronBalA) + println("party A atom bal: ", atomBalPartyA) + println("party A neutron bal: ", neutronBalPartyA) + println("party B neutron bal: ", neutronBalPartyB) + println("party B atom bal: ", atomBalPartyB) + + println("\n") + + if atomBalPartyA != 0 && neutronBalPartyB != 0 && atomBalPartyB != 0 { + println("nice") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + }) + + t.Run("two party POL side-based happy path", func(t *testing.T) { + + var hubReceiverAddr string + var neutronReceiverAddr string + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "100", // sec + IbcTransferTimeout: "100", // sec + } + + currentHeight := testCtx.GetNeutronHeight() + depositBlock := Block(currentHeight + 180) + lockupBlock := Block(currentHeight + 200) + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + depositDeadline := Expiration{ + AtHeight: &depositBlock, + } + + atomCoin := Coin{ + Denom: cosmosAtom.Config().Denom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + + neutronCoin := Coin{ + Denom: cosmosNeutron.Config().Denom, + Amount: strconv.FormatUint(neutronContributionAmount, 10), + } + hubReceiverAddr = sideBasedHappyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix) + neutronReceiverAddr = sideBasedHappyCaseNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix) + partyAConfig := InterchainCovenantParty{ + RemoteChainDenom: "uatom", + PartyReceiverAddr: hubReceiverAddr, + Addr: hubNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + Contribution: atomCoin, + NativeDenom: neutronAtomIbcDenom, + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + partyBConfig := NativeCovenantParty{ + PartyReceiverAddr: neutronReceiverAddr, + Addr: neutronReceiverAddr, + Contribution: neutronCoin, + NativeDenom: cosmosNeutron.Config().Denom, + } + codeIds := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + } + + ragequitTerms := RagequitTerms{ + Penalty: "0.1", + } + + ragequitConfig := RagequitConfig{ + Enabled: &ragequitTerms, + } + + poolAddress := stableswapAddress + pairType := PairType{ + Stable: struct{}{}, + } + + liquidPoolerConfig := LiquidPoolerConfig{ + Astroport: &AstroportLiquidPoolerConfig{ + PairType: pairType, + PoolAddress: poolAddress, + AssetADenom: neutronAtomIbcDenom, + AssetBDenom: cosmosNeutron.Config().Denom, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "100000", + AssetBLimit: "100000", + }, + }, + } + + covenantMsg := CovenantInstantiateMsg{ + Label: "two-party-pol-covenant-side-happy", + Timeouts: timeouts, + ContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{Interchain: &partyAConfig}, + PartyBConfig: CovenantPartyConfig{Native: &partyBConfig}, + RagequitConfig: &ragequitConfig, + DepositDeadline: depositDeadline, + PartyAShare: "0.5", + PartyBShare: "0.5", + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.1", + AcceptablePriceSpread: "0.09", + }, + CovenantType: "side", + Splits: map[string]SplitConfig{ + neutronAtomIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "1.0", + neutronReceiverAddr: "0.0", + }, + }, + cosmosNeutron.Config().Denom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "0.0", + neutronReceiverAddr: "1.0", + }, + }, + }, + FallbackSplit: nil, + LiquidPoolerConfig: liquidPoolerConfig, + } + + covenantAddress = testCtx.ManualInstantiate(covenantSideBasedRqCodeId, covenantMsg, neutronUser, keyring.BackendTest) + println("covenant address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + liquidPoolerAddress, + } + if partyAIbcForwarderAddress != "" { + addrs = append(addrs, partyAIbcForwarderAddress) + } + if partyBIbcForwarderAddress != "" { + addrs = append(addrs, partyBIbcForwarderAddress) + } + + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + + testCtx.SkipBlocks(2) + }) + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + + if forwarderAState == "ica_created" { + testCtx.SkipBlocks(5) + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyBDepositAddress}, cosmosNeutron, sideBasedHappyCaseNeutronAccount, int64(neutronContributionAmount)) + testCtx.FundChainAddrs([]string{partyADepositAddress}, cosmosAtom, sideBasedHappyCaseHubAccount, int64(atomContributionAmount)) + + testCtx.SkipBlocks(3) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + holderNeutronBal := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + + println("holder atom bal: ", holderAtomBal) + println("holder neutron bal: ", holderNeutronBal) + println("holder state: ", holderState) + + if holderAtomBal == atomContributionAmount && holderNeutronBal == neutronContributionAmount { + println("holder/liquidpooler received atom & neutron") + break + } else if holderState == "active" { + println("holderState: ", holderState) + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until holder sends the funds to LPer and receives LP tokens in return", func(t *testing.T) { + for { + holderLpTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, liquidPoolerAddress) + println("holder lp token balance: ", holderLpTokenBal) + + if holderLpTokenBal == 0 { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("lockup expires", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + holderState := testCtx.QueryContractState(holderAddress) + println("holder state: ", holderState) + if holderState == "expired" { + break + } + } + }) + + t.Run("party A claims", func(t *testing.T) { + testCtx.SkipBlocks(5) + testCtx.HolderClaim(holderAddress, sideBasedHappyCaseNeutronAccount, keyring.BackendTest) + testCtx.SkipBlocks(5) + + for { + routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + routerNeutronBalB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyBRouterAddress) + routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + routerNeutronBalA := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, partyARouterAddress) + + println("routerAtomBalB: ", routerAtomBalB) + println("routerNeutronBalB: ", routerNeutronBalB) + println("routerAtomBalA: ", routerAtomBalA) + println("routerNeutronBalA: ", routerNeutronBalA) + + if routerNeutronBalB != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + + }) + + t.Run("tick routers until both parties receive their funds", func(t *testing.T) { + for { + neutronBalPartyB := testCtx.QueryNeutronDenomBalance(cosmosNeutron.Config().Denom, neutronReceiverAddr) + atomBalPartyA := testCtx.QueryHubDenomBalance(cosmosAtom.Config().Denom, hubReceiverAddr) + + println("party A atom bal: ", atomBalPartyA) + println("party B neutron bal: ", neutronBalPartyB) + + if atomBalPartyA != 0 && neutronBalPartyB != 0 { + println("nice") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + }) + }) +} diff --git a/interchaintest/two-party-pol/two_party_pol_test.go b/interchaintest/two-party-pol/two_party_pol_test.go new file mode 100644 index 00000000..c24aae55 --- /dev/null +++ b/interchaintest/two-party-pol/two_party_pol_test.go @@ -0,0 +1,1624 @@ +package covenant_two_party_pol + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + ibctest "github.com/strangelove-ventures/interchaintest/v4" + "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v4/ibc" + "github.com/strangelove-ventures/interchaintest/v4/relayer" + "github.com/strangelove-ventures/interchaintest/v4/testreporter" + "github.com/strangelove-ventures/interchaintest/v4/testutil" + "github.com/stretchr/testify/require" + utils "github.com/timewave-computer/covenants/interchaintest/utils" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +const gaiaNeutronICSPath = "gn-ics-path" +const gaiaNeutronIBCPath = "gn-ibc-path" +const gaiaOsmosisIBCPath = "go-ibc-path" +const neutronOsmosisIBCPath = "no-ibc-path" +const nativeAtomDenom = "uatom" +const nativeOsmoDenom = "uosmo" +const nativeNtrnDenom = "untrn" + +var covenantAddress string +var clockAddress string +var partyARouterAddress, partyBRouterAddress string +var liquidPoolerAddress string +var partyAIbcForwarderAddress, partyBIbcForwarderAddress string +var partyADepositAddress, partyBDepositAddress string +var holderAddress string +var neutronAtomIbcDenom, neutronOsmoIbcDenom, osmoNeutronAtomIbcDenom, gaiaNeutronOsmoIbcDenom string +var osmosisAtomIbcDenom string +var hubOsmoIbcDenom string +var atomNeutronICSConnectionId, neutronAtomICSConnectionId string +var neutronOsmosisIBCConnId, osmosisNeutronIBCConnId string +var atomNeutronIBCConnId, neutronAtomIBCConnId string +var gaiaOsmosisIBCConnId, osmosisGaiaIBCConnId string +var tokenAddress string +var whitelistAddress string +var factoryAddress string +var coinRegistryAddress string +var stableswapAddress string +var liquidityTokenAddress string + +// PARTY_A +const atomContributionAmount uint64 = 500_000_000 // in uatom + +// PARTY_B +const osmoContributionAmount uint64 = 5_000_000_000 // in uosmo + +// sets up and tests a two party pol between hub and osmo facilitated by neutron +func TestTwoPartyPol(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + ctx := context.Background() + + // Modify the the timeout_commit in the config.toml node files + // to reduce the block commit times. This speeds up the tests + // by about 35% + configFileOverrides := make(map[string]any) + configTomlOverrides := make(testutil.Toml) + consensus := make(testutil.Toml) + consensus["timeout_commit"] = "1s" + configTomlOverrides["consensus"] = consensus + configFileOverrides["config/config.toml"] = configTomlOverrides + + // Chain Factory + cf := ibctest.NewBuiltinChainFactory(zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)), []*ibctest.ChainSpec{ + {Name: "gaia", Version: "v9.1.0", ChainConfig: ibc.ChainConfig{ + GasAdjustment: 1.3, + GasPrices: "0.0atom", + ModifyGenesis: utils.SetupGaiaGenesis(utils.GetDefaultInterchainGenesisMessages()), + ConfigFileOverrides: configFileOverrides, + }}, + { + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Name: "neutron", + ChainID: "neutron-2", + Images: []ibc.DockerImage{ + { + Repository: "ghcr.io/strangelove-ventures/heighliner/neutron", + Version: "v2.0.0", + UidGid: "1025:1025", + }, + }, + Bin: "neutrond", + Bech32Prefix: "neutron", + Denom: nativeNtrnDenom, + GasPrices: "0.0untrn,0.0uatom", + GasAdjustment: 1.3, + TrustingPeriod: "1197504s", + NoHostMount: false, + ModifyGenesis: utils.SetupNeutronGenesis( + "0.05", + []string{nativeNtrnDenom}, + []string{nativeAtomDenom}, + utils.GetDefaultNeutronInterchainGenesisMessages(), + ), + ConfigFileOverrides: configFileOverrides, + }, + }, + { + Name: "osmosis", + Version: "v14.0.0", + ChainConfig: ibc.ChainConfig{ + Type: "cosmos", + Bin: "osmosisd", + Bech32Prefix: "osmo", + Denom: nativeOsmoDenom, + ModifyGenesis: utils.SetupOsmoGenesis( + append(utils.GetDefaultInterchainGenesisMessages(), "/ibc.applications.interchain_accounts.v1.InterchainAccount"), + ), + GasPrices: "0.0uosmo", + GasAdjustment: 1.3, + Images: []ibc.DockerImage{ + { + Repository: "ghcr.io/strangelove-ventures/heighliner/osmosis", + Version: "v14.0.0", + UidGid: "1025:1025", + }, + }, + TrustingPeriod: "336h", + NoHostMount: false, + ConfigFileOverrides: configFileOverrides, + }, + }, + }) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + + // We have three chains + atom, neutron, osmosis := chains[0], chains[1], chains[2] + cosmosAtom, cosmosNeutron, cosmosOsmosis := atom.(*cosmos.CosmosChain), neutron.(*cosmos.CosmosChain), osmosis.(*cosmos.CosmosChain) + + // Relayer Factory + client, network := ibctest.DockerSetup(t) + r := ibctest.NewBuiltinRelayerFactory( + ibc.CosmosRly, + zaptest.NewLogger(t, zaptest.Level(zap.InfoLevel)), + relayer.CustomDockerImage("ghcr.io/cosmos/relayer", "v2.4.0", "1000:1000"), + relayer.RelayerOptionExtraStartFlags{Flags: []string{"-p", "events", "-b", "100", "-d", "--log-format", "console"}}, + ).Build(t, client, network) + + // Prep Interchain + ic := ibctest.NewInterchain(). + AddChain(cosmosAtom). + AddChain(cosmosNeutron). + AddChain(cosmosOsmosis). + AddRelayer(r, "relayer"). + AddProviderConsumerLink(ibctest.ProviderConsumerLink{ + Provider: cosmosAtom, + Consumer: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronICSPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosNeutron, + Relayer: r, + Path: gaiaNeutronIBCPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosNeutron, + Chain2: cosmosOsmosis, + Relayer: r, + Path: neutronOsmosisIBCPath, + }). + AddLink(ibctest.InterchainLink{ + Chain1: cosmosAtom, + Chain2: cosmosOsmosis, + Relayer: r, + Path: gaiaOsmosisIBCPath, + }) + + // Log location + f, err := ibctest.CreateLogFile(fmt.Sprintf("%d.json", time.Now().Unix())) + require.NoError(t, err) + // Reporter/logs + rep := testreporter.NewReporter(f) + eRep := rep.RelayerExecReporter(t) + + // Build interchain + require.NoError( + t, + ic.Build(ctx, eRep, ibctest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + BlockDatabaseFile: ibctest.DefaultBlockDatabaseFilepath(), + SkipPathCreation: true, + }), + "failed to build interchain") + + testCtx := &utils.TestContext{ + Neutron: cosmosNeutron, + Hub: cosmosAtom, + Osmosis: cosmosOsmosis, + OsmoClients: []*ibc.ClientOutput{}, + GaiaClients: []*ibc.ClientOutput{}, + NeutronClients: []*ibc.ClientOutput{}, + OsmoConnections: []*ibc.ConnectionOutput{}, + GaiaConnections: []*ibc.ConnectionOutput{}, + NeutronConnections: []*ibc.ConnectionOutput{}, + NeutronTransferChannelIds: make(map[string]string), + GaiaTransferChannelIds: make(map[string]string), + OsmoTransferChannelIds: make(map[string]string), + GaiaIcsChannelIds: make(map[string]string), + NeutronIcsChannelIds: make(map[string]string), + T: t, + Ctx: ctx, + } + + testCtx.SkipBlocks(5) + + t.Run("generate IBC paths", func(t *testing.T) { + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosNeutron.Config().ChainID, gaiaNeutronIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosAtom.Config().ChainID, cosmosOsmosis.Config().ChainID, gaiaOsmosisIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosOsmosis.Config().ChainID, neutronOsmosisIBCPath) + utils.GeneratePath(t, ctx, r, eRep, cosmosNeutron.Config().ChainID, cosmosAtom.Config().ChainID, gaiaNeutronICSPath) + }) + + t.Run("setup neutron-gaia ICS", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + neutronClients := testCtx.GetChainClients(cosmosNeutron.Config().Name) + atomClients := testCtx.GetChainClients(cosmosAtom.Config().Name) + + err = r.UpdatePath(ctx, eRep, gaiaNeutronICSPath, ibc.PathUpdateOptions{ + SrcClientID: &neutronClients[0].ClientID, + DstClientID: &atomClients[0].ClientID, + }) + require.NoError(t, err) + + atomNeutronICSConnectionId, neutronAtomICSConnectionId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + + utils.GenerateICSChannel(t, ctx, r, eRep, gaiaNeutronICSPath, cosmosAtom, cosmosNeutron) + + utils.CreateValidator(t, ctx, r, eRep, atom, neutron) + testCtx.SkipBlocks(2) + }) + + t.Run("setup IBC interchain clients, connections, and links", func(t *testing.T) { + utils.GenerateClient(t, ctx, testCtx, r, eRep, neutronOsmosisIBCPath, cosmosNeutron, cosmosOsmosis) + neutronOsmosisIBCConnId, osmosisNeutronIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, neutronOsmosisIBCPath, cosmosNeutron, cosmosOsmosis) + utils.LinkPath(t, ctx, r, eRep, cosmosNeutron, cosmosOsmosis, neutronOsmosisIBCPath) + + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaOsmosisIBCPath, cosmosAtom, cosmosOsmosis) + gaiaOsmosisIBCConnId, osmosisGaiaIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaOsmosisIBCPath, cosmosAtom, cosmosOsmosis) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosOsmosis, gaiaOsmosisIBCPath) + + utils.GenerateClient(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + atomNeutronIBCConnId, neutronAtomIBCConnId = utils.GenerateConnections(t, ctx, testCtx, r, eRep, gaiaNeutronIBCPath, cosmosAtom, cosmosNeutron) + utils.LinkPath(t, ctx, r, eRep, cosmosAtom, cosmosNeutron, gaiaNeutronIBCPath) + }) + + // Start the relayer and clean it up when the test ends. + err = r.StartRelayer(ctx, eRep, gaiaNeutronICSPath, gaiaNeutronIBCPath, gaiaOsmosisIBCPath, neutronOsmosisIBCPath) + require.NoError(t, err, "failed to start relayer with given paths") + t.Cleanup(func() { + err = r.StopRelayer(ctx, eRep) + if err != nil { + t.Logf("failed to stop relayer: %s", err) + } + }) + testCtx.SkipBlocks(2) + + // Once the VSC packet has been relayed, x/bank transfers are + // enabled on Neutron and we can fund its account. + // The funds for this are sent from a "faucet" account created + // by interchaintest in the genesis file. + users := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), atom, neutron, osmosis) + gaiaUser, neutronUser, osmoUser := users[0], users[1], users[2] + _, _, _ = gaiaUser, neutronUser, osmoUser + hubNeutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), neutron)[0] + osmoNeutronAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(500_000_000_000), neutron)[0] + + rqCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + rqCaseOsmoAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(osmoContributionAmount), osmosis)[0] + + sideBasedRqCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + sideBasedRqCaseOsmoAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(osmoContributionAmount), osmosis)[0] + + happyCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + happyCaseOsmoAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(osmoContributionAmount), osmosis)[0] + + sideBasedHappyCaseHubAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(atomContributionAmount), atom)[0] + sideBasedHappyCaseOsmoAccount := ibctest.GetAndFundTestUsers(t, ctx, "default", int64(osmoContributionAmount), osmosis)[0] + + testCtx.SkipBlocks(5) + + t.Run("determine ibc channels", func(t *testing.T) { + neutronChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosNeutron.Config().ChainID) + gaiaChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosAtom.Config().ChainID) + osmoChannelInfo, _ := r.GetChannels(ctx, eRep, cosmosOsmosis.Config().ChainID) + + // Find all pairwise channels + utils.GetPairwiseTransferChannelIds(testCtx, osmoChannelInfo, neutronChannelInfo, osmosisNeutronIBCConnId, neutronOsmosisIBCConnId, osmosis.Config().Name, neutron.Config().Name) + utils.GetPairwiseTransferChannelIds(testCtx, osmoChannelInfo, gaiaChannelInfo, osmosisGaiaIBCConnId, gaiaOsmosisIBCConnId, osmosis.Config().Name, cosmosAtom.Config().Name) + utils.GetPairwiseTransferChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronIBCConnId, neutronAtomIBCConnId, cosmosAtom.Config().Name, neutron.Config().Name) + utils.GetPairwiseCCVChannelIds(testCtx, gaiaChannelInfo, neutronChannelInfo, atomNeutronICSConnectionId, neutronAtomICSConnectionId, cosmosAtom.Config().Name, cosmosNeutron.Config().Name) + }) + + t.Run("determine ibc denoms", func(t *testing.T) { + // We can determine the ibc denoms of: + // 1. ATOM on Neutron + neutronAtomIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + nativeAtomDenom, + ) + // 2. Osmo on neutron + neutronOsmoIbcDenom = testCtx.GetIbcDenom( + testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + nativeOsmoDenom, + ) + // 3. hub atom => neutron => osmosis + osmoNeutronAtomIbcDenom = testCtx.GetMultihopIbcDenom( + []string{ + testCtx.OsmoTransferChannelIds[cosmosNeutron.Config().Name], + testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + }, + nativeAtomDenom, + ) + // 4. osmosis osmo => neutron => hub + gaiaNeutronOsmoIbcDenom = testCtx.GetMultihopIbcDenom( + []string{ + testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + }, + nativeOsmoDenom, + ) + }) + + t.Run("two party pol covenant setup", func(t *testing.T) { + // Wasm code that we need to store on Neutron + const covenantContractPath = "wasms/valence_covenant_two_party_pol.wasm" + const clockContractPath = "wasms/valence_clock.wasm" + const interchainRouterContractPath = "wasms/valence_interchain_router.wasm" + const nativeRouterContractPath = "wasms/valence_native_router.wasm" + const ibcForwarderContractPath = "wasms/valence_ibc_forwarder.wasm" + const holderContractPath = "wasms/valence_two_party_pol_holder.wasm" + const liquidPoolerPath = "wasms/valence_astroport_liquid_pooler.wasm" + + // After storing on Neutron, we will receive a code id + // We parse all the subcontracts into uint64 + // The will be required when we instantiate the covenant. + var clockCodeId uint64 + var interchainRouterCodeId uint64 + var nativeRouterCodeId uint64 + var ibcForwarderCodeId uint64 + var holderCodeId uint64 + var lperCodeId uint64 + var covenantCodeId uint64 + var covenantRqCodeId uint64 + var covenantSideBasedRqCodeId uint64 + + t.Run("deploy covenant contracts", func(t *testing.T) { + // something was going wrong with instantiating the same code twice, + // hence this weird workaround + covenantCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + covenantRqCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + covenantSideBasedRqCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, covenantContractPath) + + // store clock and get code id + clockCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, clockContractPath) + + // store routers and get code id + interchainRouterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, interchainRouterContractPath) + nativeRouterCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, nativeRouterContractPath) + + // store forwarder and get code id + ibcForwarderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, ibcForwarderContractPath) + + // store lper, get code + lperCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, liquidPoolerPath) + + // store holder and get code id + holderCodeId = testCtx.StoreContract(cosmosNeutron, neutronUser, holderContractPath) + + testCtx.SkipBlocks(5) + }) + + t.Run("deploy astroport contracts", func(t *testing.T) { + stablePairCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_pair_stable.wasm") + factoryCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_factory.wasm") + whitelistCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_whitelist.wasm") + tokenCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_token.wasm") + coinRegistryCodeId := testCtx.StoreContract(cosmosNeutron, neutronUser, "wasms/astroport_native_coin_registry.wasm") + + t.Run("astroport token", func(t *testing.T) { + msg := NativeTokenInstantiateMsg{ + Name: "nativetoken", + Symbol: "ntk", + Decimals: 5, + InitialBalances: []Cw20Coin{}, + Mint: nil, + Marketing: nil, + } + str, _ := json.Marshal(msg) + + tokenAddress, err = cosmosNeutron.InstantiateContract( + ctx, neutronUser.KeyName, strconv.FormatUint(tokenCodeId, 10), string(str), true) + require.NoError(t, err, "Failed to instantiate nativetoken") + println("astroport token: ", tokenAddress) + }) + + t.Run("whitelist", func(t *testing.T) { + msg := WhitelistInstantiateMsg{ + Admins: []string{neutronUser.Bech32Address(neutron.Config().Bech32Prefix)}, + Mutable: false, + } + str, _ := json.Marshal(msg) + + whitelistAddress, err = cosmosNeutron.InstantiateContract( + ctx, neutronUser.KeyName, strconv.FormatUint(whitelistCodeId, 10), string(str), true) + require.NoError(t, err, "Failed to instantiate Whitelist") + println("astroport whitelist: ", whitelistAddress) + + }) + + t.Run("native coins registry", func(t *testing.T) { + msg := NativeCoinRegistryInstantiateMsg{ + Owner: neutronUser.Bech32Address(neutron.Config().Bech32Prefix), + } + str, _ := json.Marshal(msg) + + nativeCoinRegistryAddress, err := cosmosNeutron.InstantiateContract( + ctx, neutronUser.KeyName, strconv.FormatUint(coinRegistryCodeId, 10), string(str), true) + require.NoError(t, err, "Failed to instantiate NativeCoinRegistry") + coinRegistryAddress = nativeCoinRegistryAddress + println("astroport native coins registry: ", coinRegistryAddress) + }) + + t.Run("add coins to registry", func(t *testing.T) { + // Add ibc native tokens for uosmo and uatom to the native coin registry + // each of these tokens has a precision of 6 + addMessage := fmt.Sprintf( + `{"add":{"native_coins":[["%s",6],["%s",6]]}}`, + neutronAtomIbcDenom, + neutronOsmoIbcDenom) + _, err = cosmosNeutron.ExecuteContract(ctx, neutronUser.KeyName, coinRegistryAddress, addMessage) + require.NoError(t, err, err) + testCtx.SkipBlocks(2) + }) + + t.Run("factory", func(t *testing.T) { + factoryAddress = testCtx.InstantiateAstroportFactory( + stablePairCodeId, tokenCodeId, whitelistCodeId, factoryCodeId, coinRegistryAddress, neutronUser) + println("astroport factory: ", factoryAddress) + testCtx.SkipBlocks(2) + }) + + t.Run("create pair on factory", func(t *testing.T) { + testCtx.CreateAstroportFactoryPair(3, neutronOsmoIbcDenom, neutronAtomIbcDenom, factoryAddress, neutronUser, keyring.BackendTest) + }) + }) + + t.Run("add liquidity to the atom-osmo stableswap pool", func(t *testing.T) { + liquidityTokenAddress, stableswapAddress = testCtx.QueryAstroLpTokenAndStableswapAddress( + factoryAddress, neutronOsmoIbcDenom, neutronAtomIbcDenom) + // set up the pool with 1:10 ratio of atom/osmo + _, err := atom.SendIBCTransfer(ctx, + testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + gaiaUser.KeyName, + ibc.WalletAmount{ + Address: neutronUser.Bech32Address(neutron.Config().Bech32Prefix), + Denom: cosmosAtom.Config().Denom, + Amount: int64(atomContributionAmount), + }, + ibc.TransferOptions{}) + require.NoError(t, err) + + _, err = osmosis.SendIBCTransfer(ctx, + testCtx.OsmoTransferChannelIds[cosmosNeutron.Config().Name], + osmoUser.KeyName, + ibc.WalletAmount{ + Address: neutronUser.Bech32Address(neutron.Config().Bech32Prefix), + Denom: osmosis.Config().Denom, + Amount: int64(osmoContributionAmount), + }, + ibc.TransferOptions{}) + require.NoError(t, err) + + testCtx.SkipBlocks(2) + + testCtx.ProvideAstroportLiquidity( + neutronAtomIbcDenom, neutronOsmoIbcDenom, atomContributionAmount, osmoContributionAmount, neutronUser, stableswapAddress) + + testCtx.SkipBlocks(2) + neutronUserLPTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, neutronUser.Bech32Address(neutron.Config().Bech32Prefix)) + println("neutronUser lp token bal: ", neutronUserLPTokenBal) + }) + + t.Run("two party POL happy path", func(t *testing.T) { + var depositBlock Block + var lockupBlock Block + + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "100", // sec + IbcTransferTimeout: "100", // sec + } + + currentHeight := testCtx.GetNeutronHeight() + depositBlock = Block(currentHeight + 180) + lockupBlock = Block(currentHeight + 200) + + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + depositDeadline := Expiration{ + AtHeight: &depositBlock, + } + + atomCoin := Coin{ + Denom: cosmosAtom.Config().Denom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + + osmoCoin := Coin{ + Denom: cosmosOsmosis.Config().Denom, + Amount: strconv.FormatUint(osmoContributionAmount, 10), + } + + hubReceiverAddr := happyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix) + osmoReceiverAddr := happyCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix) + + partyAConfig := InterchainCovenantParty{ + Addr: hubNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronAtomIbcDenom, + RemoteChainDenom: "uatom", + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyReceiverAddr: hubReceiverAddr, + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + Contribution: atomCoin, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + partyBConfig := InterchainCovenantParty{ + Addr: osmoNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + NativeDenom: neutronOsmoIbcDenom, + RemoteChainDenom: "uosmo", + PartyToHostChainChannelId: testCtx.OsmoTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + PartyReceiverAddr: osmoReceiverAddr, + PartyChainConnectionId: neutronOsmosisIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + Contribution: osmoCoin, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + codeIds := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + } + + ragequitTerms := RagequitTerms{ + Penalty: "0.1", + } + + ragequitConfig := RagequitConfig{ + Enabled: &ragequitTerms, + } + + poolAddress := stableswapAddress + pairType := PairType{ + Stable: struct{}{}, + } + + denomSplits := map[string]SplitConfig{ + neutronAtomIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "0.5", + osmoReceiverAddr: "0.5", + }, + }, + neutronOsmoIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "0.5", + osmoReceiverAddr: "0.5", + }, + }, + } + + liquidPoolerConfig := LiquidPoolerConfig{ + Astroport: &AstroportLiquidPoolerConfig{ + PairType: pairType, + PoolAddress: poolAddress, + AssetADenom: neutronAtomIbcDenom, + AssetBDenom: neutronOsmoIbcDenom, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "100000", + AssetBLimit: "1000000", + }, + }, + } + + covenantMsg := CovenantInstantiateMsg{ + Label: "two-party-pol-covenant-happy", + Timeouts: timeouts, + ContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{ + Interchain: &partyAConfig, + }, + PartyBConfig: CovenantPartyConfig{ + Interchain: &partyBConfig, + }, + RagequitConfig: &ragequitConfig, + DepositDeadline: depositDeadline, + PartyAShare: "0.5", + PartyBShare: "0.5", + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.1", + AcceptablePriceSpread: "0.09", + }, + CovenantType: "share", + Splits: denomSplits, + FallbackSplit: nil, + LiquidPoolerConfig: liquidPoolerConfig, + } + + covenantAddress = testCtx.ManualInstantiate(covenantCodeId, covenantMsg, neutronUser, keyring.BackendTest) + + println("covenant address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + partyAIbcForwarderAddress, + partyBIbcForwarderAddress, + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + liquidPoolerAddress, + } + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + }) + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + forwarderBState := testCtx.QueryContractState(partyBIbcForwarderAddress) + + if forwarderAState == forwarderBState && forwarderBState == "ica_created" { + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyBDepositAddress}, cosmosOsmosis, happyCaseOsmoAccount, int64(osmoContributionAmount)) + testCtx.FundChainAddrs([]string{partyADepositAddress}, cosmosAtom, happyCaseHubAccount, int64(atomContributionAmount)) + + testCtx.SkipBlocks(3) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + holderOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + println("holder ibc atom balance: ", holderAtomBal) + println("holder ibc osmo balance: ", holderOsmoBal) + println("holder state: ", holderState) + + if holderAtomBal == atomContributionAmount && holderOsmoBal == osmoContributionAmount { + println("holder received atom & osmo") + break + } else if holderState == "active" { + println("holder: active") + break + } + } + }) + + t.Run("tick until holder sends funds to LiquidPooler and receives LP tokens in return", func(t *testing.T) { + for { + if testCtx.QueryLpTokenBalance(liquidityTokenAddress, liquidPoolerAddress) == 0 { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("tick until holder expires", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + holderState := testCtx.QueryContractState(holderAddress) + println("holder state: ", holderState) + + if holderState == "expired" { + break + } + } + }) + + t.Run("party A claims and router receives the funds", func(t *testing.T) { + testCtx.SkipBlocks(10) + testCtx.HolderClaim(holderAddress, hubNeutronAccount, keyring.BackendTest) + testCtx.SkipBlocks(5) + for { + routerOsmoBalA := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, partyARouterAddress) + routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + println("routerAtomBalA: ", routerAtomBalA) + println("routerOsmoBalA: ", routerOsmoBalA) + if routerAtomBalA != 0 && routerOsmoBalA != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until party A claim is distributed", func(t *testing.T) { + for { + atomBalPartyA, _ := cosmosAtom.GetBalance( + ctx, happyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), cosmosAtom.Config().Denom) + osmoBalPartyA, _ := cosmosAtom.GetBalance( + ctx, happyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), gaiaNeutronOsmoIbcDenom) + + println("party A atom bal: ", atomBalPartyA) + println("party A osmo bal: ", osmoBalPartyA) + + if atomBalPartyA != 0 && osmoBalPartyA != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("party B claims and router receives the funds", func(t *testing.T) { + testCtx.HolderClaim(holderAddress, osmoNeutronAccount, keyring.BackendTest) + for { + routerOsmoBalB := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, partyBRouterAddress) + routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + println("routerAtomBalB: ", routerAtomBalB) + println("routerOsmoBalB: ", routerOsmoBalB) + if routerAtomBalB != 0 && routerOsmoBalB != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick routers until both parties receive their funds", func(t *testing.T) { + for { + osmoBalPartyA, _ := cosmosAtom.GetBalance( + ctx, happyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), gaiaNeutronOsmoIbcDenom) + osmoBalPartyB, _ := cosmosOsmosis.GetBalance( + ctx, happyCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), cosmosOsmosis.Config().Denom) + atomBalPartyA, _ := cosmosAtom.GetBalance( + ctx, happyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), cosmosAtom.Config().Denom) + atomBalPartyB, _ := cosmosOsmosis.GetBalance( + ctx, happyCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), osmoNeutronAtomIbcDenom) + + println("party A osmo bal: ", osmoBalPartyA) + println("party A atom bal: ", atomBalPartyA) + println("party B osmo bal: ", osmoBalPartyB) + println("party B atom bal: ", atomBalPartyB) + + if atomBalPartyA != 0 && osmoBalPartyB != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + }) + + t.Run("two party share based POL ragequit path", func(t *testing.T) { + + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "100", // sec + IbcTransferTimeout: "100", // sec + } + + currentHeight := testCtx.GetNeutronHeight() + depositBlock := Block(currentHeight + 200) + lockupBlock := Block(currentHeight + 300) + + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + depositDeadline := Expiration{ + AtHeight: &depositBlock, + } + + atomCoin := Coin{ + Denom: cosmosAtom.Config().Denom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + + osmoCoin := Coin{ + Denom: cosmosOsmosis.Config().Denom, + Amount: strconv.FormatUint(osmoContributionAmount, 10), + } + hubReceiverAddr := rqCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix) + osmoReceiverAddr := rqCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix) + partyAConfig := InterchainCovenantParty{ + RemoteChainDenom: "uatom", + PartyReceiverAddr: hubReceiverAddr, + Addr: hubNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + Contribution: atomCoin, + NativeDenom: neutronAtomIbcDenom, + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + partyBConfig := InterchainCovenantParty{ + RemoteChainDenom: "uosmo", + PartyReceiverAddr: osmoReceiverAddr, + Addr: osmoNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + Contribution: osmoCoin, + NativeDenom: neutronOsmoIbcDenom, + PartyToHostChainChannelId: testCtx.OsmoTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + PartyChainConnectionId: neutronOsmosisIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + codeIds := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + } + + ragequitTerms := RagequitTerms{ + Penalty: "0.1", + } + + ragequitConfig := RagequitConfig{ + Enabled: &ragequitTerms, + } + + poolAddress := stableswapAddress + pairType := PairType{ + Stable: struct{}{}, + } + + liquidPoolerConfig := LiquidPoolerConfig{ + Astroport: &AstroportLiquidPoolerConfig{ + PairType: pairType, + PoolAddress: poolAddress, + AssetADenom: neutronAtomIbcDenom, + AssetBDenom: neutronOsmoIbcDenom, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "100000", + AssetBLimit: "100000", + }, + }, + } + + covenantMsg := CovenantInstantiateMsg{ + Label: "two-party-pol-covenant-ragequit", + Timeouts: timeouts, + ContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{Interchain: &partyAConfig}, + PartyBConfig: CovenantPartyConfig{Interchain: &partyBConfig}, + RagequitConfig: &ragequitConfig, + DepositDeadline: depositDeadline, + PartyAShare: "0.5", + PartyBShare: "0.5", + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.1", + AcceptablePriceSpread: "0.09", + }, + CovenantType: "share", + Splits: map[string]SplitConfig{ + neutronAtomIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "0.5", + osmoReceiverAddr: "0.5", + }, + }, + neutronOsmoIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "0.5", + osmoReceiverAddr: "0.5", + }, + }, + }, + FallbackSplit: nil, + LiquidPoolerConfig: liquidPoolerConfig, + } + + covenantAddress = testCtx.ManualInstantiate(covenantRqCodeId, covenantMsg, neutronUser, keyring.BackendTest) + println("covenant address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + partyAIbcForwarderAddress, + partyBIbcForwarderAddress, + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + liquidPoolerAddress, + } + println("funding addresses with 5000000000untrn") + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + }) + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + testCtx.SkipBlocks(5) + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + forwarderBState := testCtx.QueryContractState(partyBIbcForwarderAddress) + + if forwarderAState == forwarderBState && forwarderBState == "ica_created" { + testCtx.SkipBlocks(3) + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyBDepositAddress}, cosmosOsmosis, rqCaseOsmoAccount, int64(osmoContributionAmount)) + testCtx.FundChainAddrs([]string{partyADepositAddress}, cosmosAtom, rqCaseHubAccount, int64(atomContributionAmount)) + + testCtx.SkipBlocks(3) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + holderOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + + println("holder atom bal: ", holderAtomBal) + println("holder osmo bal: ", holderOsmoBal) + println("holder state: ", holderState) + + if holderAtomBal == atomContributionAmount && holderOsmoBal == osmoContributionAmount { + println("holder received atom & osmo") + break + } else if holderState == "active" { + println("holder is active") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until holder sends funds to LPer and liquidity is provided", func(t *testing.T) { + for { + lperLpTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, liquidPoolerAddress) + + if lperLpTokenBal == 0 { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("party A ragequits", func(t *testing.T) { + testCtx.SkipBlocks(10) + testCtx.HolderRagequit(holderAddress, hubNeutronAccount, keyring.BackendTest) + testCtx.SkipBlocks(5) + for { + routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + routerOsmoBalB := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, partyBRouterAddress) + + println("routerAtomBalA: ", routerAtomBalA) + println("routerOsmoBalB: ", routerOsmoBalB) + + if routerAtomBalA != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until party A ragequit is distributed", func(t *testing.T) { + for { + osmoBalPartyA, _ := cosmosAtom.GetBalance( + ctx, rqCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), gaiaNeutronOsmoIbcDenom) + osmoBalPartyB, _ := cosmosOsmosis.GetBalance( + ctx, rqCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), cosmosOsmosis.Config().Denom) + atomBalPartyA, _ := cosmosAtom.GetBalance( + ctx, rqCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), cosmosAtom.Config().Denom) + atomBalPartyB, _ := cosmosOsmosis.GetBalance( + ctx, rqCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), osmoNeutronAtomIbcDenom) + + println("party A osmo bal: ", osmoBalPartyA) + println("party A atom bal: ", atomBalPartyA) + println("party B osmo bal: ", osmoBalPartyB) + println("party B atom bal: ", atomBalPartyB) + + if atomBalPartyA != 0 && osmoBalPartyA != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("party B claims and router receives the funds", func(t *testing.T) { + testCtx.HolderClaim(holderAddress, osmoNeutronAccount, keyring.BackendTest) + for { + routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + routerOsmoBalB := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, partyBRouterAddress) + + println("routerAtomBalB: ", routerAtomBalB) + println("routerOsmoBalB: ", routerOsmoBalB) + + if routerOsmoBalB != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick routers until both parties receive their funds", func(t *testing.T) { + for { + osmoBalPartyB, _ := cosmosOsmosis.GetBalance( + ctx, rqCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), cosmosOsmosis.Config().Denom) + atomBalPartyA, _ := cosmosAtom.GetBalance( + ctx, rqCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), cosmosAtom.Config().Denom) + atomBalPartyB, _ := cosmosOsmosis.GetBalance( + ctx, rqCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), osmoNeutronAtomIbcDenom) + + println("party A atom bal: ", atomBalPartyA) + println("party B osmo bal: ", osmoBalPartyB) + println("party B atom bal: ", atomBalPartyB) + + if atomBalPartyA != 0 && osmoBalPartyB != 0 && atomBalPartyB != 0 { + println("nice") + break + } + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + }) + }) + + t.Run("two party POL side-based ragequit path", func(t *testing.T) { + + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "100", // sec + IbcTransferTimeout: "100", // sec + } + + currentHeight := testCtx.GetNeutronHeight() + depositBlock := Block(currentHeight + 200) + lockupBlock := Block(currentHeight + 300) + + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + depositDeadline := Expiration{ + AtHeight: &depositBlock, + } + + atomCoin := Coin{ + Denom: cosmosAtom.Config().Denom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + + osmoCoin := Coin{ + Denom: cosmosOsmosis.Config().Denom, + Amount: strconv.FormatUint(osmoContributionAmount, 10), + } + hubReceiverAddr := sideBasedRqCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix) + osmoReceiverAddr := sideBasedRqCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix) + partyAConfig := InterchainCovenantParty{ + RemoteChainDenom: "uatom", + PartyReceiverAddr: hubReceiverAddr, + Addr: hubNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + Contribution: atomCoin, + NativeDenom: neutronAtomIbcDenom, + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + partyBConfig := InterchainCovenantParty{ + RemoteChainDenom: "uosmo", + PartyReceiverAddr: osmoReceiverAddr, + Addr: osmoNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + Contribution: osmoCoin, + NativeDenom: neutronOsmoIbcDenom, + PartyToHostChainChannelId: testCtx.OsmoTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + PartyChainConnectionId: neutronOsmosisIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + codeIds := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + } + + ragequitTerms := RagequitTerms{ + Penalty: "0.1", + } + + ragequitConfig := RagequitConfig{ + Enabled: &ragequitTerms, + } + + poolAddress := stableswapAddress + pairType := PairType{ + Stable: struct{}{}, + } + + liquidPoolerConfig := LiquidPoolerConfig{ + Astroport: &AstroportLiquidPoolerConfig{ + PairType: pairType, + PoolAddress: poolAddress, + AssetADenom: neutronAtomIbcDenom, + AssetBDenom: neutronOsmoIbcDenom, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "1000000", + AssetBLimit: "1000000", + }, + }, + } + + covenantMsg := CovenantInstantiateMsg{ + Label: "two-party-pol-covenant-side-ragequit", + Timeouts: timeouts, + ContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{Interchain: &partyAConfig}, + PartyBConfig: CovenantPartyConfig{Interchain: &partyBConfig}, + RagequitConfig: &ragequitConfig, + DepositDeadline: depositDeadline, + PartyAShare: "0.5", + PartyBShare: "0.5", + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.1", + AcceptablePriceSpread: "0.09", + }, + CovenantType: "side", + Splits: map[string]SplitConfig{ + neutronAtomIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "1.0", + osmoReceiverAddr: "0.0", + }, + }, + neutronOsmoIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "0.0", + osmoReceiverAddr: "1.0", + }, + }, + }, + FallbackSplit: nil, + LiquidPoolerConfig: liquidPoolerConfig, + } + + covenantAddress = testCtx.ManualInstantiate(covenantSideBasedRqCodeId, covenantMsg, neutronUser, keyring.BackendTest) + println("covenant address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + partyAIbcForwarderAddress, + partyBIbcForwarderAddress, + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + liquidPoolerAddress, + } + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + + testCtx.SkipBlocks(2) + }) + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + forwarderBState := testCtx.QueryContractState(partyBIbcForwarderAddress) + + if forwarderAState == forwarderBState && forwarderBState == "ica_created" { + testCtx.SkipBlocks(5) + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyBDepositAddress}, cosmosOsmosis, sideBasedRqCaseOsmoAccount, int64(osmoContributionAmount)) + testCtx.FundChainAddrs([]string{partyADepositAddress}, cosmosAtom, sideBasedRqCaseHubAccount, int64(atomContributionAmount)) + + testCtx.SkipBlocks(3) + + atomBal, _ := cosmosAtom.GetBalance(ctx, partyADepositAddress, nativeAtomDenom) + require.Equal(t, int64(atomContributionAmount), atomBal) + osmoBal, _ := cosmosOsmosis.GetBalance(ctx, partyBDepositAddress, nativeOsmoDenom) + require.Equal(t, int64(osmoContributionAmount), osmoBal) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + holderOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + + println("holder atom bal: ", holderAtomBal) + println("holder osmo bal: ", holderOsmoBal) + println("holder state: ", holderState) + + if holderAtomBal == atomContributionAmount && holderOsmoBal == osmoContributionAmount { + println("holder received atom & osmo") + break + } else if holderState == "active" { + println("holderState: ", holderState) + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until holder sends the funds to LPer it provides liquidity", func(t *testing.T) { + for { + lperLpTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, liquidPoolerAddress) + println("liquid pooler lp token balance: ", lperLpTokenBal) + + if lperLpTokenBal == 0 { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("party A ragequits", func(t *testing.T) { + testCtx.SkipBlocks(10) + testCtx.HolderRagequit(holderAddress, hubNeutronAccount, keyring.BackendTest) + testCtx.SkipBlocks(5) + for { + routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + routerOsmoBalB := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, partyBRouterAddress) + + println("routerAtomBalA: ", routerAtomBalA) + println("routerOsmoBalB: ", routerOsmoBalB) + + if routerAtomBalA != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick routers until both parties receive their funds", func(t *testing.T) { + for { + osmoBalPartyB, _ := cosmosOsmosis.GetBalance( + ctx, sideBasedRqCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), cosmosOsmosis.Config().Denom, + ) + atomBalPartyA, _ := cosmosAtom.GetBalance( + ctx, sideBasedRqCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), cosmosAtom.Config().Denom, + ) + atomBalPartyB, _ := cosmosOsmosis.GetBalance( + ctx, sideBasedRqCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), osmoNeutronAtomIbcDenom, + ) + + println("party A atom bal: ", atomBalPartyA) + println("party B osmo bal: ", osmoBalPartyB) + println("party B atom bal: ", atomBalPartyB) + + if atomBalPartyA != 0 && osmoBalPartyB != 0 && atomBalPartyB != 0 { + println("nice") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + }) + + t.Run("two party POL side-based happy path", func(t *testing.T) { + var expirationHeight Block + t.Run("instantiate covenant", func(t *testing.T) { + timeouts := Timeouts{ + IcaTimeout: "100", // sec + IbcTransferTimeout: "100", // sec + } + + currentHeight := testCtx.GetNeutronHeight() + depositBlock := Block(currentHeight + 210) + lockupBlock := Block(currentHeight + 230) + expirationHeight = lockupBlock + lockupConfig := Expiration{ + AtHeight: &lockupBlock, + } + depositDeadline := Expiration{ + AtHeight: &depositBlock, + } + + atomCoin := Coin{ + Denom: cosmosAtom.Config().Denom, + Amount: strconv.FormatUint(atomContributionAmount, 10), + } + + osmoCoin := Coin{ + Denom: cosmosOsmosis.Config().Denom, + Amount: strconv.FormatUint(osmoContributionAmount, 10), + } + hubReceiverAddr := sideBasedHappyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix) + osmoReceiverAddr := sideBasedHappyCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix) + partyAConfig := InterchainCovenantParty{ + RemoteChainDenom: "uatom", + PartyReceiverAddr: hubReceiverAddr, + Addr: hubNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + Contribution: atomCoin, + NativeDenom: neutronAtomIbcDenom, + PartyToHostChainChannelId: testCtx.GaiaTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosAtom.Config().Name], + PartyChainConnectionId: neutronAtomIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + partyBConfig := InterchainCovenantParty{ + RemoteChainDenom: "uosmo", + PartyReceiverAddr: osmoReceiverAddr, + Addr: osmoNeutronAccount.Bech32Address(cosmosNeutron.Config().Bech32Prefix), + Contribution: osmoCoin, + NativeDenom: neutronOsmoIbcDenom, + PartyToHostChainChannelId: testCtx.OsmoTransferChannelIds[cosmosNeutron.Config().Name], + HostToPartyChainChannelId: testCtx.NeutronTransferChannelIds[cosmosOsmosis.Config().Name], + PartyChainConnectionId: neutronOsmosisIBCConnId, + IbcTransferTimeout: timeouts.IbcTransferTimeout, + DenomToPfmMap: map[string]PacketForwardMiddlewareConfig{}, + } + codeIds := ContractCodeIds{ + IbcForwarderCode: ibcForwarderCodeId, + InterchainRouterCode: interchainRouterCodeId, + NativeRouterCode: nativeRouterCodeId, + ClockCode: clockCodeId, + HolderCode: holderCodeId, + LiquidPoolerCode: lperCodeId, + } + + ragequitTerms := RagequitTerms{ + Penalty: "0.1", + } + + ragequitConfig := RagequitConfig{ + Enabled: &ragequitTerms, + } + + poolAddress := stableswapAddress + pairType := PairType{ + Stable: struct{}{}, + } + + liquidPoolerConfig := LiquidPoolerConfig{ + Astroport: &AstroportLiquidPoolerConfig{ + PairType: pairType, + PoolAddress: poolAddress, + AssetADenom: neutronAtomIbcDenom, + AssetBDenom: neutronOsmoIbcDenom, + SingleSideLpLimits: SingleSideLpLimits{ + AssetALimit: "1000000", + AssetBLimit: "10000000", + }, + }, + } + + covenantMsg := CovenantInstantiateMsg{ + Label: "two-party-pol-covenant-side-happy", + Timeouts: timeouts, + ContractCodeIds: codeIds, + LockupConfig: lockupConfig, + PartyAConfig: CovenantPartyConfig{Interchain: &partyAConfig}, + PartyBConfig: CovenantPartyConfig{Interchain: &partyBConfig}, + RagequitConfig: &ragequitConfig, + DepositDeadline: depositDeadline, + PartyAShare: "0.5", + PartyBShare: "0.5", + PoolPriceConfig: PoolPriceConfig{ + ExpectedSpotPrice: "0.1", + AcceptablePriceSpread: "0.09", + }, + CovenantType: "side", + Splits: map[string]SplitConfig{ + neutronAtomIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "1.0", + osmoReceiverAddr: "0.0", + }, + }, + neutronOsmoIbcDenom: SplitConfig{ + Receivers: map[string]string{ + hubReceiverAddr: "0.0", + osmoReceiverAddr: "1.0", + }, + }, + }, + FallbackSplit: nil, + LiquidPoolerConfig: liquidPoolerConfig, + } + + covenantAddress = testCtx.ManualInstantiate(covenantSideBasedRqCodeId, covenantMsg, neutronUser, keyring.BackendTest) + println("covenant address: ", covenantAddress) + }) + + t.Run("query covenant contracts", func(t *testing.T) { + clockAddress = testCtx.QueryClockAddress(covenantAddress) + holderAddress = testCtx.QueryHolderAddress(covenantAddress) + liquidPoolerAddress = testCtx.QueryLiquidPoolerAddress(covenantAddress) + partyARouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_a") + partyBRouterAddress = testCtx.QueryInterchainRouterAddress(covenantAddress, "party_b") + partyAIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_a") + partyBIbcForwarderAddress = testCtx.QueryIbcForwarderAddress(covenantAddress, "party_b") + }) + + t.Run("fund contracts with neutron", func(t *testing.T) { + addrs := []string{ + partyAIbcForwarderAddress, + partyBIbcForwarderAddress, + clockAddress, + partyARouterAddress, + partyBRouterAddress, + holderAddress, + liquidPoolerAddress, + } + testCtx.FundChainAddrs(addrs, cosmosNeutron, neutronUser, 5000000000) + + testCtx.SkipBlocks(2) + }) + + t.Run("tick until forwarders create ICA", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + + forwarderAState := testCtx.QueryContractState(partyAIbcForwarderAddress) + forwarderBState := testCtx.QueryContractState(partyBIbcForwarderAddress) + + if forwarderAState == forwarderBState && forwarderBState == "ica_created" { + testCtx.SkipBlocks(5) + partyADepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_a") + partyBDepositAddress = testCtx.QueryDepositAddress(covenantAddress, "party_b") + break + } + } + }) + + t.Run("fund the forwarders with sufficient funds", func(t *testing.T) { + testCtx.FundChainAddrs([]string{partyBDepositAddress}, cosmosOsmosis, sideBasedHappyCaseOsmoAccount, int64(osmoContributionAmount)) + testCtx.FundChainAddrs([]string{partyADepositAddress}, cosmosAtom, sideBasedHappyCaseHubAccount, int64(atomContributionAmount)) + + testCtx.SkipBlocks(3) + + atomBal, _ := cosmosAtom.GetBalance(ctx, partyADepositAddress, nativeAtomDenom) + require.Equal(t, int64(atomContributionAmount), atomBal) + osmoBal, _ := cosmosOsmosis.GetBalance(ctx, partyBDepositAddress, nativeOsmoDenom) + require.Equal(t, int64(osmoContributionAmount), osmoBal) + }) + + t.Run("tick until forwarders forward the funds to holder", func(t *testing.T) { + for { + holderOsmoBal := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, holderAddress) + holderAtomBal := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, holderAddress) + holderState := testCtx.QueryContractState(holderAddress) + + println("holder atom bal: ", holderAtomBal) + println("holder osmo bal: ", holderOsmoBal) + println("holder state: ", holderState) + + if holderAtomBal == atomContributionAmount && holderOsmoBal == osmoContributionAmount { + println("holder/liquidpooler received atom & osmo") + break + } else if holderState == "active" { + println("holderState: ", holderState) + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + + t.Run("tick until holder sends the funds to LPer and it provides liquidity", func(t *testing.T) { + for { + lperLpTokenBal := testCtx.QueryLpTokenBalance(liquidityTokenAddress, liquidPoolerAddress) + println("liquid pooler lp token balance: ", lperLpTokenBal) + + if lperLpTokenBal == 0 { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } else { + break + } + } + }) + + t.Run("lockup expires", func(t *testing.T) { + for { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + if testCtx.GetNeutronHeight() >= uint64(expirationHeight) { + break + } + } + }) + + t.Run("party A claims", func(t *testing.T) { + for { + routerAtomBalB := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyBRouterAddress) + routerOsmoBalB := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, partyBRouterAddress) + routerAtomBalA := testCtx.QueryNeutronDenomBalance(neutronAtomIbcDenom, partyARouterAddress) + routerOsmoBalA := testCtx.QueryNeutronDenomBalance(neutronOsmoIbcDenom, partyARouterAddress) + + println("routerAtomBalB: ", routerAtomBalB) + println("routerOsmoBalB: ", routerOsmoBalB) + println("routerAtomBalA: ", routerAtomBalA) + println("routerOsmoBalA: ", routerOsmoBalA) + + if routerOsmoBalB != 0 { + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + testCtx.HolderClaim(holderAddress, osmoNeutronAccount, keyring.BackendTest) + } + } + + }) + + t.Run("tick routers until both parties receive their funds", func(t *testing.T) { + for { + osmoBalPartyB, _ := cosmosOsmosis.GetBalance( + ctx, sideBasedHappyCaseOsmoAccount.Bech32Address(cosmosOsmosis.Config().Bech32Prefix), cosmosOsmosis.Config().Denom, + ) + atomBalPartyA, _ := cosmosAtom.GetBalance( + ctx, sideBasedHappyCaseHubAccount.Bech32Address(cosmosAtom.Config().Bech32Prefix), cosmosAtom.Config().Denom, + ) + + println("party A atom bal: ", atomBalPartyA) + println("party B osmo bal: ", osmoBalPartyB) + + if atomBalPartyA != 0 && osmoBalPartyB != 0 { + println("nice") + break + } else { + testCtx.Tick(clockAddress, keyring.BackendTest, neutronUser.KeyName) + } + } + }) + }) + }) +} diff --git a/interchaintest/two-party-pol/types.go b/interchaintest/two-party-pol/types.go new file mode 100644 index 00000000..21af82c0 --- /dev/null +++ b/interchaintest/two-party-pol/types.go @@ -0,0 +1,531 @@ +package covenant_two_party_pol + +import ( + cw "github.com/CosmWasm/wasmvm/types" +) + +////////////////////////////////////////////// +///// Covenant contracts +////////////////////////////////////////////// + +// ----- Covenant Instantiation ------ +type CovenantInstantiateMsg struct { + Label string `json:"label"` + Timeouts Timeouts `json:"timeouts"` + ContractCodeIds ContractCodeIds `json:"contract_codes"` + TickMaxGas string `json:"clock_tick_max_gas,omitempty"` + LockupConfig Expiration `json:"lockup_config"` + PartyAConfig CovenantPartyConfig `json:"party_a_config"` + PartyBConfig CovenantPartyConfig `json:"party_b_config"` + RagequitConfig *RagequitConfig `json:"ragequit_config,omitempty"` + DepositDeadline Expiration `json:"deposit_deadline"` + CovenantType string `json:"covenant_type"` + PartyAShare string `json:"party_a_share"` + PartyBShare string `json:"party_b_share"` + Splits map[string]SplitConfig `json:"splits"` + FallbackSplit *SplitConfig `json:"fallback_split,omitempty"` + EmergencyCommittee string `json:"emergency_committee,omitempty"` + LiquidPoolerConfig LiquidPoolerConfig `json:"liquid_pooler_config"` + PoolPriceConfig PoolPriceConfig `json:"pool_price_config"` +} + +type PacketForwardMiddlewareConfig struct { + LocalToHopChainChannelId string `json:"local_to_hop_chain_channel_id"` + HopToDestinationChainChannelId string `json:"hop_to_destination_chain_channel_id"` + HopChainReceiverAddress string `json:"hop_chain_receiver_address"` +} + +type LiquidPoolerConfig struct { + Astroport *AstroportLiquidPoolerConfig `json:"astroport,omitempty"` + Osmosis *OsmosisLiquidPoolerConfig `json:"osmosis,omitempty"` +} + +type OsmosisLiquidPoolerConfig struct { + NoteAddress string `json:"note_address"` + PoolId string `json:"pool_id"` + OsmoIbcTimeout string `json:"osmo_ibc_timeout"` + OsmoOutpost string `json:"osmo_outpost"` + Party1ChainInfo PartyChainInfo `json:"party_1_chain_info"` + Party2ChainInfo PartyChainInfo `json:"party_2_chain_info"` + LpTokenDenom string `json:"lp_token_denom"` + OsmoToNeutronChannelId string `json:"osmo_to_neutron_channel_id"` + Party1DenomInfo PartyDenomInfo `json:"party_1_denom_info"` + Party2DenomInfo PartyDenomInfo `json:"party_2_denom_info"` + FundingDuration Duration `json:"funding_duration"` + SingleSideLpLimits SingleSideLpLimits `json:"single_side_lp_limits"` +} + +type SingleSideLpLimits struct { + AssetALimit string `json:"asset_a_limit"` + AssetBLimit string `json:"asset_b_limit"` +} + +type AstroportLiquidPoolerConfig struct { + PairType PairType `json:"pool_pair_type"` + PoolAddress string `json:"pool_address"` + AssetADenom string `json:"asset_a_denom"` + AssetBDenom string `json:"asset_b_denom"` + SingleSideLpLimits SingleSideLpLimits `json:"single_side_lp_limits"` +} + +type Receiver struct { + Address string `json:"addr"` + Share string `json:"share"` +} + +type SplitConfig struct { + Receivers map[string]string `json:"receivers"` +} + +type ContractCodeIds struct { + IbcForwarderCode uint64 `json:"ibc_forwarder_code"` + InterchainRouterCode uint64 `json:"interchain_router_code"` + NativeRouterCode uint64 `json:"native_router_code"` + ClockCode uint64 `json:"clock_code"` + HolderCode uint64 `json:"holder_code"` + LiquidPoolerCode uint64 `json:"liquid_pooler_code"` +} + +type Timeouts struct { + IcaTimeout string `json:"ica_timeout"` + IbcTransferTimeout string `json:"ibc_transfer_timeout"` +} + +type Timestamp string +type Block uint64 + +type Expiration struct { + Never string `json:"none,omitempty"` + AtHeight *Block `json:"at_height,omitempty"` + AtTime *Timestamp `json:"at_time,omitempty"` +} + +type Duration struct { + Height *uint64 `json:"height,omitempty"` + Time *uint64 `json:"time,omitempty"` +} + +type RagequitConfig struct { + Disabled bool `json:"disabled,omitempty"` + Enabled *RagequitTerms `json:"enabled,omitempty"` +} + +type Share string +type Side string + +type CovenantType struct { + Share string `json:"share,omitempty"` + Side string `json:"side,omitempty"` +} + +type RagequitTerms struct { + Penalty string `json:"penalty"` + State *RagequitState `json:"state,omitempty"` +} + +type RagequitState struct { + Coins []Coin `json:"coins"` + RqParty CovenantParty `json:"rq_party"` +} + +type CovenantParty struct { + Contribution Coin `json:"contribution"` + Addr string `json:"addr"` + Allocation string `json:"allocation"` + Router string `json:"router"` +} + +type CovenantPartyConfig struct { + Interchain *InterchainCovenantParty `json:"interchain,omitempty"` + Native *NativeCovenantParty `json:"native,omitempty"` +} + +type InterchainCovenantParty struct { + Addr string `json:"addr"` + NativeDenom string `json:"native_denom"` + RemoteChainDenom string `json:"remote_chain_denom"` + PartyToHostChainChannelId string `json:"party_to_host_chain_channel_id"` + HostToPartyChainChannelId string `json:"host_to_party_chain_channel_id"` + PartyReceiverAddr string `json:"party_receiver_addr"` + PartyChainConnectionId string `json:"party_chain_connection_id"` + IbcTransferTimeout string `json:"ibc_transfer_timeout"` + Contribution Coin `json:"contribution"` + DenomToPfmMap map[string]PacketForwardMiddlewareConfig `json:"denom_to_pfm_map"` +} + +type NativeCovenantParty struct { + Addr string `json:"addr"` + NativeDenom string `json:"native_denom"` + PartyReceiverAddr string `json:"party_receiver_addr"` + Contribution Coin `json:"contribution"` +} + +// type CovenantPartyConfig struct { +// ControllerAddr string `json:"controller_addr"` +// HostAddr string `json:"host_addr"` +// Contribution Coin `json:"contribution"` +// IbcDenom string `json:"ibc_denom"` +// PartyToHostChainChannelId string `json:"party_to_host_chain_channel_id"` +// HostToPartyChainChannelId string `json:"host_to_party_chain_channel_id"` +// PartyChainConnectionId string `json:"party_chain_connection_id"` +// IbcTransferTimeout string `json:"ibc_transfer_timeout"` +// } + +type Coin struct { + Denom string `json:"denom"` + Amount string `json:"amount"` +} + +// ----- Covenant Queries ------ +type ClockAddress struct{} +type ClockAddressQuery struct { + ClockAddress ClockAddress `json:"clock_address"` +} + +type HolderAddress struct{} +type HolderAddressQuery struct { + HolderAddress HolderAddress `json:"holder_address"` +} + +type CovenantParties struct{} +type CovenantPartiesQuery struct { + CovenantParties CovenantParties `json:"covenant_parties"` +} + +type Party struct { + Party string `json:"party"` +} +type InterchainRouterQuery struct { + Party Party `json:"interchain_router_address"` +} +type IbcForwarderQuery struct { + Party Party `json:"ibc_forwarder_address"` +} +type LiquidPoolerAddress struct{} +type LiquidPoolerQuery struct { + LiquidPoolerAddress LiquidPoolerAddress `json:"liquid_pooler_address"` +} +type CovenantAddressQueryResponse struct { + Data string `json:"data"` +} + +// astroport stableswap +type StableswapInstantiateMsg struct { + TokenCodeId uint64 `json:"token_code_id"` + FactoryAddr string `json:"factory_addr"` + AssetInfos []AssetInfo `json:"asset_infos"` + InitParams []byte `json:"init_params"` +} + +type AssetInfo struct { + Token *Token `json:"token,omitempty"` + NativeToken *NativeToken `json:"native_token,omitempty"` +} + +type StablePoolParams struct { + Amp uint64 `json:"amp"` + Owner *string `json:"owner"` +} + +type Token struct { + ContractAddr string `json:"contract_addr"` +} + +type NativeToken struct { + Denom string `json:"denom"` +} + +type CwCoin struct { + Denom string `json:"denom"` + Amount uint64 `json:"amount"` +} + +// astroport factory +type FactoryInstantiateMsg struct { + PairConfigs []PairConfig `json:"pair_configs"` + TokenCodeId uint64 `json:"token_code_id"` + FeeAddress *string `json:"fee_address"` + GeneratorAddress *string `json:"generator_address"` + Owner string `json:"owner"` + WhitelistCodeId uint64 `json:"whitelist_code_id"` + CoinRegistryAddress string `json:"coin_registry_address"` +} + +type PairConfig struct { + CodeId uint64 `json:"code_id"` + PairType PairType `json:"pair_type"` + TotalFeeBps uint64 `json:"total_fee_bps"` + MakerFeeBps uint64 `json:"maker_fee_bps"` + IsDisabled bool `json:"is_disabled"` + IsGeneratorDisabled bool `json:"is_generator_disabled"` +} + +type PairType struct { + // Xyk struct{} `json:"xyk,omitempty"` + Stable struct{} `json:"stable,omitempty"` + // Custom struct{} `json:"custom,omitempty"` +} + +// astroport native coin registry + +type NativeCoinRegistryInstantiateMsg struct { + Owner string `json:"owner"` +} + +type AddExecuteMsg struct { + Add Add `json:"add"` +} + +type Add struct { + NativeCoins []NativeCoin `json:"native_coins"` +} + +type NativeCoin struct { + Name string `json:"name"` + Value uint8 `json:"value"` +} + +// Add { native_coins: Vec<(String, u8)> }, + +// astroport native token +type NativeTokenInstantiateMsg struct { + Name string `json:"name"` + Symbol string `json:"symbol"` + Decimals uint8 `json:"decimals"` + InitialBalances []Cw20Coin `json:"initial_balances"` + Mint *MinterResponse `json:"mint"` + Marketing *InstantiateMarketingInfo `json:"marketing"` +} + +type Cw20Coin struct { + Address string `json:"address"` + Amount uint64 `json:"amount"` +} + +type MinterResponse struct { + Minter string `json:"minter"` + Cap *uint64 `json:"cap,omitempty"` +} + +type InstantiateMarketingInfo struct { + Project string `json:"project"` + Description string `json:"description"` + Marketing string `json:"marketing"` + Logo Logo `json:"logo"` +} + +type Logo struct { + Url string `json:"url"` +} + +// astroport whitelist +type WhitelistInstantiateMsg struct { + Admins []string `json:"admins"` + Mutable bool `json:"mutable"` +} + +type ProvideLiqudityMsg struct { + ProvideLiquidity ProvideLiquidityStruct `json:"provide_liquidity"` +} + +type ProvideLiquidityStruct struct { + Assets []AstroportAsset `json:"assets"` + SlippageTolerance string `json:"slippage_tolerance"` + AutoStake bool `json:"auto_stake"` + Receiver string `json:"receiver"` +} + +// factory + +type FactoryPairResponse struct { + Data PairInfo `json:"data"` +} + +type LpPositionQueryResponse struct { + Data string `json:"data"` +} + +type AstroportAsset struct { + Info AssetInfo `json:"info"` + Amount string `json:"amount"` +} + +type LpPositionQuery struct{} + +type PairInfo struct { + LiquidityToken string `json:"liquidity_token"` + ContractAddr string `json:"contract_addr"` + PairType PairType `json:"pair_type"` + AssetInfos []AssetInfo `json:"asset_infos"` +} + +type LPPositionQuery struct { + LpPosition LpPositionQuery `json:"lp_position"` +} + +type Pair struct { + AssetInfos []AssetInfo `json:"asset_infos"` +} + +type PairQuery struct { + Pair Pair `json:"pair"` +} + +type CreatePair struct { + PairType PairType `json:"pair_type"` + AssetInfos []AssetInfo `json:"asset_infos"` + InitParams []byte `json:"init_params"` +} + +type CreatePairMsg struct { + CreatePair CreatePair `json:"create_pair"` +} + +type BalanceResponse struct { + Balance string `json:"balance"` +} + +type Cw20BalanceResponse struct { + Data BalanceResponse `json:"data"` +} + +type AllAccountsResponse struct { + Data []string `json:"all_accounts_response"` +} + +type Cw20QueryMsg struct { + Balance Balance `json:"balance"` + // AllAccounts *AllAccounts `json:"all_accounts"` +} + +type AllAccounts struct { +} + +type Balance struct { + Address string `json:"address"` +} + +type NativeBalQueryResponse struct { + Amount string `json:"amount"` + Denom string `json:"denom"` +} + +// polytone types +type PolytonePair struct { + ConnectionId string `json:"connection_id"` + RemotePort string `json:"remote_port"` +} + +type NoteInstantiate struct { + Pair *PolytonePair `json:"pair,omitempty"` + BlockMaxGas string `json:"block_max_gas,omitempty"` +} + +type VoiceInstantiate struct { + ProxyCodeId uint64 `json:"proxy_code_id,string"` + BlockMaxGas uint64 `json:"block_max_gas,string"` +} + +type CallbackRequest struct { + Receiver string `json:"receiver"` + Msg string `json:"msg"` +} + +type CallbackMessage struct { + Initiator string `json:"initiator"` + InitiatorMsg string `json:"initiator_msg"` + Result Callback `json:"result"` +} + +type Callback struct { + Success []string `json:"success,omitempty"` + Error string `json:"error,omitempty"` +} + +type NoteExecuteMsg struct { + Msgs []cw.CosmosMsg `json:"msgs"` + TimeoutSeconds uint64 `json:"timeout_seconds,string"` + Callback *CallbackRequest `json:"callback,omitempty"` +} + +type NoteQuery struct { + Msgs []cw.CosmosMsg `json:"msgs"` + TimeoutSeconds uint64 `json:"timeout_seconds,string"` + Callback CallbackRequest `json:"callback"` +} + +type NoteExecute struct { + Query *NoteQuery `json:"query,omitempty"` + Execute *NoteExecuteMsg `json:"execute,omitempty"` +} + +type RemoteAddress struct { + LocalAddress string `json:"local_address"` +} +type NoteQueryMsg struct { + RemoteAddressQuery RemoteAddress `json:"remote_address"` +} + +type TesterInstantiate struct { +} + +type StargateMsg struct { + TypeUrl string `json:"type_url"` + Value string `json:"value"` +} + +// osmosis.gamm.v1beta1.MsgJoinPool +type MsgJoinPool struct { + Sender string `json:"sender"` + PoolId uint64 `json:"pool_id"` + ShareOutAmount string `json:"share_out_amount"` + TokenInMaxs []cw.Coin `json:"token_in_maxs"` +} + +type OsmoLiquidPoolerInstantiateMsg struct { + ClockAddress string `json:"clock_address"` + HolderAddress string `json:"holder_address"` + NoteAddress string `json:"note_address"` + PoolId string `json:"pool_id"` + OsmoIbcTimeout string `json:"osmo_ibc_timeout"` + Party1ChainInfo PartyChainInfo `json:"party_1_chain_info"` + Party2ChainInfo PartyChainInfo `json:"party_2_chain_info"` + OsmoToNeutronChannelId string `json:"osmo_to_neutron_channel_id"` + Party1DenomInfo PartyDenomInfo `json:"party_1_denom_info"` + Party2DenomInfo PartyDenomInfo `json:"party_2_denom_info"` + OsmoOutpost string `json:"osmo_outpost"` + LpTokenDenom string `json:"lp_token_denom"` + SlippageTolerance string `json:"slippage_tolerance"` + FundingDuration Duration `json:"funding_duration"` + SingleSideLpLimits SingleSideLpLimits `json:"single_side_lp_limits"` + PoolPriceConfig PoolPriceConfig `json:"pool_price_config"` +} +type PoolPriceConfig struct { + ExpectedSpotPrice string `json:"expected_spot_price"` + AcceptablePriceSpread string `json:"acceptable_price_spread"` +} + +type PartyDenomInfo struct { + OsmosisCoin cw.Coin `json:"osmosis_coin"` + LocalDenom string `json:"local_denom"` +} + +type PartyChainInfo struct { + NeutronToPartyChainChannel string `json:"neutron_to_party_chain_channel"` + PartyChainToNeutronChannel string `json:"party_chain_to_neutron_channel"` + InwardsPfm *ForwardMetadata `json:"inwards_pfm,omitempty"` + OutwardsPfm *ForwardMetadata `json:"outwards_pfm,omitempty"` + IbcTimeout string `json:"ibc_timeout"` +} + +type PacketMetadata struct { + ForwardMetadata *ForwardMetadata `json:"forward,omitempty"` +} + +type ForwardMetadata struct { + Receiver string `json:"receiver"` + Port string `json:"port"` + Channel string `json:"channel"` + // Timeout string `json:"timeout,omitempty"` + // Retries uint8 `json:"retries,omitempty"` +} diff --git a/interchaintest/utils/connection_helpers.go b/interchaintest/utils/connection_helpers.go new file mode 100644 index 00000000..6bc0f6c3 --- /dev/null +++ b/interchaintest/utils/connection_helpers.go @@ -0,0 +1,1648 @@ +package utils + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" + "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v4/ibc" + "github.com/strangelove-ventures/interchaintest/v4/testreporter" + "github.com/strangelove-ventures/interchaintest/v4/testutil" + "github.com/stretchr/testify/require" +) + +type TestContext struct { + Neutron *cosmos.CosmosChain + Hub *cosmos.CosmosChain + Osmosis *cosmos.CosmosChain + Stride *cosmos.CosmosChain + + OsmoClients []*ibc.ClientOutput + GaiaClients []*ibc.ClientOutput + NeutronClients []*ibc.ClientOutput + StrideClients []*ibc.ClientOutput + + OsmoConnections []*ibc.ConnectionOutput + GaiaConnections []*ibc.ConnectionOutput + NeutronConnections []*ibc.ConnectionOutput + StrideConnections []*ibc.ConnectionOutput + + NeutronTransferChannelIds map[string]string + GaiaTransferChannelIds map[string]string + OsmoTransferChannelIds map[string]string + StrideTransferChannelIds map[string]string + + GaiaIcsChannelIds map[string]string + NeutronIcsChannelIds map[string]string + T *testing.T + Ctx context.Context +} + +func (testCtx *TestContext) Tick(clock string, keyring string, from string) { + neutronHeight, _ := testCtx.Neutron.Height(testCtx.Ctx) + println("tick neutron@", neutronHeight) + cmd := []string{"neutrond", "tx", "wasm", "execute", clock, + `{"tick":{}}`, + "--gas-prices", "0.0untrn", + "--gas-adjustment", `2`, + "--output", "json", + "--node", testCtx.Neutron.GetRPCAddress(), + "--home", testCtx.Neutron.HomeDir(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--from", from, + "--gas", "1500000", + "--keyring-backend", keyring, + "-y", + } + + tickresponse, _, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + require.NoError(testCtx.T, err) + println("tick reponse: ", string(tickresponse)) + testCtx.SkipBlocks(3) +} + +func (testCtx *TestContext) TickStride(clock string, keyring string, from string) { + neutronHeight, _ := testCtx.Neutron.Height(testCtx.Ctx) + println("tick neutron@", neutronHeight) + cmd := []string{"neutrond", "tx", "wasm", "execute", clock, + `{"tick":{}}`, + "--gas-prices", "0.0untrn", + "--gas-adjustment", `1.5`, + "--output", "json", + "--node", testCtx.Neutron.GetRPCAddress(), + "--home", testCtx.Neutron.HomeDir(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--from", from, + "--gas", "4500000", + "--keyring-backend", keyring, + "-y", + } + + tickresponse, _, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + require.NoError(testCtx.T, err) + println("tick reponse: ", string(tickresponse)) + testCtx.SkipBlocksStride(3) +} + +func (testCtx *TestContext) SkipBlocks(n uint64) { + require.NoError( + testCtx.T, + testutil.WaitForBlocks(testCtx.Ctx, int(n), testCtx.Hub, testCtx.Neutron, testCtx.Osmosis), + "failed to wait for blocks") +} + +func (testCtx *TestContext) SkipBlocksStride(n uint64) { + require.NoError( + testCtx.T, + testutil.WaitForBlocks(testCtx.Ctx, int(n), testCtx.Hub, testCtx.Neutron, testCtx.Stride), + "failed to wait for blocks") +} + +func (testCtx *TestContext) GetIbcDenom(channelId string, denom string) string { + prefixedDenom := transfertypes.GetPrefixedDenom("transfer", channelId, denom) + srcDenomTrace := transfertypes.ParseDenomTrace(prefixedDenom) + return srcDenomTrace.IBCDenom() +} + +// channel trace should be an ordered list of the path denom would take, +// starting from the source chain, and ending on the destination chain. +// assumes "transfer" ports. +func (testCtx *TestContext) GetMultihopIbcDenom(channelTrace []string, denom string) string { + var portChannelTrace []string + + for _, channel := range channelTrace { + portChannelTrace = append(portChannelTrace, fmt.Sprintf("%s/%s", "transfer", channel)) + } + + prefixedDenom := fmt.Sprintf("%s/%s", strings.Join(portChannelTrace, "/"), denom) + + denomTrace := transfertypes.ParseDenomTrace(prefixedDenom) + return denomTrace.IBCDenom() + +} + +func (testCtx *TestContext) GetChainClients(chain string) []*ibc.ClientOutput { + switch chain { + case "neutron-2": + return testCtx.NeutronClients + case "gaia-1": + return testCtx.GaiaClients + case "osmosis-3": + return testCtx.OsmoClients + case "stride-3": + return testCtx.StrideClients + default: + return ibc.ClientOutputs{} + } +} + +func (testCtx *TestContext) SetTransferChannelId(chain string, destChain string, channelId string) { + switch chain { + case "neutron-2": + testCtx.NeutronTransferChannelIds[destChain] = channelId + case "gaia-1": + testCtx.GaiaTransferChannelIds[destChain] = channelId + case "osmosis-3": + testCtx.OsmoTransferChannelIds[destChain] = channelId + case "stride-3": + testCtx.StrideTransferChannelIds[destChain] = channelId + default: + } +} + +func (testCtx *TestContext) SetIcsChannelId(chain string, destChain string, channelId string) { + switch chain { + case "neutron-2": + testCtx.NeutronIcsChannelIds[destChain] = channelId + case "gaia-1": + testCtx.GaiaIcsChannelIds[destChain] = channelId + default: + } +} + +func (testCtx *TestContext) UpdateChainClients(chain string, clients []*ibc.ClientOutput) { + switch chain { + case "neutron-2": + testCtx.NeutronClients = clients + case "gaia-1": + testCtx.GaiaClients = clients + case "osmosis-3": + testCtx.OsmoClients = clients + case "stride-3": + testCtx.StrideClients = clients + default: + } +} + +func (testCtx *TestContext) GetChainConnections(chain string) []*ibc.ConnectionOutput { + switch chain { + case "neutron-2": + return testCtx.NeutronConnections + case "gaia-1": + return testCtx.GaiaConnections + case "osmosis-3": + return testCtx.OsmoConnections + case "stride-3": + return testCtx.StrideConnections + default: + println("error finding connections for chain ", chain) + return []*ibc.ConnectionOutput{} + } +} + +func (testCtx *TestContext) UpdateChainConnections(chain string, connections []*ibc.ConnectionOutput) { + switch chain { + case "neutron-2": + testCtx.NeutronConnections = connections + case "gaia-1": + testCtx.GaiaConnections = connections + case "osmosis-3": + testCtx.OsmoConnections = connections + case "stride-3": + testCtx.StrideConnections = connections + default: + } +} + +func GeneratePath( + t *testing.T, + ctx context.Context, + r ibc.Relayer, + eRep *testreporter.RelayerExecReporter, + chainAId string, + chainBId string, + path string, +) { + err := r.GeneratePath(ctx, eRep, chainAId, chainBId, path) + require.NoError(t, err) +} + +func GenerateICSChannel( + t *testing.T, + ctx context.Context, + r ibc.Relayer, + eRep *testreporter.RelayerExecReporter, + icsPath string, + chainA ibc.Chain, + chainB ibc.Chain, +) { + + err := r.CreateChannel(ctx, eRep, icsPath, ibc.CreateChannelOptions{ + SourcePortName: "consumer", + DestPortName: "provider", + Order: ibc.Ordered, + Version: "1", + }) + require.NoError(t, err) + err = testutil.WaitForBlocks(ctx, 2, chainA, chainB) + require.NoError(t, err, "failed to wait for blocks") +} + +func CreateValidator( + t *testing.T, + ctx context.Context, + r ibc.Relayer, + eRep *testreporter.RelayerExecReporter, + chain ibc.Chain, + counterparty ibc.Chain, +) { + cmd := GetCreateValidatorCmd(chain) + _, _, err := chain.Exec(ctx, cmd, nil) + require.NoError(t, err) + + // Wait a bit for the VSC packet to get relayed. + err = testutil.WaitForBlocks(ctx, 2, chain, counterparty) + require.NoError(t, err, "failed to wait for blocks") +} + +func LinkPath( + t *testing.T, + ctx context.Context, + r ibc.Relayer, + eRep *testreporter.RelayerExecReporter, + chainA ibc.Chain, + chainB ibc.Chain, + path string, +) { + err := r.LinkPath(ctx, eRep, path, ibc.DefaultChannelOpts(), ibc.DefaultClientOpts()) + require.NoError(t, err) + err = testutil.WaitForBlocks(ctx, 2, chainA, chainB) + require.NoError(t, err, "failed to wait for blocks") +} + +func GenerateClient( + t *testing.T, + ctx context.Context, + testCtx *TestContext, + r ibc.Relayer, + eRep *testreporter.RelayerExecReporter, + path string, + chainA ibc.Chain, + chainB ibc.Chain, +) (string, string) { + chainAClients := testCtx.GetChainClients(chainA.Config().Name) + chainBClients := testCtx.GetChainClients(chainB.Config().Name) + + err := r.CreateClients(ctx, eRep, path, ibc.CreateClientOptions{TrustingPeriod: "330h"}) + require.NoError(t, err) + err = testutil.WaitForBlocks(ctx, 2, chainA, chainB) + require.NoError(t, err, "failed to wait for blocks") + + newChainAClients, _ := r.GetClients(ctx, eRep, chainA.Config().ChainID) + newChainBClients, _ := r.GetClients(ctx, eRep, chainB.Config().ChainID) + var newClientA, newClientB string + + aClientDiff := ClientDifference(chainAClients, newChainAClients) + bClientDiff := ClientDifference(chainBClients, newChainBClients) + + if len(aClientDiff) > 0 { + newClientA = aClientDiff[0] + } else { + newClientA = "" + } + + if len(bClientDiff) > 0 { + newClientB = bClientDiff[0] + } else { + newClientB = "" + } + + testCtx.UpdateChainClients(chainA.Config().Name, newChainAClients) + testCtx.UpdateChainClients(chainB.Config().Name, newChainBClients) + + return newClientA, newClientB +} + +func GenerateConnections( + t *testing.T, + ctx context.Context, + testCtx *TestContext, + r ibc.Relayer, + eRep *testreporter.RelayerExecReporter, + path string, + chainA ibc.Chain, + chainB ibc.Chain, +) (string, string) { + chainAConns := testCtx.GetChainConnections(chainA.Config().Name) + chainBConns := testCtx.GetChainConnections(chainB.Config().Name) + + err := r.CreateConnections(ctx, eRep, path) + require.NoError(t, err) + err = testutil.WaitForBlocks(ctx, 2, chainA, chainB) + require.NoError(t, err, "failed to wait for blocks") + + newChainAConns, _ := r.GetConnections(ctx, eRep, chainA.Config().ChainID) + newChainBConns, _ := r.GetConnections(ctx, eRep, chainB.Config().ChainID) + + newChainAConnection := ConnectionDifference(chainAConns, newChainAConns) + newChainBConnection := ConnectionDifference(chainBConns, newChainBConns) + + require.NotEqual(t, 0, len(newChainAConnection), "more than one connection generated", strings.Join(newChainAConnection, " ")) + require.NotEqual(t, 0, len(newChainBConnection), "more than one connection generated", strings.Join(newChainBConnection, " ")) + + testCtx.UpdateChainConnections(chainA.Config().Name, newChainAConns) + testCtx.UpdateChainConnections(chainB.Config().Name, newChainBConns) + + return newChainAConnection[0], newChainBConnection[0] +} + +func ConnectionDifference(a, b []*ibc.ConnectionOutput) (diff []string) { + m := make(map[string]bool) + + // we first mark all existing connections + for _, item := range a { + m[item.ID] = true + } + + // and append all new ones + for _, item := range b { + if _, ok := m[item.ID]; !ok { + diff = append(diff, item.ID) + } + } + return +} + +func ClientDifference(a, b []*ibc.ClientOutput) (diff []string) { + m := make(map[string]bool) + + // we first mark all existing clients + for _, item := range a { + m[item.ClientID] = true + } + + // and append all new ones + for _, item := range b { + if _, ok := m[item.ClientID]; !ok { + diff = append(diff, item.ClientID) + } + } + return +} + +func PrintChannels(channels []ibc.ChannelOutput, chain string) { + for _, channel := range channels { + print("\n\n", chain, " channels after create channel :", channel.ChannelID, " to ", channel.Counterparty.ChannelID, "\n") + } +} + +func PrintConnections(connections ibc.ConnectionOutputs) { + for _, connection := range connections { + print(connection.ID, "\n") + } +} + +func ChannelDifference(oldChannels, newChannels []ibc.ChannelOutput) (diff []string) { + m := make(map[string]bool) + // we first mark all existing channels + for _, channel := range newChannels { + m[channel.ChannelID] = true + } + + // then find the new ones + for _, channel := range oldChannels { + if _, ok := m[channel.ChannelID]; !ok { + diff = append(diff, channel.ChannelID) + } + } + + return +} + +func GetPairwiseConnectionIds( + aconns ibc.ConnectionOutputs, + bconns ibc.ConnectionOutputs, +) ([]string, []string, error) { + abconnids := make([]string, 0) + baconnids := make([]string, 0) + found := false + for _, a := range aconns { + for _, b := range bconns { + if a.ClientID == b.Counterparty.ClientId && + b.ClientID == a.Counterparty.ClientId && + a.ID == b.Counterparty.ConnectionId && + b.ID == a.Counterparty.ConnectionId { + found = true + abconnids = append(abconnids, a.ID) + baconnids = append(baconnids, b.ID) + } + } + } + if found { + return abconnids, baconnids, nil + } else { + return abconnids, baconnids, errors.New("no connection found") + } +} + +// returns transfer channel ids +func GetPairwiseTransferChannelIds( + testCtx *TestContext, + achans []ibc.ChannelOutput, + bchans []ibc.ChannelOutput, + aToBConnId string, + bToAConnId string, + chainA string, + chainB string, +) (string, string) { + + for _, a := range achans { + for _, b := range bchans { + if a.ChannelID == b.Counterparty.ChannelID && + b.ChannelID == a.Counterparty.ChannelID && + a.PortID == "transfer" && + b.PortID == "transfer" && + a.Ordering == "ORDER_UNORDERED" && + b.Ordering == "ORDER_UNORDERED" && + a.ConnectionHops[0] == aToBConnId && + b.ConnectionHops[0] == bToAConnId { + testCtx.SetTransferChannelId(chainA, chainB, a.ChannelID) + testCtx.SetTransferChannelId(chainB, chainA, b.ChannelID) + return a.ChannelID, b.ChannelID + } + } + } + panic("failed to match pairwise transfer channels") +} + +// returns ccv channel ids +func GetPairwiseCCVChannelIds( + testCtx *TestContext, + achans []ibc.ChannelOutput, + bchans []ibc.ChannelOutput, + aToBConnId string, + bToAConnId string, + chainA string, + chainB string, +) (string, string) { + for _, a := range achans { + for _, b := range bchans { + if a.ChannelID == b.Counterparty.ChannelID && + b.ChannelID == a.Counterparty.ChannelID && + a.PortID == "provider" && + b.PortID == "consumer" && + a.Ordering == "ORDER_ORDERED" && + b.Ordering == "ORDER_ORDERED" && + a.ConnectionHops[0] == aToBConnId && + b.ConnectionHops[0] == bToAConnId { + testCtx.SetIcsChannelId(chainA, chainB, a.ChannelID) + testCtx.SetIcsChannelId(chainB, chainA, b.ChannelID) + return a.ChannelID, b.ChannelID + } + } + } + panic("failed to match pairwise ICS channels") +} + +type NativeBalQueryResponse struct { + Amount string `json:"amount"` + Denom string `json:"denom"` +} + +func (testCtx *TestContext) QueryNeutronDenomBalance(denom string, addr string) uint64 { + queryCmd := []string{"neutrond", "query", "bank", + "balances", addr, + "--denom", denom, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + } + var nativeBalanceResponse NativeBalQueryResponse + + queryResp, _, err := testCtx.Neutron.Exec(testCtx.Ctx, queryCmd, nil) + require.NoError(testCtx.T, err, "failed to query") + + require.NoError( + testCtx.T, + json.Unmarshal(queryResp, &nativeBalanceResponse), + "failed to unmarshal json", + ) + parsedBalance, err := strconv.ParseUint(nativeBalanceResponse.Amount, 10, 64) + require.NoError(testCtx.T, err, "failed to parse balance response to uint64") + return parsedBalance +} + +func (testCtx *TestContext) QueryHubDenomBalance(denom string, addr string) uint64 { + bal, err := testCtx.Hub.GetBalance(testCtx.Ctx, addr, denom) + require.NoError(testCtx.T, err, "failed to get hub denom balance") + + uintBal := uint64(bal) + // println(addr, " balance: (", denom, ",", uintBal, ")") + return uintBal +} + +func (testCtx *TestContext) QueryOsmoDenomBalance(denom string, addr string) uint64 { + bal, err := testCtx.Osmosis.GetBalance(testCtx.Ctx, addr, denom) + require.NoError(testCtx.T, err, "failed to get osmosis denom balance") + + uintBal := uint64(bal) + return uintBal +} + +func (testCtx *TestContext) QueryStrideDenomBalance(denom string, addr string) uint64 { + bal, err := testCtx.Stride.GetBalance(testCtx.Ctx, addr, denom) + require.NoError(testCtx.T, err, "failed to get stride denom balance") + + uintBal := uint64(bal) + return uintBal +} + +func (testCtx *TestContext) FundChainAddrs(addrs []string, chain *cosmos.CosmosChain, from *ibc.Wallet, amount int64) { + for i := 0; i < len(addrs); i++ { + err := chain.SendFunds(testCtx.Ctx, from.KeyName, ibc.WalletAmount{ + Address: addrs[i], + Denom: chain.Config().Denom, + Amount: int64(amount), + }) + require.NoError(testCtx.T, err, "failed to send funds to addr") + } +} + +func (testCtx *TestContext) QueryClockAddress(contract string) string { + var response CovenantAddressQueryResponse + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, ClockAddressQuery{}, &response) + require.NoError( + testCtx.T, + err, + "failed to query clock address", + ) + println("clock addr: ", response.Data) + return response.Data +} + +type ClockAddress struct{} +type ClockAddressQuery struct { + ClockAddress ClockAddress `json:"clock_address"` +} + +type HolderAddress struct{} +type HolderAddressQuery struct { + HolderAddress HolderAddress `json:"holder_address"` +} + +func (testCtx *TestContext) QueryHolderAddress(contract string) string { + var response CovenantAddressQueryResponse + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, HolderAddressQuery{}, &response) + require.NoError( + testCtx.T, + err, + "failed to query holder address", + ) + println("holder addr: ", response.Data) + return response.Data +} + +func (testCtx *TestContext) QueryIbcForwarderAddress(contract string, party string) string { + var response CovenantAddressQueryResponse + query := IbcForwarderQuery{ + Party: Party{ + Party: party, + }, + } + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, query, &response) + require.NoError( + testCtx.T, + err, + "failed to query ibc forwarder address", + ) + println(party, " forwarder addr: ", response.Data) + return response.Data +} + +func (testCtx *TestContext) QueryIbcForwarderTyAddress(contract string, ty string) string { + var response CovenantAddressQueryResponse + + type Type struct { + Type string `json:"ty"` + } + type IbcForwarderQuery struct { + Type Type `json:"ibc_forwarder_address"` + } + query := IbcForwarderQuery{ + Type: Type{ + Type: ty, + }, + } + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, query, &response) + require.NoError( + testCtx.T, + err, + "failed to query ibc forwarder address", + ) + println(ty, " forwarder addr: ", response.Data) + return response.Data +} + +func (testCtx *TestContext) QueryRemoteChainSplitterAddress(contract string) string { + var response CovenantAddressQueryResponse + + type SplitterAddress struct{} + type SplitterAddressQuery struct { + SplitterAddress SplitterAddress `json:"splitter_address"` + } + query := SplitterAddressQuery{} + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, query, &response) + require.NoError( + testCtx.T, + err, + "failed to query splitter address", + ) + println("splitter addr: ", response.Data) + return response.Data +} + +type Party struct { + Party string `json:"party"` +} +type InterchainRouterQuery struct { + Party Party `json:"interchain_router_address"` +} +type IbcForwarderQuery struct { + Party Party `json:"ibc_forwarder_address"` +} + +func (testCtx *TestContext) QueryInterchainRouterAddress(contract string, party string) string { + var response CovenantAddressQueryResponse + query := InterchainRouterQuery{ + Party: Party{ + Party: party, + }, + } + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, query, &response) + require.NoError( + testCtx.T, + err, + "failed to query interchain router address", + ) + println(party, " router addr: ", response.Data) + + return response.Data +} + +func (testCtx *TestContext) QuerySinglePartyInterchainRouterAddress(contract string) string { + var response CovenantAddressQueryResponse + + type RouterQuery struct{} + type SinglePartyInterchainRouterQuery struct { + RouterQuery RouterQuery `json:"interchain_router_address"` + } + query := SinglePartyInterchainRouterQuery{ + RouterQuery: RouterQuery{}, + } + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, query, &response) + require.NoError( + testCtx.T, + err, + "failed to query interchain router address", + ) + println("router addr: ", response.Data) + + return response.Data +} + +type SplitterAddress struct{} +type SplitterAddressQuery struct { + SplitterAddress SplitterAddress `json:"splitter_address"` +} + +func (testCtx *TestContext) QueryInterchainSplitterAddress(contract string) string { + var response CovenantAddressQueryResponse + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, SplitterAddressQuery{}, &response) + require.NoError( + testCtx.T, + err, + "failed to query interchain router address", + ) + println("splitter addr: ", response.Data) + + return response.Data +} + +func (testCtx *TestContext) StoreContract(chain *cosmos.CosmosChain, from *ibc.Wallet, path string) uint64 { + codeIdStr, err := chain.StoreContract(testCtx.Ctx, from.KeyName, path) + require.NoError(testCtx.T, err, "failed to store contract") + codeId, err := strconv.ParseUint(codeIdStr, 10, 64) + require.NoError(testCtx.T, err, "failed to parse codeId") + return codeId +} + +func (testCtx *TestContext) QueryContractState(contract string) string { + var response CovenantAddressQueryResponse + type ContractState struct{} + type ContractStateQuery struct { + ContractState ContractState `json:"contract_state"` + } + contractStateQuery := ContractStateQuery{ + ContractState: ContractState{}, + } + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, contractStateQuery, &response) + require.NoError( + testCtx.T, + err, + fmt.Sprintf("failed to query %s state", contract), + ) + return response.Data +} + +func (testCtx *TestContext) QueryProxyAddress(contract string) string { + var response CovenantAddressQueryResponse + type ProxyAddress struct{} + type ProxyAddressQuery struct { + ProxyAddress ProxyAddress `json:"proxy_address"` + } + proxyAddressQuery := ProxyAddressQuery{ + ProxyAddress: ProxyAddress{}, + } + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, proxyAddressQuery, &response) + require.NoError( + testCtx.T, + err, + fmt.Sprintf("failed to query %s state", contract), + ) + return response.Data +} + +type CovenantAddressQueryResponse struct { + Data string `json:"data"` +} + +func (testCtx *TestContext) QueryDepositAddress(covenant string, party string) string { + var depositAddressResponse CovenantAddressQueryResponse + + type PartyDepositAddress struct { + Party string `json:"party"` + } + type PartyDepositAddressQuery struct { + PartyDepositAddress PartyDepositAddress `json:"party_deposit_address"` + } + depositAddressQuery := PartyDepositAddressQuery{ + PartyDepositAddress: PartyDepositAddress{ + Party: party, + }, + } + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, covenant, depositAddressQuery, &depositAddressResponse) + require.NoError( + testCtx.T, + err, + fmt.Sprintf("failed to query %s deposit address", party), + ) + println(party, " deposit address: ", depositAddressResponse.Data) + return depositAddressResponse.Data +} + +func (testCtx *TestContext) QueryDepositAddressSingleParty(covenant string) string { + var depositAddressResponse CovenantAddressQueryResponse + + type PartyDepositAddress struct{} + type PartyDepositAddressQuery struct { + PartyDepositAddress PartyDepositAddress `json:"party_deposit_address"` + } + depositAddressQuery := PartyDepositAddressQuery{ + PartyDepositAddress: PartyDepositAddress{}, + } + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, covenant, depositAddressQuery, &depositAddressResponse) + require.NoError( + testCtx.T, + err, + "failed to query party deposit address", + ) + println("party deposit address: ", depositAddressResponse.Data) + return depositAddressResponse.Data +} + +func (testCtx *TestContext) QueryContractDepositAddress(contract string) string { + var depositAddressResponse CovenantAddressQueryResponse + + type DepositAddress struct{} + type DepositAddressQuery struct { + DepositAddress DepositAddress `json:"deposit_address"` + } + depositAddressQuery := DepositAddressQuery{ + DepositAddress: DepositAddress{}, + } + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, depositAddressQuery, &depositAddressResponse) + require.NoError( + testCtx.T, + err, + "failed to query contract deposit address", + ) + println("contract deposit address: ", depositAddressResponse.Data) + return depositAddressResponse.Data +} + +func (testCtx *TestContext) QueryContractICA(contract string) string { + var icaAddressResponse CovenantAddressQueryResponse + + type IcaAddress struct{} + type IcaAddressQuery struct { + IcaAddress IcaAddress `json:"ica_address"` + } + icaAddressQuery := IcaAddressQuery{ + IcaAddress: IcaAddress{}, + } + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, icaAddressQuery, &icaAddressResponse) + require.NoError( + testCtx.T, + err, + "failed to query contract ica address", + ) + println("contract deposit address: ", icaAddressResponse.Data) + return icaAddressResponse.Data +} + +func (testCtx *TestContext) ManualInstantiate(codeId uint64, msg any, from *ibc.Wallet, keyring string) string { + codeIdStr := strconv.FormatUint(codeId, 10) + + str, err := json.Marshal(msg) + require.NoError(testCtx.T, err, "Failed to marshall CovenantInstantiateMsg") + instantiateMsg := string(str) + + cmd := []string{"neutrond", "tx", "wasm", "instantiate", codeIdStr, + instantiateMsg, + "--label", fmt.Sprintf("covenant-%s", codeIdStr), + "--no-admin", + "--from", from.KeyName, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "900090000", + "--keyring-backend", keyring, + "-y", + } + + prettyJson, _ := json.MarshalIndent(msg, "", " ") + println("covenant instantiation message:") + fmt.Println(string(prettyJson)) + + covInstantiationResp, _, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + println("covenant instantiation response: ", string(covInstantiationResp)) + + testCtx.SkipBlocks(5) + if err != nil { + println("manual instantiation failed") + } + + testCtx.SkipBlocks(5) + + queryCmd := []string{"neutrond", "query", "wasm", + "list-contract-by-code", codeIdStr, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + } + + queryResp, _, err := testCtx.Neutron.Exec(testCtx.Ctx, queryCmd, nil) + println("query response: ", string(queryResp)) + require.NoError(testCtx.T, err, "failed to query") + + type QueryContractResponse struct { + Contracts []string `json:"contracts"` + Pagination any `json:"pagination"` + } + + contactsRes := QueryContractResponse{} + require.NoError(testCtx.T, json.Unmarshal(queryResp, &contactsRes), "failed to unmarshal contract response") + + covenantAddress := contactsRes.Contracts[len(contactsRes.Contracts)-1] + + return covenantAddress +} + +func (testCtx *TestContext) ManualInstantiateLS(codeId uint64, msg any, from *ibc.Wallet, keyring string) string { + codeIdStr := strconv.FormatUint(codeId, 10) + + str, err := json.Marshal(msg) + require.NoError(testCtx.T, err, "Failed to marshall CovenantInstantiateMsg") + instantiateMsg := string(str) + + cmd := []string{"neutrond", "tx", "wasm", "instantiate", codeIdStr, + instantiateMsg, + "--label", fmt.Sprintf("covenant-%s", codeIdStr), + "--no-admin", + "--from", from.KeyName, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "900090000", + "--keyring-backend", keyring, + "-y", + } + + prettyJson, _ := json.MarshalIndent(msg, "", " ") + println("covenant instantiation message:") + fmt.Println(string(prettyJson)) + + covInstantiationResp, _, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + // require.NoError(testCtx.T, err, "manual instantiation failed") + println("covenant instantiation response: ", string(covInstantiationResp)) + if err != nil { + println("failed to instantiate covenant") + } + require.NoError(testCtx.T, + testutil.WaitForBlocks(testCtx.Ctx, 5, testCtx.Hub, testCtx.Neutron, testCtx.Stride)) + + queryCmd := []string{"neutrond", "query", "wasm", + "list-contract-by-code", codeIdStr, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + } + + queryResp, _, err := testCtx.Neutron.Exec(testCtx.Ctx, queryCmd, nil) + require.NoError(testCtx.T, err, "failed to query") + + type QueryContractResponse struct { + Contracts []string `json:"contracts"` + Pagination any `json:"pagination"` + } + + contactsRes := QueryContractResponse{} + require.NoError(testCtx.T, json.Unmarshal(queryResp, &contactsRes), "failed to unmarshal contract response") + + covenantAddress := contactsRes.Contracts[len(contactsRes.Contracts)-1] + + return covenantAddress +} + +// astroport whitelist +type WhitelistInstantiateMsg struct { + Admins []string `json:"admins"` + Mutable bool `json:"mutable"` +} + +type ProvideLiqudityMsg struct { + ProvideLiquidity ProvideLiquidityStruct `json:"provide_liquidity"` +} + +type ProvideLiquidityStruct struct { + Assets []AstroportAsset `json:"assets"` + SlippageTolerance string `json:"slippage_tolerance"` + AutoStake bool `json:"auto_stake"` + Receiver string `json:"receiver"` +} + +// factory + +type FactoryPairResponse struct { + Data PairInfo `json:"data"` +} + +type LpPositionQueryResponse struct { + Data string `json:"data"` +} + +type AstroportAsset struct { + Info AssetInfo `json:"info"` + Amount string `json:"amount"` +} + +type LpPositionQuery struct{} + +type PairInfo struct { + LiquidityToken string `json:"liquidity_token"` + ContractAddr string `json:"contract_addr"` + PairType PairType `json:"pair_type"` + AssetInfos []AssetInfo `json:"asset_infos"` +} + +type AssetInfo struct { + Token *Token `json:"token,omitempty"` + NativeToken *NativeToken `json:"native_token,omitempty"` +} + +type StablePoolParams struct { + Amp uint64 `json:"amp"` + Owner *string `json:"owner"` +} + +type Token struct { + ContractAddr string `json:"contract_addr"` +} + +type NativeToken struct { + Denom string `json:"denom"` +} + +type CwCoin struct { + Denom string `json:"denom"` + Amount uint64 `json:"amount"` +} + +type LPPositionQuery struct { + LpPosition LpPositionQuery `json:"lp_position"` +} + +type Pair struct { + AssetInfos []AssetInfo `json:"asset_infos"` +} + +type PairQuery struct { + Pair Pair `json:"pair"` +} + +// astroport factory +type FactoryInstantiateMsg struct { + PairConfigs []PairConfig `json:"pair_configs"` + TokenCodeId uint64 `json:"token_code_id"` + FeeAddress *string `json:"fee_address"` + GeneratorAddress *string `json:"generator_address"` + Owner string `json:"owner"` + WhitelistCodeId uint64 `json:"whitelist_code_id"` + CoinRegistryAddress string `json:"coin_registry_address"` +} + +type PairConfig struct { + CodeId uint64 `json:"code_id"` + PairType PairType `json:"pair_type"` + TotalFeeBps uint64 `json:"total_fee_bps"` + MakerFeeBps uint64 `json:"maker_fee_bps"` + IsDisabled bool `json:"is_disabled"` + IsGeneratorDisabled bool `json:"is_generator_disabled"` +} + +type PairType struct { + // Xyk struct{} `json:"xyk,omitempty"` + Stable struct{} `json:"stable,omitempty"` + // Custom struct{} `json:"custom,omitempty"` +} + +func (testCtx *TestContext) InstantiateOsmoOutpost(outpostCode uint64, from *ibc.Wallet) string { + outpostAddress, err := testCtx.Osmosis.InstantiateContract( + testCtx.Ctx, + from.KeyName, + strconv.FormatUint(outpostCode, 10), + "{}", + true, + ) + require.NoError(testCtx.T, err, "Failed to instantiate outpost") + return outpostAddress +} + +func (testCtx *TestContext) InstantiateAstroportFactory(pairCodeId uint64, tokenCodeId uint64, whitelistCodeId uint64, factoryCodeId uint64, coinRegistryAddr string, from *ibc.Wallet) string { + msg := FactoryInstantiateMsg{ + PairConfigs: []PairConfig{ + { + CodeId: pairCodeId, + PairType: PairType{ + Stable: struct{}{}, + }, + TotalFeeBps: 0, + MakerFeeBps: 0, + IsDisabled: false, + IsGeneratorDisabled: true, + }, + }, + TokenCodeId: tokenCodeId, + FeeAddress: nil, + GeneratorAddress: nil, + Owner: from.Bech32Address(testCtx.Neutron.Config().Bech32Prefix), + WhitelistCodeId: whitelistCodeId, + CoinRegistryAddress: coinRegistryAddr, + } + + str, _ := json.Marshal(msg) + factoryAddr, err := testCtx.Neutron.InstantiateContract( + testCtx.Ctx, from.KeyName, strconv.FormatUint(factoryCodeId, 10), string(str), true) + require.NoError(testCtx.T, err, "Failed to instantiate Factory") + + return factoryAddr +} + +func (testCtx *TestContext) QueryAstroLpTokenAndStableswapAddress( + factoryAddress string, denom1 string, denom2 string, +) (string, string) { + pairQueryMsg := PairQuery{ + Pair: Pair{ + AssetInfos: []AssetInfo{ + { + NativeToken: &NativeToken{ + Denom: denom1, + }, + }, + { + NativeToken: &NativeToken{ + Denom: denom2, + }, + }, + }, + }, + } + queryJson, _ := json.Marshal(pairQueryMsg) + queryCmd := []string{"neutrond", "query", "wasm", "contract-state", "smart", + factoryAddress, string(queryJson), + } + + factoryQueryRespBytes, _, _ := testCtx.Neutron.Exec(testCtx.Ctx, queryCmd, nil) + print(string(factoryQueryRespBytes)) + + var response FactoryPairResponse + err := testCtx.Neutron.QueryContract(testCtx.Ctx, factoryAddress, pairQueryMsg, &response) + require.NoError(testCtx.T, err, "failed to query pair info") + + stableswapAddress := response.Data.ContractAddr + print("\n stableswap address: ", stableswapAddress, "\n") + liquidityTokenAddress := response.Data.LiquidityToken + print("\n liquidity token: ", liquidityTokenAddress, "\n") + + return liquidityTokenAddress, stableswapAddress +} + +func (testCtx *TestContext) ProvideAstroportLiquidity(denom1 string, denom2 string, amount1 uint64, amount2 uint64, from *ibc.Wallet, pool string) { + + msg := ProvideLiqudityMsg{ + ProvideLiquidity: ProvideLiquidityStruct{ + Assets: []AstroportAsset{ + { + Info: AssetInfo{ + NativeToken: &NativeToken{ + Denom: denom1, + }, + }, + Amount: strconv.FormatUint(amount1, 10), + }, + { + Info: AssetInfo{ + NativeToken: &NativeToken{ + Denom: denom2, + }, + }, + Amount: strconv.FormatUint(amount2, 10), + }, + }, + SlippageTolerance: "0.01", + AutoStake: false, + Receiver: from.Bech32Address(testCtx.Neutron.Config().Bech32Prefix), + }, + } + + str, err := json.Marshal(msg) + require.NoError(testCtx.T, err, "Failed to marshall provide liquidity msg") + amountStr := strconv.FormatUint(amount1, 10) + denom1 + "," + strconv.FormatUint(amount2, 10) + denom2 + + cmd := []string{"neutrond", "tx", "wasm", "execute", pool, + string(str), + "--from", from.KeyName, + "--amount", amountStr, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "900000", + "--keyring-backend", keyring.BackendTest, + "-y", + } + println("liq provision msg: \n ", strings.Join(cmd, " "), "\n") + + _, _, err = testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + require.NoError(testCtx.T, err) +} + +type CreatePair struct { + PairType PairType `json:"pair_type"` + AssetInfos []AssetInfo `json:"asset_infos"` + InitParams []byte `json:"init_params"` +} + +type CreatePairMsg struct { + CreatePair CreatePair `json:"create_pair"` +} + +type BalanceResponse struct { + Balance string `json:"balance"` +} + +type Cw20BalanceResponse struct { + Data BalanceResponse `json:"data"` +} + +func (testCtx *TestContext) CreateAstroportFactoryPair(amp uint64, denom1 string, denom2 string, factory string, from *ibc.Wallet, keyring string) { + initParams := StablePoolParams{ + Amp: amp, + } + binaryData, _ := json.Marshal(initParams) + + createPairMsg := CreatePairMsg{ + CreatePair: CreatePair{ + PairType: PairType{ + Stable: struct{}{}, + }, + AssetInfos: []AssetInfo{ + { + NativeToken: &NativeToken{ + Denom: denom1, + }, + }, + { + NativeToken: &NativeToken{ + Denom: denom2, + }, + }, + }, + InitParams: binaryData, + }, + } + + str, _ := json.Marshal(createPairMsg) + + createCmd := []string{"neutrond", "tx", "wasm", "execute", + factory, + string(str), + "--gas-prices", "0.0untrn", + "--gas-adjustment", `1.5`, + "--output", "json", + "--node", testCtx.Neutron.GetRPCAddress(), + "--home", testCtx.Neutron.HomeDir(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--from", from.KeyName, + "--gas", "auto", + "--keyring-backend", keyring, + "-y", + } + + _, _, err := testCtx.Neutron.Exec(testCtx.Ctx, createCmd, nil) + require.NoError(testCtx.T, err, err) + testCtx.SkipBlocks(3) +} + +func (testCtx *TestContext) CreateAstroportFactoryPairStride(amp uint64, denom1 string, denom2 string, factory string, from *ibc.Wallet, keyring string) { + initParams := StablePoolParams{ + Amp: amp, + } + binaryData, _ := json.Marshal(initParams) + + createPairMsg := CreatePairMsg{ + CreatePair: CreatePair{ + PairType: PairType{ + Stable: struct{}{}, + }, + AssetInfos: []AssetInfo{ + { + NativeToken: &NativeToken{ + Denom: denom1, + }, + }, + { + NativeToken: &NativeToken{ + Denom: denom2, + }, + }, + }, + InitParams: binaryData, + }, + } + + str, _ := json.Marshal(createPairMsg) + + createCmd := []string{"neutrond", "tx", "wasm", "execute", + factory, + string(str), + "--gas-prices", "0.0untrn", + "--gas-adjustment", `1.5`, + "--output", "json", + "--node", testCtx.Neutron.GetRPCAddress(), + "--home", testCtx.Neutron.HomeDir(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--from", from.KeyName, + "--gas", "auto", + "--keyring-backend", keyring, + "-y", + } + + _, _, err := testCtx.Neutron.Exec(testCtx.Ctx, createCmd, nil) + require.NoError(testCtx.T, err, err) + testCtx.SkipBlocksStride(3) +} + +func (testCtx *TestContext) QueryLpTokenBalance(token string, addr string) uint64 { + bal := Balance{ + Address: addr, + } + + balanceQueryMsg := Cw20QueryMsg{ + Balance: bal, + } + var response Cw20BalanceResponse + require.NoError( + testCtx.T, + testCtx.Neutron.QueryContract(testCtx.Ctx, token, balanceQueryMsg, &response), + "failed to query lp token balance", + ) + balString := response.Data.Balance + + lpBal, err := strconv.ParseUint(balString, 10, 64) + if err != nil { + panic(err) + } + println(addr, " lp token balance: ", lpBal) + return lpBal +} + +type AllAccountsResponse struct { + Data []string `json:"all_accounts_response"` +} + +type Cw20QueryMsg struct { + Balance Balance `json:"balance"` + // AllAccounts *AllAccounts `json:"all_accounts"` +} + +type AllAccounts struct { +} + +type Balance struct { + Address string `json:"address"` +} + +func (testCtx *TestContext) GetNeutronHeight() uint64 { + currentHeight, err := testCtx.Neutron.Height(testCtx.Ctx) + require.NoError(testCtx.T, err, "failed to get neutron height") + println("neutron height: ", currentHeight) + return currentHeight +} + +type LiquidPoolerAddress struct{} +type LiquidPoolerQuery struct { + LiquidPoolerAddress LiquidPoolerAddress `json:"liquid_pooler_address"` +} + +func (testCtx *TestContext) QueryLiquidPoolerAddress(contract string) string { + var response CovenantAddressQueryResponse + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, LiquidPoolerQuery{}, &response) + require.NoError( + testCtx.T, + err, + "failed to query liquid pooler address", + ) + println("liquid pooler address: ", response.Data) + return response.Data +} + +func (testCtx *TestContext) QueryLiquidStakerAddress(contract string) string { + var response CovenantAddressQueryResponse + type LiquidStakerAddress struct{} + type LiquidStakerQuery struct { + LiquidStakerAddress LiquidStakerAddress `json:"liquid_staker_address"` + } + + err := testCtx.Neutron.QueryContract(testCtx.Ctx, contract, LiquidStakerQuery{}, &response) + require.NoError( + testCtx.T, + err, + "failed to query liquid staker address", + ) + println("liquid staker address: ", response.Data) + return response.Data +} + +func (testCtx *TestContext) HolderClaim(contract string, from *ibc.Wallet, keyring string) { + + cmd := []string{"neutrond", "tx", "wasm", "execute", contract, + `{"claim":{}}`, + "--from", from.GetKeyName(), + "--gas-prices", "0.0untrn", + "--gas-adjustment", `1.5`, + "--output", "json", + "--node", testCtx.Neutron.GetRPCAddress(), + "--home", testCtx.Neutron.HomeDir(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "42069420", + "--keyring-backend", keyring, + "-y", + } + + resp, _, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + if err != nil { + println("claim failed: ", err) + } + println("claim response: ", string(resp)) + testCtx.SkipBlocks(3) +} + +func (testCtx *TestContext) HolderRagequit(contract string, from *ibc.Wallet, keyring string) { + + cmd := []string{"neutrond", "tx", "wasm", "execute", contract, + `{"ragequit":{}}`, + "--from", from.GetKeyName(), + "--gas-prices", "0.0untrn", + "--gas-adjustment", `1.5`, + "--output", "json", + "--node", testCtx.Neutron.GetRPCAddress(), + "--home", testCtx.Neutron.HomeDir(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "42069420", + "--keyring-backend", keyring, + "-y", + } + + _, _, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + require.NoError(testCtx.T, err, "ragequit failed") +} + +// polytone types +type PolytonePair struct { + ConnectionId string `json:"connection_id"` + RemotePort string `json:"remote_port"` +} + +type NoteInstantiate struct { + Pair *PolytonePair `json:"pair,omitempty"` + BlockMaxGas string `json:"block_max_gas,omitempty"` +} + +type VoiceInstantiate struct { + ProxyCodeId uint64 `json:"proxy_code_id,string"` + BlockMaxGas uint64 `json:"block_max_gas,string"` +} + +type CallbackRequest struct { + Receiver string `json:"receiver"` + Msg string `json:"msg"` +} + +type CallbackMessage struct { + Initiator string `json:"initiator"` + InitiatorMsg string `json:"initiator_msg"` + Result Callback `json:"result"` +} + +type Callback struct { + Success []string `json:"success,omitempty"` + Error string `json:"error,omitempty"` +} + +func (testCtx *TestContext) InstantiateCmdExecNeutron(codeId uint64, label string, msg any, from *ibc.Wallet, keyring string) string { + codeIdStr := strconv.FormatUint(codeId, 10) + + instantiateJson, err := json.Marshal(msg) + require.NoError(testCtx.T, err, err) + + cmd := []string{"neutrond", "tx", "wasm", "instantiate", codeIdStr, + string(instantiateJson), + "--label", label, + "--no-admin", + "--from", from.KeyName, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "900090000", + "--keyring-backend", keyring, + "-y", + } + stdout, _, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + require.NoError(testCtx.T, err, "manual instantiation failed") + testCtx.SkipBlocks(5) + println("manual instantiation response: ", string(stdout)) + + queryCmd := []string{"neutrond", "query", "wasm", + "list-contract-by-code", codeIdStr, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + } + + queryResp, _, err := testCtx.Neutron.Exec(testCtx.Ctx, queryCmd, nil) + require.NoError(testCtx.T, err, "failed to query") + + type QueryContractResponse struct { + Contracts []string `json:"contracts"` + Pagination any `json:"pagination"` + } + + contactsRes := QueryContractResponse{} + require.NoError(testCtx.T, json.Unmarshal(queryResp, &contactsRes), "failed to unmarshal contract response") + + instantiatedAddress := contactsRes.Contracts[len(contactsRes.Contracts)-1] + + return instantiatedAddress +} + +func (testCtx *TestContext) ManualExecNeutron(contract string, msg any, from *ibc.Wallet, keyring string) { + + executeJson, err := json.Marshal(msg) + require.NoError(testCtx.T, err, err) + + cmd := []string{"neutrond", "tx", "wasm", "execute", contract, + string(executeJson), + "--from", from.KeyName, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "900090000", + "--fees", "500001untrn", + "--keyring-backend", keyring, + "-y", + } + stdout, _, err := testCtx.Neutron.Exec(testCtx.Ctx, cmd, nil) + require.NoError(testCtx.T, err, "manual execution failed") + testCtx.SkipBlocks(5) + println("manual execution response: ", string(stdout)) +} + +func (testCtx *TestContext) InstantiateCmdExecOsmo(codeId uint64, label string, msg any, from *ibc.Wallet, keyring string) string { + codeIdStr := strconv.FormatUint(codeId, 10) + + instantiateJson, err := json.Marshal(msg) + require.NoError(testCtx.T, err, err) + + cmd := []string{"osmosisd", "tx", "wasm", "instantiate", codeIdStr, + string(instantiateJson), + "--label", label, + "--no-admin", + "--from", from.KeyName, + "--output", "json", + "--home", testCtx.Osmosis.HomeDir(), + "--node", testCtx.Osmosis.GetRPCAddress(), + "--chain-id", testCtx.Osmosis.Config().ChainID, + "--gas", "25000000", + "--fees", "500000uosmo", + "--keyring-backend", keyring, + "-y", + } + stdout, _, err := testCtx.Osmosis.Exec(testCtx.Ctx, cmd, nil) + require.NoError(testCtx.T, err, "manual instantiation failed") + testCtx.SkipBlocks(5) + println("manual instantiation response: ", string(stdout)) + + queryCmd := []string{"osmosisd", "query", "wasm", + "list-contract-by-code", codeIdStr, + "--output", "json", + "--home", testCtx.Osmosis.HomeDir(), + "--node", testCtx.Osmosis.GetRPCAddress(), + "--chain-id", testCtx.Osmosis.Config().ChainID, + } + + queryResp, _, err := testCtx.Osmosis.Exec(testCtx.Ctx, queryCmd, nil) + require.NoError(testCtx.T, err, "failed to query") + + type QueryContractResponse struct { + Contracts []string `json:"contracts"` + Pagination any `json:"pagination"` + } + + contactsRes := QueryContractResponse{} + require.NoError(testCtx.T, json.Unmarshal(queryResp, &contactsRes), "failed to unmarshal contract response") + + instantiatedAddress := contactsRes.Contracts[len(contactsRes.Contracts)-1] + + return instantiatedAddress +} + +func (testCtx *TestContext) GetPermisionlessLsTransferMessage(amount uint64, liquidStakerAddress string, user *ibc.Wallet, keyring string) []string { + type TransferAmount struct { + Amount uint64 `json:"amount,string"` + } + + type TransferExecutionMsg struct { + Transfer TransferAmount `json:"transfer"` + } + + // Construct a transfer message + msg := TransferExecutionMsg{ + Transfer: TransferAmount{ + Amount: amount, + }, + } + transferMsgJson, err := json.Marshal(msg) + require.NoError(testCtx.T, err) + + // transfer command for permissionless transfer from stride ica to lper + transferCmd := []string{"neutrond", "tx", "wasm", "execute", liquidStakerAddress, + string(transferMsgJson), + "--from", user.KeyName, + "--gas-prices", "0.0untrn", + "--gas-adjustment", `1.8`, + "--output", "json", + "--home", testCtx.Neutron.HomeDir(), + "--node", testCtx.Neutron.GetRPCAddress(), + "--chain-id", testCtx.Neutron.Config().ChainID, + "--gas", "42069420", + "--keyring-backend", keyring, + "-y", + } + return transferCmd +} diff --git a/interchaintest/utils/genesis_helpers.go b/interchaintest/utils/genesis_helpers.go new file mode 100644 index 00000000..5095cce6 --- /dev/null +++ b/interchaintest/utils/genesis_helpers.go @@ -0,0 +1,313 @@ +package utils + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/icza/dyno" + "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v4/ibc" + "github.com/strangelove-ventures/interchaintest/v4/testreporter" +) + +// Sets custom fields for the Neutron genesis file that interchaintest isn't aware of by default. +// +// soft_opt_out_threshold - the bottom `soft_opt_out_threshold` +// percentage of validators may opt out of running a Neutron +// node [^1]. +// +// reward_denoms - the reward denominations allowed to be sent to the +// provider (atom) from the consumer (neutron) [^2]. +// +// provider_reward_denoms - the reward denominations allowed to be +// sent to the consumer by the provider [^2]. +// +// [^1]: https://docs.neutron.org/neutron/consumer-chain-launch#relevant-parameters +// [^2]: https://github.com/cosmos/interchain-security/blob/54e9852d3c89a2513cd0170a56c6eec894fc878d/proto/interchain_security/ccv/consumer/v1/consumer.proto#L61-L66 +func SetupNeutronGenesis( + soft_opt_out_threshold string, + reward_denoms []string, + provider_reward_denoms []string, + allowed_messages []string) func(ibc.ChainConfig, []byte) ([]byte, error) { + return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) { + g := make(map[string]interface{}) + if err := json.Unmarshal(genbz, &g); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis file: %w", err) + } + + if err := dyno.Set(g, soft_opt_out_threshold, "app_state", "ccvconsumer", "params", "soft_opt_out_threshold"); err != nil { + return nil, fmt.Errorf("failed to set soft_opt_out_threshold in genesis json: %w", err) + } + + if err := dyno.Set(g, reward_denoms, "app_state", "ccvconsumer", "params", "reward_denoms"); err != nil { + return nil, fmt.Errorf("failed to set reward_denoms in genesis json: %w", err) + } + + if err := dyno.Set(g, provider_reward_denoms, "app_state", "ccvconsumer", "params", "provider_reward_denoms"); err != nil { + return nil, fmt.Errorf("failed to set provider_reward_denoms in genesis json: %w", err) + } + + if err := dyno.Set(g, allowed_messages, "app_state", "interchainaccounts", "host_genesis_state", "params", "allow_messages"); err != nil { + return nil, fmt.Errorf("failed to set allow_messages for interchainaccount host in genesis json: %w", err) + } + + if err := dyno.Set(g, "1000000000", "consensus_params", "block", "max_gas"); err != nil { + return nil, fmt.Errorf("failed to set block max gas: %w", err) + } + + minGasEntries := []interface{}{ + map[string]string{"denom": "untrn", "amount": "0"}, + } + + if err := dyno.Set(g, minGasEntries, "app_state", "globalfee", "params", "minimum_gas_prices"); err != nil { + return nil, fmt.Errorf("failed to set min gas entries in genesis json: %w", err) + } + + if err := dyno.Set(g, "neutron1ctnjk7an90lz5wjfvr3cf6x984a8cjnv8dpmztmlpcq4xteaa2xsj6vhez", "app_state", "tokenfactory", "params", "fee_collector_address"); err != nil { + return nil, fmt.Errorf("failed to set fee_collector_address in genesis json: %w", err) + } + + if err := dyno.Set(g, "neutron1ctnjk7an90lz5wjfvr3cf6x984a8cjnv8dpmztmlpcq4xteaa2xsj6vhez", "app_state", "feeburner", "params", "treasury_address"); err != nil { + return nil, fmt.Errorf("failed to set treasury_address in genesis json: %w", err) + } + + out, err := json.Marshal(g) + // println("neutron genesis:") + // print(string(out)) + if err != nil { + return nil, fmt.Errorf("failed to marshal genesis bytes to json: %w", err) + } + return out, nil + } +} + +// Sets custom fields for the Gaia genesis file that interchaintest isn't aware of by default. +// +// allowed_messages - explicitly allowed messages to be accepted by the the interchainaccounts section +func SetupGaiaGenesis(allowed_messages []string) func(ibc.ChainConfig, []byte) ([]byte, error) { + return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) { + g := make(map[string]interface{}) + if err := json.Unmarshal(genbz, &g); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis file: %w", err) + } + + if err := dyno.Set(g, allowed_messages, "app_state", "interchainaccounts", "host_genesis_state", "params", "allow_messages"); err != nil { + return nil, fmt.Errorf("failed to set allow_messages for interchainaccount host in genesis json: %w", err) + } + + out, err := json.Marshal(g) + if err != nil { + return nil, fmt.Errorf("failed to marshal genesis bytes to json: %w", err) + } + return out, nil + } +} + +func GetDefaultInterchainGenesisMessages() []string { + return []string{ + "/cosmos.bank.v1beta1.MsgSend", + "/cosmos.bank.v1beta1.MsgMultiSend", + "/cosmos.staking.v1beta1.MsgDelegate", + "/cosmos.staking.v1beta1.MsgUndelegate", + "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "/cosmos.staking.v1beta1.MsgRedeemTokensforShares", + "/cosmos.staking.v1beta1.MsgTokenizeShares", + "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress", + "/ibc.applications.transfer.v1.MsgTransfer", + } +} + +func GetDefaultNeutronInterchainGenesisMessages() []string { + return []string{ + "/cosmos.bank.v1beta1.MsgSend", + "/cosmos.bank.v1beta1.MsgMultiSend", + "/cosmos.staking.v1beta1.MsgDelegate", + "/cosmos.staking.v1beta1.MsgUndelegate", + "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "/cosmos.staking.v1beta1.MsgRedeemTokensforShares", + "/cosmos.staking.v1beta1.MsgTokenizeShares", + "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress", + "/ibc.applications.transfer.v1.MsgTransfer", + "/ibc.lightclients.localhost.v2.ClientState", + "/ibc.core.client.v1.MsgCreateClient", + "/ibc.core.client.v1.Query/ClientState", + "/ibc.core.client.v1.Query/ConsensusState", + "/ibc.core.connection.v1.Query/Connection", + } +} + +func SetupOsmoGenesis(allowed_messages []string) func(ibc.ChainConfig, []byte) ([]byte, error) { + return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) { + g := make(map[string]interface{}) + if err := json.Unmarshal(genbz, &g); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis file: %w", err) + } + + missingFields := map[string]interface{}{ + "active_channels": []interface{}{}, + "interchain_accounts": []interface{}{}, + "port": "icahost", + "params": map[string]interface{}{ + "host_enabled": true, + "allow_messages": []interface{}{}, + }, + } + if g["app_state"].(map[string]interface{})["interchainaccounts"] == nil { + g["app_state"].(map[string]interface{})["interchainaccounts"] = make(map[string]interface{}) + } + + if g["app_state"].(map[string]interface{})["interchainaccounts"].(map[string]interface{})["host_genesis_state"] == nil { + g["app_state"].(map[string]interface{})["interchainaccounts"].(map[string]interface{})["host_genesis_state"] = make(map[string]interface{}) + } + + if err := dyno.Set(g, missingFields, "app_state", "interchainaccounts", "host_genesis_state"); err != nil { + return nil, fmt.Errorf("failed to set interchainaccounts for app_state in genesis json: %w. \ngenesis json: %s", err, g) + } + + if err := dyno.Set(g, allowed_messages, "app_state", "interchainaccounts", "host_genesis_state", "params", "allow_messages"); err != nil { + return nil, fmt.Errorf("failed to set allow_messages for interchainaccount host in genesis json: %w. \ngenesis json: %s", err, g) + } + + // type Fee struct { + // Amount string `json:"amount"` + // Denom string `json:"denom"` + // } + // zeroCreationFee := []Fee{ + // { + // Amount: "10", + // Denom: "uosmo", + // }, + // } + + // if err := dyno.Set(g, zeroCreationFee, "app_state", "gamm", "params", "pool_creation_fee"); err != nil { + // return nil, fmt.Errorf("failed to set poolmanager pool creation fee") + // } + + // // Retrieve tokenfactory params map + // // tokenfactoryParams, err := dyno.Get(g, "app_state", "tokenfactory", "params") + // // if err != nil { + // // return nil, fmt.Errorf("failed to get params for tokenfactory: %w", err) + // // } + + // // // Assert the type of the params to be map[string]interface{} + // // tokenfactoryParamsMap, ok := tokenfactoryParams.(map[string]interface{}) + // // if !ok { + // // return nil, fmt.Errorf("params for tokenfactory is not a map") + // // } + + // // // Update only the denom_creation_gas_consume field + // // tokenfactoryParamsMap["denom_creation_gas_consume"] = "1" + + // // // Set the modified params map back + // // if err := dyno.Set(g, tokenfactoryParamsMap, "app_state", "tokenfactory", "params"); err != nil { + // // return nil, fmt.Errorf("failed to set modified params for tokenfactory: %w", err) + // // } + + // // genutil gas_limits + // // Retrieve the gen_txs array + // genTxs, err := dyno.Get(g, "app_state", "genutil", "gen_txs") + // if err != nil { + // return nil, fmt.Errorf("failed to get gen_txs for genutil: %w", err) + // } + + // genTxsSlice, ok := genTxs.([]interface{}) + // if !ok { + // return nil, fmt.Errorf("gen_txs in genutil is not a slice") + // } + + // // Update the gas_limit for each item in the slice + // for _, genTx := range genTxsSlice { + // genTxMap, ok := genTx.(map[string]interface{}) + // if !ok { + // return nil, fmt.Errorf("gen_tx item is not a map") + // } + + // if authInfo, ok := genTxMap["auth_info"].(map[string]interface{}); ok { + // if fee, ok := authInfo["fee"].(map[string]interface{}); ok { + // fee["gas_limit"] = "350000" + // } + // } + // } + + // // Set the modified gen_txs slice back + // if err := dyno.Set(g, genTxsSlice, "app_state", "genutil", "gen_txs"); err != nil { + // return nil, fmt.Errorf("failed to set modified gen_txs for genutil: %w", err) + // } + + // if err := dyno.Set(g, "100000000", "consensus_params", "block", "max_gas"); err != nil { + // return nil, fmt.Errorf("failed to set block max gas: %w", err) + // } + + out, err := json.Marshal(g) + println("osmo genesis:") + print(string(out)) + if err != nil { + return nil, fmt.Errorf("failed to marshal genesis bytes to json: %w", err) + } + return out, nil + } +} + +func SetupStrideGenesis(allowed_messages []string) func(ibc.ChainConfig, []byte) ([]byte, error) { + return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) { + g := make(map[string]interface{}) + if err := json.Unmarshal(genbz, &g); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis file: %w", err) + } + + if err := dyno.Set(g, true, "app_state", "autopilot", "params", "stakeibc_active"); err != nil { + return nil, fmt.Errorf("failed to set autopilot stakeibc in genesis json: %w", err) + } + + if err := dyno.Set(g, allowed_messages, "app_state", "interchainaccounts", "host_genesis_state", "params", "allow_messages"); err != nil { + return nil, fmt.Errorf("failed to set allow_messages for interchainaccount host in genesis json: %w", err) + } + + out, err := json.Marshal(g) + if err != nil { + return nil, fmt.Errorf("failed to marshal genesis bytes to json: %w", err) + } + + return out, nil + } +} + +func GetCreateValidatorCmd(chain ibc.Chain) []string { + // Before receiving a validator set change (VSC) packet, + // consumer chains disallow bank transfers. To trigger a VSC + // packet, this creates a validator (from a random public key) + // that will never do anything, triggering a VSC + // packet. Eventually this validator will become jailed, + // triggering another one. + cmd := []string{"gaiad", "tx", "staking", "create-validator", + "--amount", "1000000uatom", + "--pubkey", `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"qwrYHaJ7sNHfYBR1nzDr851+wT4ed6p8BbwTeVhaHoA="}`, + "--moniker", "a", + "--commission-rate", "0.1", + "--commission-max-rate", "0.2", + "--commission-max-change-rate", "0.01", + "--min-self-delegation", "1000000", + "--node", chain.GetRPCAddress(), + "--home", chain.HomeDir(), + "--chain-id", chain.Config().ChainID, + "--from", "faucet", + "--fees", "20000uatom", + "--keyring-backend", keyring.BackendTest, + "-y", + } + + return cmd +} + +func GetChannelMap(r ibc.Relayer, ctx context.Context, eRep *testreporter.RelayerExecReporter, + cosmosStride *cosmos.CosmosChain, cosmosNeutron *cosmos.CosmosChain, cosmosAtom *cosmos.CosmosChain) map[string]string { + channelMap := map[string]string{ + "hi": "Dog", + } + + return channelMap +} diff --git a/stride-covenant/astroport/astroport_factory.wasm b/interchaintest/wasms/astroport/astroport_factory.wasm similarity index 100% rename from stride-covenant/astroport/astroport_factory.wasm rename to interchaintest/wasms/astroport/astroport_factory.wasm diff --git a/stride-covenant/astroport/astroport_native_coin_registry.wasm b/interchaintest/wasms/astroport/astroport_native_coin_registry.wasm similarity index 100% rename from stride-covenant/astroport/astroport_native_coin_registry.wasm rename to interchaintest/wasms/astroport/astroport_native_coin_registry.wasm diff --git a/stride-covenant/astroport/astroport_pair.wasm b/interchaintest/wasms/astroport/astroport_pair.wasm similarity index 100% rename from stride-covenant/astroport/astroport_pair.wasm rename to interchaintest/wasms/astroport/astroport_pair.wasm diff --git a/stride-covenant/astroport/astroport_pair_stable.wasm b/interchaintest/wasms/astroport/astroport_pair_stable.wasm similarity index 100% rename from stride-covenant/astroport/astroport_pair_stable.wasm rename to interchaintest/wasms/astroport/astroport_pair_stable.wasm diff --git a/stride-covenant/astroport/astroport_token.wasm b/interchaintest/wasms/astroport/astroport_token.wasm similarity index 100% rename from stride-covenant/astroport/astroport_token.wasm rename to interchaintest/wasms/astroport/astroport_token.wasm diff --git a/stride-covenant/astroport/astroport_whitelist.wasm b/interchaintest/wasms/astroport/astroport_whitelist.wasm similarity index 100% rename from stride-covenant/astroport/astroport_whitelist.wasm rename to interchaintest/wasms/astroport/astroport_whitelist.wasm diff --git a/interchaintest/wasms/polytone/polytone_listener.wasm b/interchaintest/wasms/polytone/polytone_listener.wasm new file mode 100644 index 00000000..04c451bb Binary files /dev/null and b/interchaintest/wasms/polytone/polytone_listener.wasm differ diff --git a/interchaintest/wasms/polytone/polytone_note.wasm b/interchaintest/wasms/polytone/polytone_note.wasm new file mode 100644 index 00000000..7b855e1a Binary files /dev/null and b/interchaintest/wasms/polytone/polytone_note.wasm differ diff --git a/interchaintest/wasms/polytone/polytone_proxy.wasm b/interchaintest/wasms/polytone/polytone_proxy.wasm new file mode 100644 index 00000000..d033f0bd Binary files /dev/null and b/interchaintest/wasms/polytone/polytone_proxy.wasm differ diff --git a/interchaintest/wasms/polytone/polytone_tester.wasm b/interchaintest/wasms/polytone/polytone_tester.wasm new file mode 100644 index 00000000..14d2d39e Binary files /dev/null and b/interchaintest/wasms/polytone/polytone_tester.wasm differ diff --git a/interchaintest/wasms/polytone/polytone_voice.wasm b/interchaintest/wasms/polytone/polytone_voice.wasm new file mode 100644 index 00000000..5fbc34fe Binary files /dev/null and b/interchaintest/wasms/polytone/polytone_voice.wasm differ diff --git a/justfile b/justfile new file mode 100644 index 00000000..8df47820 --- /dev/null +++ b/justfile @@ -0,0 +1,39 @@ +build: + cargo build + +test: + cargo test + +lint: + cargo clippy --all-targets -- -D warnings + +schema: + #!/usr/bin/env sh + ./schemagen.sh + +optimize: + #!/usr/bin/env sh + ./optimize.sh + if [[ $(uname -m) =~ "arm64" ]]; then + for file in ./artifacts/*-aarch64.wasm; do + if [ -f "$file" ]; then + new_name="${file%-aarch64.wasm}.wasm" + mv "$file" "./$new_name" + fi + done + fi + +local-e2e-rebuild TEST PATTERN='.*': optimize + mkdir -p interchaintest/{{TEST}}/wasms + cp -R interchaintest/wasms/polytone/*.wasm interchaintest/{{TEST}}/wasms + cp -R interchaintest/wasms/astroport/*.wasm interchaintest/{{TEST}}/wasms + cp -R artifacts/*.wasm interchaintest/{{TEST}}/wasms + ls interchaintest/{{TEST}}/wasms + cd interchaintest/{{TEST}} && go clean -testcache && go test -timeout 60m -v -run '{{PATTERN}}' + +local-e2e TEST PATTERN='.*': + mkdir -p interchaintest/{{TEST}}/wasms + cp -R interchaintest/wasms/polytone/*.wasm interchaintest/{{TEST}}/wasms + cp -R interchaintest/wasms/astroport/*.wasm interchaintest/{{TEST}}/wasms + cp -R artifacts/*.wasm interchaintest/{{TEST}}/wasms + cd interchaintest/{{TEST}} && go clean -testcache && go test -timeout 60m -v -run '{{PATTERN}}' diff --git a/optimize.sh b/optimize.sh new file mode 100755 index 00000000..b169db48 --- /dev/null +++ b/optimize.sh @@ -0,0 +1,14 @@ +#!/bin/bash +if [[ $(uname -m) =~ "arm64" ]]; then \ + docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/workspace-optimizer-arm64:0.14.0 + +else + docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + --platform linux/amd64 \ + cosmwasm/workspace-optimizer:0.14.0 +fi diff --git a/packages/clock-derive/Cargo.toml b/packages/clock-derive/Cargo.toml deleted file mode 100644 index b25504a0..00000000 --- a/packages/clock-derive/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "covenant-clock-derive" -version = "0.0.1" -edition = "2021" -authors = ["ekez "] -description = "A package for deriving the covenant-clock interface." -license = { workspace = true } - - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1" -quote = "1" -syn = "1" diff --git a/packages/clock-derive/src/lib.rs b/packages/clock-derive/src/lib.rs deleted file mode 100644 index bc3c0e30..00000000 --- a/packages/clock-derive/src/lib.rs +++ /dev/null @@ -1,52 +0,0 @@ -use proc_macro::TokenStream; - -use quote::quote; -use syn::{parse_macro_input, AttributeArgs, DataEnum, DeriveInput}; - -// Merges the variants of two enums. -fn merge_variants(metadata: TokenStream, left: TokenStream, right: TokenStream) -> TokenStream { - use syn::Data::Enum; - - let args = parse_macro_input!(metadata as AttributeArgs); - if let Some(first_arg) = args.first() { - return syn::Error::new_spanned(first_arg, "macro takes no arguments") - .to_compile_error() - .into(); - } - - let mut left: DeriveInput = parse_macro_input!(left); - let right: DeriveInput = parse_macro_input!(right); - - if let ( - Enum(DataEnum { variants, .. }), - Enum(DataEnum { - variants: to_add, .. - }), - ) = (&mut left.data, right.data) - { - variants.extend(to_add); - - quote! { #left }.into() - } else { - syn::Error::new(left.ident.span(), "variants may only be added for enums") - .to_compile_error() - .into() - } -} - -#[proc_macro_attribute] -pub fn clocked(metadata: TokenStream, input: TokenStream) -> TokenStream { - merge_variants( - metadata, - input, - quote!( - enum Clocked { - /// Wakes the state machine up. The caller should - /// check the sender of the tick is the clock if - /// they'd like to pause when the clock does. - Tick {}, - } - ) - .into(), - ) -} diff --git a/packages/covenant-macros/Cargo.toml b/packages/covenant-macros/Cargo.toml new file mode 100644 index 00000000..aa15f741 --- /dev/null +++ b/packages/covenant-macros/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "covenant-macros" +version = { workspace = true } +edition = { workspace = true } +authors = ["ekez , benskey bekauz@protonmail.com"] +description = "A package for deriving the covenant interfaces." +license = "BSD-3" + + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { workspace = true } diff --git a/packages/covenant-macros/src/lib.rs b/packages/covenant-macros/src/lib.rs new file mode 100644 index 00000000..fee75c69 --- /dev/null +++ b/packages/covenant-macros/src/lib.rs @@ -0,0 +1,188 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, AttributeArgs, DataEnum, DeriveInput}; + +// Merges the variants of two enums. +fn merge_variants(metadata: TokenStream, left: TokenStream, right: TokenStream) -> TokenStream { + use syn::Data::Enum; + + let args = parse_macro_input!(metadata as AttributeArgs); + if let Some(first_arg) = args.first() { + return syn::Error::new_spanned(first_arg, "macro takes no arguments") + .to_compile_error() + .into(); + } + + let mut left: DeriveInput = parse_macro_input!(left); + let right: DeriveInput = parse_macro_input!(right); + + if let ( + Enum(DataEnum { variants, .. }), + Enum(DataEnum { + variants: to_add, .. + }), + ) = (&mut left.data, right.data) + { + variants.extend(to_add); + + quote! { #left }.into() + } else { + syn::Error::new(left.ident.span(), "variants may only be added for enums") + .to_compile_error() + .into() + } +} + +#[proc_macro_attribute] +pub fn clocked(metadata: TokenStream, input: TokenStream) -> TokenStream { + merge_variants( + metadata, + input, + quote!( + enum Clocked { + /// Wakes the state machine up. The caller should + /// check the sender of the tick is the clock if + /// they'd like to pause when the clock does. + Tick {}, + } + ) + .into(), + ) +} + +#[proc_macro_attribute] +pub fn covenant_deposit_address(metadata: TokenStream, input: TokenStream) -> TokenStream { + merge_variants( + metadata, + input, + quote!( + enum Deposit { + /// Returns the address a contract expects to receive funds to + #[returns(Option)] + DepositAddress {}, + } + ) + .into(), + ) +} + +#[proc_macro_attribute] +pub fn covenant_clock_address(metadata: TokenStream, input: TokenStream) -> TokenStream { + merge_variants( + metadata, + input, + quote!( + enum Clock { + /// Returns the associated clock address authorized to submit ticks + #[returns(Addr)] + ClockAddress {}, + } + ) + .into(), + ) +} + +#[proc_macro_attribute] +pub fn covenant_remote_chain(metadata: TokenStream, input: TokenStream) -> TokenStream { + merge_variants( + metadata, + input, + quote!( + enum RemoteChain { + /// Returns the associated remote chain information + #[returns(RemoteChainInfo)] + RemoteChainInfo {}, + } + ) + .into(), + ) +} + +#[proc_macro_attribute] +pub fn covenant_ica_address(metadata: TokenStream, input: TokenStream) -> TokenStream { + merge_variants( + metadata, + input, + quote!( + enum ICA { + /// Returns the associated remote chain information + #[returns(Option)] + IcaAddress {}, + } + ) + .into(), + ) +} + +#[proc_macro_attribute] +pub fn covenant_next_contract(metadata: TokenStream, input: TokenStream) -> TokenStream { + merge_variants( + metadata, + input, + quote!( + enum NextContract { + /// Returns the associated remote chain information + #[returns(Option)] + NextContract {}, + } + ) + .into(), + ) +} + +#[proc_macro_attribute] +pub fn covenant_lper_withdraw(metadata: TokenStream, input: TokenStream) -> TokenStream { + merge_variants( + metadata, + input, + quote!( + enum WithdrawMsgs { + /// Tells the LPer to withdraw his position + /// Should only be called by the holder of the covenant + Withdraw { percentage: Option }, + } + ) + .into(), + ) +} + +#[proc_macro_attribute] +pub fn covenant_holder_distribute(metadata: TokenStream, input: TokenStream) -> TokenStream { + merge_variants( + metadata, + input, + quote!( + enum DistributeMsgs { + /// After LPer finished withdrawing from LP, it sends the funds to the holder + /// and the holder distributes them based on its logic + /// Should only be called by the LPer of the covenant + Distribute {}, + /// This message is sent in case we do an IBC withdraw + /// The withdraw can fail in async way, in case that happens we want the holder to be notified on that. + /// In case of astroport, the withdraww + distribution is atomic, so nothing to worry there + /// But in case of osmosis, the withdraw is async, so the "claim" will successful happen, + /// while the withdraw can fail, in case the withdraw fails here, we execute this message on the holder + WithdrawFailed {}, + } + ) + .into(), + ) +} + +#[proc_macro_attribute] +pub fn covenant_holder_emergency_withdraw( + metadata: TokenStream, + input: TokenStream, +) -> TokenStream { + merge_variants( + metadata, + input, + quote!( + enum EmergencyWithdrawMsgs { + /// Allows for the emergency committee to withdraw the funds on case of an emergency + EmergencyWithdraw {}, + } + ) + .into(), + ) +} diff --git a/packages/covenant-utils/Cargo.toml b/packages/covenant-utils/Cargo.toml new file mode 100644 index 00000000..4769add9 --- /dev/null +++ b/packages/covenant-utils/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "covenant-utils" +version = { workspace = true } +edition = { workspace = true } +authors = ["benskey bekauz@protonmail.com"] +description = "A package for common utils for covenants" +license = { workspace = true } + +[lib] + +[dependencies] +cosmwasm-schema = { workspace = true } +cw-storage-plus = { workspace = true } +neutron-sdk = { workspace = true } +cosmwasm-std = { workspace = true } +prost = { workspace = true } +cosmos-sdk-proto = { workspace = true } +cw20 = { workspace = true } +cw-utils = { workspace = true } +astroport = { workspace = true } +polytone = "1.0.0" +covenant-macros = { workspace = true } +sha2 = { workspace = true } +bech32 = { workspace = true } +serde-json-wasm = { workspace = true } diff --git a/packages/covenant-utils/src/astroport.rs b/packages/covenant-utils/src/astroport.rs new file mode 100644 index 00000000..83ae39c0 --- /dev/null +++ b/packages/covenant-utils/src/astroport.rs @@ -0,0 +1,54 @@ +use astroport::asset::PairInfo; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{QuerierWrapper, StdError, Uint128}; +use cw20::BalanceResponse; + +/// queries the liquidity token balance of given address +pub fn query_liquidity_token_balance( + querier: QuerierWrapper, + liquidity_token: &str, + contract_addr: String, +) -> Result { + let liquidity_token_balance: BalanceResponse = querier.query_wasm_smart( + liquidity_token, + &cw20::Cw20QueryMsg::Balance { + address: contract_addr, + }, + )?; + Ok(liquidity_token_balance.balance) +} + +/// queries the cw20 liquidity token address corresponding to a given pool +pub fn query_liquidity_token_address( + querier: QuerierWrapper, + pool: String, +) -> Result { + let pair_info: PairInfo = + querier.query_wasm_smart(pool, &astroport::pair::QueryMsg::Pair {})?; + Ok(pair_info.liquidity_token.to_string()) +} + +pub fn query_astro_pool_token( + querier: QuerierWrapper, + pool: String, + addr: String, +) -> Result { + let pair_info: PairInfo = + querier.query_wasm_smart(pool, &astroport::pair::QueryMsg::Pair {})?; + + let liquidity_token_balance: BalanceResponse = querier.query_wasm_smart( + pair_info.liquidity_token.as_ref(), + &cw20::Cw20QueryMsg::Balance { address: addr }, + )?; + + Ok(AstroportPoolTokenResponse { + pair_info, + balance_response: liquidity_token_balance, + }) +} + +#[cw_serde] +pub struct AstroportPoolTokenResponse { + pub pair_info: PairInfo, + pub balance_response: BalanceResponse, +} diff --git a/packages/covenant-utils/src/deadline.rs b/packages/covenant-utils/src/deadline.rs new file mode 100644 index 00000000..a720dd47 --- /dev/null +++ b/packages/covenant-utils/src/deadline.rs @@ -0,0 +1,44 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::BlockInfo; +use cw_utils::{Duration, Expiration}; + +#[cw_serde] +#[serde(untagged)] +pub enum Deadline { + Expiration(Expiration), + Duration(Duration), +} + +impl Default for Deadline { + fn default() -> Self { + Deadline::Expiration(Expiration::default()) + } +} + +impl Deadline { + pub fn into_expiration(self, block: &BlockInfo) -> Expiration { + match self { + Deadline::Expiration(expiration) => expiration, + Deadline::Duration(duration) => duration.after(block), + } + } +} + +#[cfg(test)] +mod test { + use cosmwasm_schema::cw_serde; + use cosmwasm_std::from_json; + + use super::Deadline; + + #[cw_serde] + struct Example { + expires: Deadline, + } + + #[test] + fn test() { + let json_string = "{\"expires\": {\"never\": {}}}"; + println!("{:?}", from_json::(&json_string).unwrap()); + } +} diff --git a/packages/covenant-utils/src/ica.rs b/packages/covenant-utils/src/ica.rs new file mode 100644 index 00000000..670bd580 --- /dev/null +++ b/packages/covenant-utils/src/ica.rs @@ -0,0 +1,202 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + Binary, Coin, CosmosMsg, DepsMut, Env, QuerierWrapper, QueryRequest, Reply, Response, StdError, + StdResult, Storage, SubMsg, Uint64, +}; +use neutron_sdk::{ + bindings::{ + msg::{MsgSubmitTxResponse, NeutronMsg}, + query::NeutronQuery, + }, + interchain_txs::helpers::get_port_id, + sudo::msg::RequestPacket, +}; + +use crate::neutron::{OpenAckVersion, SudoPayload}; + +type ExecuteDeps<'a> = DepsMut<'a, NeutronQuery>; + +pub const INTERCHAIN_ACCOUNT_ID: &str = "valence-ica"; + +pub trait IcaStateHelper { + fn reset_state(&self, storage: &mut dyn Storage) -> StdResult<()>; + fn clear_ica(&self, storage: &mut dyn Storage) -> StdResult<()>; + fn save_ica( + &self, + storage: &mut dyn Storage, + port_id: String, + address: String, + controller_connection_id: String, + ) -> StdResult<()>; + fn save_state_ica_created(&self, storage: &mut dyn Storage) -> StdResult<()>; + fn save_reply_payload(&self, storage: &mut dyn Storage, payload: SudoPayload) -> StdResult<()>; + fn read_reply_payload(&self, storage: &mut dyn Storage) -> StdResult; + fn save_sudo_payload( + &self, + storage: &mut dyn Storage, + channel_id: String, + seq_id: u64, + payload: SudoPayload, + ) -> StdResult<()>; + fn get_ica(&self, storage: &dyn Storage, key: String) -> StdResult<(String, String)>; +} + +/// reverts th contract state to Instantiated and clears the ICA storage. +/// channel is already closed. +pub fn sudo_timeout( + state_helper: &H, + deps: ExecuteDeps, + _env: Env, + _request: RequestPacket, +) -> StdResult> { + // revert the state to Instantiated to force re-creation of ICA + state_helper.reset_state(deps.storage)?; + state_helper.clear_ica(deps.storage)?; + + // returning Ok as this is anticipated. channel is already closed. + Ok(Response::default()) +} + +/// handles the response. if request sequence or source channel are missing, +/// it will return an error and close the channel. otherwise returns an Ok() +/// with data encoded in base64 as a response attribute. +pub fn sudo_response(request: RequestPacket, data: Binary) -> StdResult> { + // either of these errors will close the channel + request + .sequence + .ok_or_else(|| StdError::generic_err("sequence not found"))?; + + request + .source_channel + .ok_or_else(|| StdError::generic_err("channel_id not found"))?; + + Ok(Response::default() + .add_attribute("method", "sudo_response") + .add_attribute("data", data.to_base64())) +} + +/// handles the sudo error. if request sequence or source channel are missing, +/// it will return an error and close the channel. otherwise returns an Ok(). +pub fn sudo_error(request: RequestPacket, _details: String) -> StdResult> { + // either of these errors will close the channel + request + .sequence + .ok_or_else(|| StdError::generic_err("sequence not found"))?; + + request + .source_channel + .ok_or_else(|| StdError::generic_err("channel_id not found"))?; + + Ok(Response::default().add_attribute("method", "sudo_error")) +} + +pub fn sudo_open_ack( + state_helper: &H, + deps: ExecuteDeps, + _env: Env, + port_id: String, + _channel_id: String, + _counterparty_channel_id: String, + counterparty_version: String, +) -> StdResult> { + // The version variable contains a JSON value with multiple fields, + // including the generated account address. + let parsed_version: Result = + serde_json_wasm::from_str(counterparty_version.as_str()); + + // get the parsed OpenAckVersion or return an error if we fail + let Ok(parsed_version) = parsed_version else { + return Err(StdError::generic_err("Can't parse counterparty_version")); + }; + + state_helper.save_ica( + deps.storage, + port_id, + parsed_version.address, + parsed_version.controller_connection_id, + )?; + state_helper.save_state_ica_created(deps.storage)?; + + Ok(Response::default().add_attribute("method", "sudo_open_ack")) +} + +/// prepare_sudo_payload is called from reply handler +/// The method is used to extract sequence id and channel from SubmitTxResponse to +/// process sudo payload defined in msg_with_sudo_callback later in Sudo handler. +/// Such flow msg_with_sudo_callback() -> reply() -> prepare_sudo_payload() -> sudo() +/// allows you "attach" some payload to your SubmitTx message +/// and process this payload when an acknowledgement for the SubmitTx message +/// is received in Sudo handler +pub fn prepare_sudo_payload( + state_helper: &H, + deps: ExecuteDeps, + _env: Env, + msg: Reply, +) -> StdResult> { + let payload = state_helper.read_reply_payload(deps.storage)?; + + let resp: MsgSubmitTxResponse = serde_json_wasm::from_slice( + msg.result + .into_result() + .map_err(StdError::generic_err)? + .data + .ok_or_else(|| StdError::generic_err("no result"))? + .as_slice(), + ) + .map_err(|e| StdError::generic_err(format!("failed to parse response: {e:?}")))?; + + let seq_id = resp.sequence_id; + let channel_id = resp.channel; + + state_helper.save_sudo_payload(deps.storage, channel_id, seq_id, payload)?; + + Ok(Response::default()) +} + +pub fn get_ica( + state_helper: &H, + storage: &dyn Storage, + contract_addr: &str, + ica_id: &str, +) -> StdResult<(String, String)> { + let key = get_port_id(contract_addr, ica_id); + state_helper.get_ica(storage, key) +} + +pub fn msg_with_sudo_callback>, T, H: IcaStateHelper>( + state_helper: &H, + deps: ExecuteDeps, + msg: C, + payload: SudoPayload, + reply_id: u64, +) -> StdResult> { + state_helper.save_reply_payload(deps.storage, payload)?; + Ok(SubMsg::reply_on_success(msg, reply_id)) +} + +// manual definitions for neutron ictxs module +#[cw_serde] +pub struct Params { + pub msg_submit_tx_max_messages: Uint64, + pub register_fee: Vec, +} + +#[cw_serde] +pub struct QueryParamsResponse { + pub params: Params, +} + +pub fn get_ictxs_module_params_query_msg() -> QueryRequest { + QueryRequest::Stargate { + path: "/neutron.interchaintxs.v1.Query/Params".to_string(), + data: Binary(Vec::new()), + } +} + +pub fn query_ica_registration_fee( + querier: QuerierWrapper<'_, NeutronQuery>, +) -> StdResult> { + let query_msg = get_ictxs_module_params_query_msg(); + let response: QueryParamsResponse = querier.query(&query_msg)?; + Ok(response.params.register_fee) +} diff --git a/packages/covenant-utils/src/instantiate2_helper.rs b/packages/covenant-utils/src/instantiate2_helper.rs new file mode 100644 index 00000000..1556e0a1 --- /dev/null +++ b/packages/covenant-utils/src/instantiate2_helper.rs @@ -0,0 +1,50 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + instantiate2_address, Addr, Binary, CanonicalAddr, CodeInfoResponse, Deps, StdError, StdResult, +}; +use sha2::{Digest, Sha256}; + +fn get_precomputed_address( + deps: Deps, + code_id: u64, + creator: &CanonicalAddr, + salt: &[u8], +) -> StdResult { + let CodeInfoResponse { checksum, .. } = deps.querier.query_wasm_code_info(code_id)?; + + match instantiate2_address(&checksum, creator, salt) { + Ok(addr) => Ok(deps.api.addr_humanize(&addr)?), + Err(e) => Err(StdError::generic_err(e.to_string())), + } +} + +fn generate_contract_salt(salt_str: &[u8]) -> Binary { + let mut hasher = Sha256::new(); + hasher.update(salt_str); + hasher.finalize().to_vec().into() +} + +pub fn get_instantiate2_salt_and_address( + deps: Deps, + salt_bytes: &[u8], + creator_address: &CanonicalAddr, + code_id: u64, +) -> StdResult { + let salt_binary = generate_contract_salt(salt_bytes); + + let contract_instantiate2_address = + get_precomputed_address(deps, code_id, creator_address, &salt_binary)?; + + Ok(Instantiate2HelperConfig { + addr: contract_instantiate2_address, + code: code_id, + salt: salt_binary, + }) +} + +#[cw_serde] +pub struct Instantiate2HelperConfig { + pub addr: Addr, + pub code: u64, + pub salt: Binary, +} diff --git a/packages/covenant-utils/src/lib.rs b/packages/covenant-utils/src/lib.rs new file mode 100644 index 00000000..2d52b015 --- /dev/null +++ b/packages/covenant-utils/src/lib.rs @@ -0,0 +1,388 @@ +use std::collections::BTreeMap; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + to_json_string, Addr, Api, Attribute, Coin, CosmosMsg, Decimal, StdError, StdResult, Timestamp, + Uint128, Uint64, +}; +use neutron::flatten_ibc_fee_total_amount; +use neutron_sdk::{ + bindings::msg::{IbcFee, NeutronMsg}, + sudo::msg::RequestPacketTimeoutHeight, +}; + +pub mod astroport; +pub mod deadline; +pub mod ica; +pub mod instantiate2_helper; +pub mod liquid_pooler_withdraw; +pub mod neutron; +pub mod polytone; +pub mod split; +pub mod withdraw_lp_helper; + +#[cw_serde] +pub struct InterchainCovenantParty { + /// address of the receiver on destination chain + pub party_receiver_addr: String, + /// connection id to the party chain + pub party_chain_connection_id: String, + /// timeout in seconds + pub ibc_transfer_timeout: Uint64, + /// channel id from party to host chain + pub party_to_host_chain_channel_id: String, + /// channel id from host chain to the party chain + pub host_to_party_chain_channel_id: String, + /// denom provided by the party on its native chain + pub remote_chain_denom: String, + /// authorized address of the party on neutron + pub addr: String, + /// denom provided by the party on neutron + pub native_denom: String, + /// coin provided by the party on its native chain + pub contribution: Coin, + /// configuration for unwinding the denoms via pfm + pub denom_to_pfm_map: BTreeMap, + /// fallback refund address on the remote chain + pub fallback_address: Option, +} + +#[cw_serde] +pub struct NativeCovenantParty { + /// address of the receiver on destination chain + pub party_receiver_addr: String, + /// denom provided by the party on neutron + pub native_denom: String, + /// authorized address of the party on neutron + pub addr: String, + /// coin provided by the party on its native chain + pub contribution: Coin, +} + +#[cw_serde] +pub enum ReceiverConfig { + /// party expects to receive funds on the same chain + Native(String), + /// party expects to receive funds on a remote chain + Ibc(DestinationConfig), +} + +impl ReceiverConfig { + pub fn get_response_attributes(self, party: String) -> Vec { + match self { + ReceiverConfig::Native(addr) => { + vec![Attribute::new("receiver_config_native_addr", addr)] + } + ReceiverConfig::Ibc(destination_config) => destination_config + .get_response_attributes() + .into_iter() + .map(|mut a| { + a.key = party.to_string() + &a.key; + a + }) + .collect(), + } + } +} + +#[cw_serde] +pub struct CovenantParty { + /// authorized address of the party + pub addr: String, + /// denom provided by the party + pub native_denom: String, + /// information about receiver address + pub receiver_config: ReceiverConfig, +} + +impl CovenantParty { + pub fn validate_receiver_address(&self, api: &dyn Api) -> StdResult { + match &self.receiver_config { + ReceiverConfig::Native(addr) => api.addr_validate(addr), + ReceiverConfig::Ibc(destination_config) => { + match soft_validate_remote_chain_addr( + api, + &destination_config.destination_receiver_addr, + ) { + Ok(_) => Ok(Addr::unchecked( + &destination_config.destination_receiver_addr, + )), + Err(e) => Err(e), + } + } + } + } +} + +#[cw_serde] +pub struct CovenantPartiesConfig { + pub party_a: CovenantParty, + pub party_b: CovenantParty, +} + +impl CovenantPartiesConfig { + pub fn get_response_attributes(self) -> Vec { + let mut attrs = vec![ + Attribute::new("party_a_address", self.party_a.addr), + Attribute::new("party_a_ibc_denom", self.party_a.native_denom), + Attribute::new("party_b_address", self.party_b.addr), + Attribute::new("party_b_ibc_denom", self.party_b.native_denom), + ]; + attrs.extend( + self.party_a + .receiver_config + .get_response_attributes("party_a_".to_string()), + ); + attrs.extend( + self.party_b + .receiver_config + .get_response_attributes("party_b_".to_string()), + ); + attrs + } + + pub fn match_caller_party(&self, caller: String) -> Result { + let a = self.clone().party_a; + let b = self.clone().party_b; + if a.addr == caller { + Ok(a) + } else if b.addr == caller { + Ok(b) + } else { + Err(StdError::generic_err("unauthorized")) + } + } + + pub fn validate_party_addresses(&self, api: &dyn Api) -> StdResult<()> { + self.party_a.validate_receiver_address(api)?; + self.party_b.validate_receiver_address(api)?; + Ok(()) + } +} + +#[cw_serde] +pub enum CovenantTerms { + TokenSwap(SwapCovenantTerms), +} + +#[cw_serde] +pub struct SwapCovenantTerms { + pub party_a_amount: Uint128, + pub party_b_amount: Uint128, +} + +#[cw_serde] +pub struct PolCovenantTerms { + pub party_a_amount: Uint128, + pub party_b_amount: Uint128, +} + +impl CovenantTerms { + pub fn get_response_attributes(self) -> Vec { + match self { + CovenantTerms::TokenSwap(terms) => { + let attrs = vec![ + Attribute::new("covenant_terms", "token_swap"), + Attribute::new("party_a_amount", terms.party_a_amount), + Attribute::new("party_b_amount", terms.party_b_amount), + ]; + attrs + } + } + } +} + +#[cw_serde] +pub struct DestinationConfig { + /// channel id of the destination chain + pub local_to_destination_chain_channel_id: String, + /// address of the receiver on destination chain + pub destination_receiver_addr: String, + /// timeout in seconds + pub ibc_transfer_timeout: Uint64, + /// pfm configurations for denoms + pub denom_to_pfm_map: BTreeMap, +} + +#[cw_serde] +pub struct PacketForwardMiddlewareConfig { + pub local_to_hop_chain_channel_id: String, + pub hop_to_destination_chain_channel_id: String, + pub hop_chain_receiver_address: String, +} + +pub fn get_default_ica_fee() -> Coin { + Coin { + denom: "untrn".to_string(), + amount: Uint128::new(1000000), + } +} + +// https://github.com/strangelove-ventures/packet-forward-middleware/blob/main/router/types/forward.go +#[cw_serde] +pub struct PacketMetadata { + pub forward: Option, +} + +#[cw_serde] +pub struct ForwardMetadata { + pub receiver: String, + pub port: String, + pub channel: String, +} + +impl DestinationConfig { + pub fn get_ibc_transfer_messages_for_coins( + &self, + coins: Vec, + current_timestamp: Timestamp, + sender_address: String, + ibc_fee: IbcFee, + ) -> StdResult>> { + let mut messages: Vec> = vec![]; + // we get the number of target denoms we have to reserve + // neutron fees for + let count = Uint128::from(1 + coins.len() as u128); + + let total_fee = flatten_ibc_fee_total_amount(&ibc_fee); + + for coin in coins { + let send_coin = if coin.denom != "untrn" { + Some(coin) + } else { + // if its neutron we're distributing we need to keep a + // reserve for ibc gas costs. + // this is safe because we pass target denoms. + let reserve_amount = count * total_fee; + if coin.amount > reserve_amount { + Some(Coin { + denom: coin.denom, + amount: coin.amount - reserve_amount, + }) + } else { + None + } + }; + + if let Some(c) = send_coin { + match self.denom_to_pfm_map.get(&c.denom) { + Some(pfm_config) => { + messages.push(CosmosMsg::Custom(NeutronMsg::IbcTransfer { + source_port: "transfer".to_string(), + // local chain to hop chain channel + source_channel: pfm_config.local_to_hop_chain_channel_id.to_string(), + token: c.clone(), + sender: sender_address.to_string(), + receiver: pfm_config.hop_chain_receiver_address.to_string(), + timeout_height: RequestPacketTimeoutHeight { + revision_number: None, + revision_height: None, + }, + timeout_timestamp: current_timestamp + .plus_seconds(self.ibc_transfer_timeout.u64()) + .nanos(), + memo: to_json_string(&PacketMetadata { + forward: Some(ForwardMetadata { + receiver: self.destination_receiver_addr.to_string(), + port: "transfer".to_string(), + // hop chain to final receiver chain channel + channel: pfm_config + .hop_to_destination_chain_channel_id + .to_string(), + }), + })?, + fee: ibc_fee.clone(), + })) + } + None => { + messages.push(CosmosMsg::Custom(NeutronMsg::IbcTransfer { + source_port: "transfer".to_string(), + source_channel: self.local_to_destination_chain_channel_id.to_string(), + token: c.clone(), + sender: sender_address.to_string(), + receiver: self.destination_receiver_addr.to_string(), + timeout_height: RequestPacketTimeoutHeight { + revision_number: None, + revision_height: None, + }, + timeout_timestamp: current_timestamp + .plus_seconds(self.ibc_transfer_timeout.u64()) + .nanos(), + memo: format!("ibc_distribution: {:?}:{:?}", c.denom, c.amount,) + .to_string(), + fee: ibc_fee.clone(), + })); + } + } + } + } + + Ok(messages) + } + + pub fn get_response_attributes(&self) -> Vec { + vec![ + Attribute::new( + "local_to_destination_chain_channel_id", + self.local_to_destination_chain_channel_id.to_string(), + ), + Attribute::new( + "destination_receiver_addr", + self.destination_receiver_addr.to_string(), + ), + Attribute::new("ibc_transfer_timeout", self.ibc_transfer_timeout), + ] + } +} + +#[cw_serde] +pub struct PfmUnwindingConfig { + // keys: relevant denoms IBC'd to neutron + // values: channel ids to facilitate ibc unwinding to party chain + pub party_1_pfm_map: BTreeMap, + pub party_2_pfm_map: BTreeMap, +} + +/// single side lp limits define the highest amount (in `Uint128`) that +/// we consider acceptable to provide single-sided. +/// if asset balance exceeds these limits, double-sided liquidity should be provided. +#[cw_serde] +pub struct SingleSideLpLimits { + pub asset_a_limit: Uint128, + pub asset_b_limit: Uint128, +} + +/// config for the pool price expectations upon covenant instantiation +#[cw_serde] +pub struct PoolPriceConfig { + pub expected_spot_price: Decimal, + pub acceptable_price_spread: Decimal, +} + +/// soft validation for addresses on remote chains. +/// skips the bech32 prefix and variant checks. +pub fn soft_validate_remote_chain_addr(api: &dyn Api, addr: &str) -> StdResult<()> { + let (_prefix, decoded, _variant) = bech32::decode(addr).map_err(|e| { + StdError::generic_err(format!( + "soft_addr_validation for address {:?} failed to bech32 decode: {:?}", + addr, + e.to_string() + )) + })?; + let decoded_bytes = as bech32::FromBase32>::from_base32(&decoded).map_err(|e| { + StdError::generic_err(format!( + "soft_addr_validation for address {:?} failed to get bytes from base32: {:?}", + addr, + e.to_string() + )) + })?; + + match api.addr_humanize(&decoded_bytes.into()) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(format!( + "soft_addr_validation for address {:?} failed to addr_humanize: {:?}", + addr, + e.to_string() + ))), + } +} diff --git a/packages/covenant-utils/src/liquid_pooler_withdraw.rs b/packages/covenant-utils/src/liquid_pooler_withdraw.rs new file mode 100644 index 00000000..24ae04d5 --- /dev/null +++ b/packages/covenant-utils/src/liquid_pooler_withdraw.rs @@ -0,0 +1,8 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Decimal; +use covenant_macros::{covenant_holder_distribute, covenant_lper_withdraw}; + +#[covenant_lper_withdraw] +#[covenant_holder_distribute] +#[cw_serde] +pub enum WithdrawLPMsgs {} diff --git a/packages/covenant-utils/src/neutron.rs b/packages/covenant-utils/src/neutron.rs new file mode 100644 index 00000000..9ee900e4 --- /dev/null +++ b/packages/covenant-utils/src/neutron.rs @@ -0,0 +1,189 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{ + Attribute, Binary, MessageInfo, QuerierWrapper, StdError, StdResult, Uint128, Uint64, +}; +use cw_utils::must_pay; +use neutron_sdk::{ + bindings::{msg::IbcFee, query::NeutronQuery, types::ProtobufAny}, + query::min_ibc_fee::MinIbcFeeResponse, + NeutronResult, +}; +use prost::Message; + +#[cw_serde] +pub struct OpenAckVersion { + pub version: String, + pub controller_connection_id: String, + pub host_connection_id: String, + pub address: String, + pub encoding: String, + pub tx_type: String, +} + +/// SudoPayload is a type that stores information about a transaction that we try to execute +/// on the host chain. This is a type introduced for our convenience. +#[cw_serde] +pub struct SudoPayload { + pub message: String, + pub port_id: String, +} + +/// Serves for storing acknowledgement calls for interchain transactions +#[cw_serde] +pub enum AcknowledgementResult { + /// Success - Got success acknowledgement in sudo with array of message item types in it + Success(Vec), + /// Error - Got error acknowledgement in sudo with payload message in it and error details + Error((String, String)), + /// Timeout - Got timeout acknowledgement in sudo with payload message in it + Timeout(String), +} + +#[cw_serde] +pub struct RemoteChainInfo { + /// connection id from neutron to the remote chain on which + /// we wish to open an ICA + pub connection_id: String, + pub channel_id: String, + pub denom: String, + pub ibc_transfer_timeout: Uint64, + pub ica_timeout: Uint64, +} + +impl RemoteChainInfo { + pub fn get_response_attributes(&self) -> Vec { + vec![ + Attribute::new("connection_id", &self.connection_id), + Attribute::new("channel_id", &self.channel_id), + Attribute::new("denom", &self.denom), + Attribute::new( + "ibc_transfer_timeout", + self.ibc_transfer_timeout.to_string(), + ), + Attribute::new("ica_timeout", self.ica_timeout.to_string()), + ] + } +} + +pub fn get_proto_coin( + denom: String, + amount: Uint128, +) -> cosmos_sdk_proto::cosmos::base::v1beta1::Coin { + cosmos_sdk_proto::cosmos::base::v1beta1::Coin { + denom, + amount: amount.to_string(), + } +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Returns the associated remote chain information + #[returns(Option)] + DepositAddress {}, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum CovenantQueryMsg { + /// Returns the associated remote chain information + #[returns(Option)] + DepositAddress {}, +} + +/// helper that serializes a MsgTransfer to protobuf +pub fn to_proto_msg_transfer(msg: impl Message) -> NeutronResult { + // Serialize the Transfer message + let mut buf = Vec::with_capacity(msg.encoded_len()); + if let Err(e) = msg.encode(&mut buf) { + return Err(StdError::generic_err(format!("Encode error: {e}")).into()); + } + + Ok(ProtobufAny { + type_url: "/ibc.applications.transfer.v1.MsgTransfer".to_string(), + value: Binary::from(buf), + }) +} + +pub fn to_proto_msg_send(msg: impl Message) -> NeutronResult { + // Serialize the Send message + let mut buf = Vec::with_capacity(msg.encoded_len()); + if let Err(e) = msg.encode(&mut buf) { + return Err(StdError::generic_err(format!("Encode error: {e}")).into()); + } + + Ok(ProtobufAny { + type_url: "/cosmos.bank.v1beta1.MsgSend".to_string(), + value: Binary::from(buf), + }) +} + +pub fn to_proto_msg_multi_send(msg: impl Message) -> NeutronResult { + // Serialize the Send message + let mut buf = Vec::with_capacity(msg.encoded_len()); + if let Err(e) = msg.encode(&mut buf) { + return Err(StdError::generic_err(format!("Encode error: {e}")).into()); + } + + Ok(ProtobufAny { + type_url: "/cosmos.bank.v1beta1.MsgMultiSend".to_string(), + value: Binary::from(buf), + }) +} + +#[cw_serde] +pub struct MinIbcFeeConfig { + pub ibc_fee: IbcFee, + pub total_ntrn_fee: Uint128, +} + +pub fn query_ibc_fee(querier: QuerierWrapper<'_, NeutronQuery>) -> StdResult { + let min_fee_query_response: MinIbcFeeResponse = + querier.query(&NeutronQuery::MinIbcFee {}.into())?; + let total_fee_amount = flatten_ibc_fee_total_amount(&min_fee_query_response.min_fee); + + Ok(MinIbcFeeConfig { + ibc_fee: min_fee_query_response.min_fee, + total_ntrn_fee: total_fee_amount, + }) +} + +pub fn flatten_ibc_fee_total_amount(ibc_fee: &IbcFee) -> Uint128 { + let mut total_amount = Uint128::zero(); + + for coin in &ibc_fee.recv_fee { + total_amount += coin.amount; + } + + for coin in &ibc_fee.ack_fee { + total_amount += coin.amount; + } + + for coin in &ibc_fee.timeout_fee { + total_amount += coin.amount; + } + + total_amount +} + +/// assertion helper that checks if the caller has covered ibc fees +/// for `count` number of transactions +pub fn assert_ibc_fee_coverage( + info: MessageInfo, + total_fee: Uint128, + count: Uint128, +) -> StdResult<()> { + // the caller must cover the ibc fees + match must_pay(&info, "untrn") { + Ok(amt) => { + if amt < total_fee.checked_mul(count)? { + Err(StdError::generic_err("insufficient fees")) + } else { + Ok(()) + } + } + Err(_) => Err(StdError::generic_err( + "must cover ibc fees to distribute fallback denoms", + )), + } +} diff --git a/packages/covenant-utils/src/polytone.rs b/packages/covenant-utils/src/polytone.rs new file mode 100644 index 00000000..e0d3ada1 --- /dev/null +++ b/packages/covenant-utils/src/polytone.rs @@ -0,0 +1,66 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{ + to_json_binary, Binary, CosmosMsg, Empty, QuerierWrapper, QueryRequest, StdError, StdResult, + Uint64, +}; +use neutron_sdk::bindings::query::NeutronQuery; +use polytone::callbacks::CallbackRequest; + +#[cw_serde] +pub enum PolytoneExecuteMsg { + Query { + msgs: Vec>, + callback: CallbackRequest, + timeout_seconds: Uint64, + }, + Execute { + msgs: Vec>, + callback: Option, + timeout_seconds: Uint64, + }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum PolytoneQueryMsg { + #[returns(Option)] + RemoteAddress { local_address: String }, + #[returns(Uint64)] + BlockMaxGas, +} + +pub fn get_polytone_execute_msg_binary( + msgs: Vec, + callback: Option, + timeout_seconds: Uint64, +) -> StdResult { + let execute_msg = PolytoneExecuteMsg::Execute { + msgs, + callback, + timeout_seconds, + }; + to_json_binary(&execute_msg) +} + +pub fn get_polytone_query_msg_binary( + msgs: Vec>, + callback: CallbackRequest, + timeout_seconds: Uint64, +) -> StdResult { + let query_msg = PolytoneExecuteMsg::Query { + msgs, + callback, + timeout_seconds, + }; + to_json_binary(&query_msg) +} + +pub fn query_polytone_proxy_address( + local_address: String, + note_address: String, + querier: QuerierWrapper, +) -> Result, StdError> { + let remote_address_query = PolytoneQueryMsg::RemoteAddress { local_address }; + + querier.query_wasm_smart(note_address, &remote_address_query) +} diff --git a/packages/covenant-utils/src/split.rs b/packages/covenant-utils/src/split.rs new file mode 100644 index 00000000..e04d1e88 --- /dev/null +++ b/packages/covenant-utils/src/split.rs @@ -0,0 +1,161 @@ +use std::collections::BTreeMap; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + Api, Attribute, BankMsg, Coin, CosmosMsg, Decimal, Fraction, StdError, StdResult, Uint128, +}; + +#[cw_serde] +pub struct SplitConfig { + /// map receiver address to its share of the split + pub receivers: BTreeMap, +} + +impl SplitConfig { + pub fn remap_receivers_to_routers( + &self, + receiver_a: String, + router_a: String, + receiver_b: String, + router_b: String, + ) -> Result { + let mut new_receivers = BTreeMap::new(); + + match self.receivers.get(&receiver_a) { + Some(val) => new_receivers.insert(router_a, *val), + None => { + return Err(StdError::not_found(format!( + "receiver {receiver_a:?} not found" + ))) + } + }; + match self.receivers.get(&receiver_b) { + Some(val) => new_receivers.insert(router_b, *val), + None => { + return Err(StdError::not_found(format!( + "receiver {receiver_b:?} not found" + ))) + } + }; + + Ok(SplitConfig { + receivers: new_receivers, + }) + } + + pub fn validate(&self, party_a: &str, party_b: &str) -> Result<(), StdError> { + let share_a = match self.receivers.get(party_a) { + Some(val) => *val, + None => return Err(StdError::not_found(party_a)), + }; + let share_b = match self.receivers.get(party_b) { + Some(val) => *val, + None => return Err(StdError::not_found(party_b)), + }; + + if share_a + share_b != Decimal::one() { + return Err(StdError::generic_err( + "shares must add up to 1.0".to_string(), + )); + } + + Ok(()) + } + + /// Validate that all shares are added to one + pub fn validate_shares_and_receiver_addresses(&self, api: &dyn Api) -> Result<(), StdError> { + let mut total_shares = Decimal::zero(); + + for (addr, share) in self.receivers.clone() { + api.addr_validate(&addr)?; + total_shares += share; + } + + if total_shares != Decimal::one() { + return Err(StdError::generic_err( + "shares must add up to 1.0".to_string(), + )); + } + + Ok(()) + } + + pub fn get_transfer_messages( + &self, + amount: Uint128, + denom: String, + filter_addr: Option, + ) -> Result, StdError> { + let msgs: Result, StdError> = self + .receivers + .iter() + .map(|(addr, share)| { + // if we are filtering for a single receiver, + // then we wish to transfer only to that receiver. + // we thus set receiver share to 1.0, as the + // entitlement already takes that into account. + match &filter_addr { + Some(filter) => { + if filter == addr { + (addr, Decimal::one()) + } else { + (addr, Decimal::zero()) + } + } + None => (addr, *share), + } + }) + .filter(|(_, share)| !share.is_zero()) + .map(|(addr, share)| { + let entitlement = amount + .checked_multiply_ratio(share.numerator(), share.denominator()) + .map_err(|_| StdError::generic_err("failed to checked_multiply".to_string()))?; + + let amount = Coin { + denom: denom.to_string(), + amount: entitlement, + }; + + Ok(CosmosMsg::Bank(BankMsg::Send { + to_address: addr.to_string(), + amount: vec![amount], + })) + }) + .collect(); + + msgs + } + + pub fn get_response_attribute(&self, denom: String) -> Attribute { + let mut receivers = "[".to_string(); + self.receivers.iter().for_each(|(receiver, share)| { + receivers.push('('); + receivers.push_str(receiver); + receivers.push(':'); + receivers.push_str(&share.to_string()); + receivers.push_str("),"); + }); + receivers.push(']'); + Attribute::new(denom, receivers) + } +} + +pub fn remap_splits( + splits: BTreeMap, + (party_a_receiver, party_a_router): (String, String), + (party_b_receiver, party_b_router): (String, String), +) -> StdResult> { + let mut remapped_splits: BTreeMap = BTreeMap::new(); + + for (denom, split) in splits.iter() { + let remapped_split = split.remap_receivers_to_routers( + party_a_receiver.clone(), + party_a_router.clone(), + party_b_receiver.clone(), + party_b_router.clone(), + )?; + remapped_splits.insert(denom.clone(), remapped_split); + } + + Ok(remapped_splits) +} diff --git a/packages/covenant-utils/src/withdraw_lp_helper.rs b/packages/covenant-utils/src/withdraw_lp_helper.rs new file mode 100644 index 00000000..3306e6ec --- /dev/null +++ b/packages/covenant-utils/src/withdraw_lp_helper.rs @@ -0,0 +1,23 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_json_binary, Addr, Decimal, StdError, WasmMsg}; +use covenant_macros::{covenant_holder_distribute, covenant_lper_withdraw}; +use cw_storage_plus::Item; + +/// Emergency committee address +pub const EMERGENCY_COMMITTEE_ADDR: Item = Item::new("e_c_a"); + +#[covenant_lper_withdraw] +#[covenant_holder_distribute] +#[cw_serde] +pub enum WithdrawLPMsgs {} + +pub fn generate_withdraw_msg( + contract_addr: String, + percentage: Option, +) -> Result { + Ok(WasmMsg::Execute { + contract_addr, + msg: to_json_binary(&WithdrawLPMsgs::Withdraw { percentage })?, + funds: vec![], + }) +} diff --git a/packages/cw-fifo/Cargo.toml b/packages/cw-fifo/Cargo.toml index e0d14126..8c676e49 100644 --- a/packages/cw-fifo/Cargo.toml +++ b/packages/cw-fifo/Cargo.toml @@ -1,13 +1,12 @@ [package] -name = "cw-fifo" -version = "0.0.1" -edition = "2021" -authors = ["ekez "] +name = "cw-fifo" +version = { workspace = true } +edition = { workspace = true } +authors = ["ekez "] description = "A CosmWasm FIFO queue with nice runtime and no size limits." -license = { workspace = true } +license = { workspace = true } [dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } +cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } -serde = { workspace = true } +serde = { workspace = true } diff --git a/schemagen.sh b/schemagen.sh new file mode 100755 index 00000000..90ff77b3 --- /dev/null +++ b/schemagen.sh @@ -0,0 +1,9 @@ +#!/bin/bash +cd contracts/ +for contract in `ls ./`; do + echo "generating schema for ${contract}" + cd ${contract}/ + cargo run schema + rm -rf ./schema/raw + cd ../ +done diff --git a/scripts/schema.sh b/scripts/schema.sh deleted file mode 100755 index 9b67bd3b..00000000 --- a/scripts/schema.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -START_DIR=$(pwd); \ -for f in ../contracts/*; do \ - echo "generating schema"; \ - cd "$f"; \ - CMD="cargo run --example schema"; \ - eval ${CMD} > /dev/null; \ - rm -rf ./schema/raw; \ - cd "$START_DIR"; \ -done \ No newline at end of file diff --git a/stride-covenant/tests/interchaintest/go.sum b/stride-covenant/tests/interchaintest/go.sum deleted file mode 100644 index 8a8ef94a..00000000 --- a/stride-covenant/tests/interchaintest/go.sum +++ /dev/null @@ -1,1308 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= -filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= -github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= -github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= -github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= -github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= -github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= -github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= -github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/StirlingMarketingGroup/go-namecase v1.0.0 h1:2CzaNtCzc4iNHirR+5ru9OzGg8rQp860gqLBFqRI02Y= -github.com/StirlingMarketingGroup/go-namecase v1.0.0/go.mod h1:ZsoSKcafcAzuBx+sndbxHu/RjDcDTrEdT4UvhniHfio= -github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= -github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= -github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= -github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -github.com/avast/retry-go/v4 v4.0.4 h1:38hLf0DsRXh+hOF6HbTni0+5QGTNdw9zbaMD7KAO830= -github.com/avast/retry-go/v4 v4.0.4/go.mod h1:HqmLvS2VLdStPCGDFjSuZ9pzlTqVRldCI4w2dO4m1Ms= -github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= -github.com/aws/aws-sdk-go-v2/config v1.1.1/go.mod h1:0XsVy9lBI/BCXm+2Tuvt39YmdHwS5unDQmxZOYe8F5Y= -github.com/aws/aws-sdk-go-v2/credentials v1.1.1/go.mod h1:mM2iIjwl7LULWtS6JCACyInboHirisUUdkBPoTHMOUo= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2/go.mod h1:3hGg3PpiEjHnrkrlasTfxFqUsZ2GCk/fMUn4CbKgSkM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2/go.mod h1:45MfaXZ0cNbeuT0KQ1XJylq8A6+OpVV2E5kvY/Kq+u8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= -github.com/aws/aws-sdk-go-v2/service/sso v1.1.1/go.mod h1:SuZJxklHxLAXgLTc1iFXbEWkXs7QRTQpCLGaKIprQW0= -github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxqTCJGUfYTWXrrBwkq736bM= -github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= -github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= -github.com/btcsuite/btcd/btcec/v2 v2.1.2 h1:YoYoC9J0jwfukodSBMzZYUVQ8PTiYg4BnOWiJVzTmLs= -github.com/btcsuite/btcd/btcec/v2 v2.1.2/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.4 h1:G2kCJurlIkguX0oxxI9sPPENuQqMVhIhV9RVkh/dpDg= -github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.4/go.mod h1:5g1oM4Zu3BOaLpsKQ+O8PAv2kNuq+kPcA1VzFbsSqxE= -github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coinbase/rosetta-sdk-go v0.7.9 h1:lqllBjMnazTjIqYrOGv8h8jxjg9+hJazIGZr9ZvoCcA= -github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= -github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= -github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/3mZuaj6Sj+PqrmIquiOKy397AKGThQPaGzNXAQ= -github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cosmos/btcutil v1.0.4 h1:n7C2ngKXo7UC9gNyMNLbzqz7Asuf+7Qv4gnX/rOdQ44= -github.com/cosmos/btcutil v1.0.4/go.mod h1:Ffqc8Hn6TJUdDgHBwIZLtrLQC1KdJ9jGJl/TvgUaxbU= -github.com/cosmos/cosmos-sdk v0.45.11-ics h1:nTxsjRKwP93V3eR9rEUIVaDuEYYJ8n8GyugX4sxyF/c= -github.com/cosmos/cosmos-sdk v0.45.11-ics/go.mod h1:knmcuR38eVg8I8U+AYhTtSLtcsHzYCROaUCbNcUgfDU= -github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= -github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= -github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= -github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= -github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= -github.com/cosmos/iavl v0.19.4 h1:t82sN+Y0WeqxDLJRSpNd8YFX5URIrT+p8n6oJbJ2Dok= -github.com/cosmos/iavl v0.19.4/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= -github.com/cosmos/ibc-go/v3 v3.4.0 h1:ha3cqEG36pqMWqA1D+kxDWBTZXpeFMd/aZIQF7I0xro= -github.com/cosmos/ibc-go/v3 v3.4.0/go.mod h1:VwB/vWu4ysT5DN2aF78d17LYmx3omSAdq6gpKvM7XRA= -github.com/cosmos/interchain-security v1.0.0-rc2 h1:t0TwFvOz9otKVtvt37qPrFoBiJSR2l58YmtXxHLYLiQ= -github.com/cosmos/interchain-security v1.0.0-rc2/go.mod h1:BjzK0YVO4NaOD8Q0+m4RHuhb3o9aMUgurU8YBMEGtJ4= -github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= -github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= -github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= -github.com/cosmos/ledger-go v0.9.3 h1:WGyZK4ikuLIkbxJm3lEr1tdQYDdTdveTwoVla7hqfhQ= -github.com/cosmos/ledger-go v0.9.3/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= -github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= -github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= -github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= -github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= -github.com/decred/base58 v1.0.3 h1:KGZuh8d1WEMIrK0leQRM47W85KqCAdl2N+uagbctdDI= -github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E= -github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU= -github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0 h1:3GIJYXQDAKpLEFriGFN8SbSffak10UXHGdIcFaMPykY= -github.com/decred/dcrd/dcrec/secp256k1/v2 v2.0.0/go.mod h1:3s92l0paYkZoIHuj4X93Teg/HB7eGM9x/zokGw+u4mY= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= -github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= -github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= -github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= -github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= -github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= -github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= -github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.19+incompatible h1:lzEmjivyNHFHMNAFLXORMBXyGIhw/UP4DvJwvyKYq64= -github.com/docker/docker v20.10.19+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac h1:opbrjaN/L8gg6Xh5D04Tem+8xVcz6ajZlGCs49mQgyg= -github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= -github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.17 h1:XEcumY+qSr1cZQaWsQs5Kck3FHB0V2RiMHPdTBJ+oT8= -github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0= -github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= -github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= -github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= -github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= -github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= -github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= -github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= -github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= -github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= -github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= -github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= -github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= -github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= -github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= -github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= -github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= -github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 h1:aSVUgRRRtOrZOC1fYmY9gV0e9z/Iu+xNVSASWjsuyGU= -github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3/go.mod h1:5PC6ZNPde8bBqU/ewGZig35+UIZtw9Ytxez8/q5ZyFE= -github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/icza/dyno v0.0.0-20220812133438-f0b6f8a18845 h1:H+uM0Bv88eur3ZSsd2NGKg3YIiuXxwxtlN7HjE66UTU= -github.com/icza/dyno v0.0.0-20220812133438-f0b6f8a18845/go.mod h1:c1tRKs5Tx7E2+uHGSyyncziFjvGpgv4H2HrqXeUQ/Uk= -github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= -github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= -github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= -github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= -github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= -github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= -github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= -github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= -github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= -github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= -github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= -github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= -github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= -github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= -github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= -github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= -github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/libp2p/go-libp2p-core v0.15.1 h1:0RY+Mi/ARK9DgG1g9xVQLb8dDaaU8tCePMtGALEfBnM= -github.com/libp2p/go-libp2p-core v0.15.1/go.mod h1:agSaboYM4hzB1cWekgVReqV5M4g5M+2eNNejV+1EEhs= -github.com/libp2p/go-openssl v0.0.7 h1:eCAzdLejcNVBzP/iZM9vqHnQm+XyCEbSSIheIPRGNsw= -github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= -github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= -github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 h1:QRUSJEgZn2Snx0EmT/QLXibWjSUDjKWvXIT19NBVp94= -github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= -github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= -github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= -github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= -github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= -github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= -github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= -github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= -github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI= -github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= -github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= -github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= -github.com/multiformats/go-multiaddr v0.4.1 h1:Pq37uLx3hsyNlTDir7FZyU8+cFCTqd5y1KiM2IzOutI= -github.com/multiformats/go-multiaddr v0.4.1/go.mod h1:3afI9HfVW8csiF8UZqtpYRiDyew8pRX7qLIGHu9FLuM= -github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= -github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= -github.com/multiformats/go-multicodec v0.4.1 h1:BSJbf+zpghcZMZrwTYBGwy0CPcVZGWiC72Cp8bBd4R4= -github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= -github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= -github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= -github.com/multiformats/go-multihash v0.1.0 h1:CgAgwqk3//SVEw3T+6DqI4mWMyRuDwZtOWcJT0q9+EA= -github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= -github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= -github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= -github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= -github.com/oxyno-zeta/gomock-extra-matcher v1.1.0 h1:Yyk5ov0ZPKBXtVEeIWtc4J2XVrHuNoIK+0F2BUJgtsc= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= -github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/xxHash v0.1.5 h1:n/jBpwTHiER4xYvK3/CdPVnLDPchj8eTJFFLUb4QHBo= -github.com/pierrec/xxHash v0.1.5/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I= -github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0/go.mod h1:4xpMLz7RBWyB+ElzHu8Llua96TRCB3YwX+l5EP1wmHk= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= -github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/regen-network/cosmos-proto v0.3.1 h1:rV7iM4SSFAagvy8RiyhiACbWEGotmqzywPxOvwMdxcg= -github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNwWejXC7QqCOnH3O0+YM= -github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= -github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= -github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= -github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= -github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= -github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= -github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= -github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= -github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= -github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= -github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/strangelove-ventures/go-subkey v1.0.7 h1:cOP/Lajg3uxV/tvspu0m6+0Cu+DJgygkEAbx/s+f35I= -github.com/strangelove-ventures/go-subkey v1.0.7/go.mod h1:E34izOIEm+sZ1YmYawYRquqBQWeZBjVB4pF7bMuhc1c= -github.com/strangelove-ventures/interchaintest/v3 v3.0.0-20230424185430-002b69e57bc7 h1:i24vHO7fE+VmQooI9AeLFdkyYAb/r1bBpx7fRwZUchI= -github.com/strangelove-ventures/interchaintest/v3 v3.0.0-20230424185430-002b69e57bc7/go.mod h1:u+NjmNJ/zYQ6y+P/n+/wGVDD225fBBC054fqeXLtrqo= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= -github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= -github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= -github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= -github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RMWx1aInLzndwxKalgi5rTqgfXxOxbEI= -github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= -github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= -github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= -github.com/tendermint/tendermint v0.34.24 h1:879MKKJWYYPJEMMKME+DWUTY4V9f/FBpnZDI82ky+4k= -github.com/tendermint/tendermint v0.34.24/go.mod h1:rXVrl4OYzmIa1I91av3iLv2HS0fGSiucyW9J4aMTpKI= -github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= -github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= -github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -github.com/zondax/hid v0.9.1 h1:gQe66rtmyZ8VeGFcOpbuH3r7erYtNEAezCAYu8LdkJo= -github.com/zondax/hid v0.9.1/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e h1:S9GbmC1iCgvbLyAokVCwiO6tVIrU9Y7c5oMx1V/ki/Y= -google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk= -google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= -lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= -lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.36.0 h1:0kmRkTmqNidmu3c7BNDSdVHCxXCkWLmWmCIVX4LUboo= -modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= -modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= -modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= -modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccgo/v3 v3.16.6 h1:3l18poV+iUemQ98O3X5OMr97LOqlzis+ytivU4NqGhA= -modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= -modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= -modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= -modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= -modernc.org/libc v1.16.7 h1:qzQtHhsZNpVPpeCu+aMIQldXeV1P0vRhSqCL0nOIJOA= -modernc.org/libc v1.16.7/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= -modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU= -modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= -modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.17.3 h1:iE+coC5g17LtByDYDWKpR6m2Z9022YrSh3bumwOnIrI= -modernc.org/sqlite v1.17.3/go.mod h1:10hPVYar9C0kfXuTWGz8s0XtB8uAGymUy51ZzStYe3k= -modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao= -modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= -modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM= -modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= -nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/unit-tests/Cargo.toml b/unit-tests/Cargo.toml new file mode 100644 index 00000000..bb4d53dd --- /dev/null +++ b/unit-tests/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "unit-tests" # the name of the package +version = "0.1.0" # the current version, obeying semver +authors = ["Art3miX ", "benskey bekauz@protonmail.com"] +edition = "2021" + +[dev-dependencies] +cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +cw-multi-test = { workspace = true } +neutron-sdk = { workspace = true } +sha2 = { workspace = true } +prost = { workspace = true } +prost-types = { workspace = true } +colored = "1.9.4" +const_format = "0.2.32" +cosmos-sdk-proto = { workspace = true } + +valence-remote-chain-splitter = { workspace = true } +valence-ibc-forwarder = { workspace = true } +valence-clock = { workspace = true } +valence-stride-liquid-staker = { workspace = true } +valence-native-splitter = { workspace = true } +valence-swap-holder = { workspace = true } +valence-covenant-swap = { workspace = true } +valence-interchain-router = { workspace = true } +valence-two-party-pol-holder = { workspace = true } +valence-covenant-two-party-pol = { workspace = true } +valence-astroport-liquid-pooler = { workspace = true } +valence-native-router = { workspace = true } +valence-outpost-osmo-liquid-pooler = { workspace = true } +valence-covenant-single-party-pol = { workspace = true } +valence-single-party-pol-holder = { workspace = true } +valence-osmo-liquid-pooler = { workspace = true } + +covenant-utils = { workspace = true } + +# astroport stuff +cw20 = { workspace = true } +cw20-base = { workspace = true } +cw1-whitelist = { workspace = true } +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", rev = "700f66d" } +astroport-token = { workspace = true } +astroport-whitelist = { workspace = true } +astroport-factory = { workspace = true } +astroport-native-coin-registry = { workspace = true } +astroport-pair-stable = { workspace = true } +astroport-pair = { workspace = true } +astroport-pair-concentrated = { workspace = true } + +osmosis-std = "0.13.2" diff --git a/unit-tests/src/lib.rs b/unit-tests/src/lib.rs new file mode 100644 index 00000000..2d6b3e53 --- /dev/null +++ b/unit-tests/src/lib.rs @@ -0,0 +1,32 @@ +#![allow(clippy::too_many_arguments)] +#![allow(dead_code)] + +#[cfg(test)] +pub mod setup; + +#[cfg(test)] +pub mod test_astroport_liquid_pooler; +#[cfg(test)] +pub mod test_ibc_forwarder; +#[cfg(test)] +pub mod test_interchain_router; +#[cfg(test)] +pub mod test_native_router; +#[cfg(test)] +pub mod test_native_splitter; +#[cfg(test)] +pub mod test_osmo_lp_outpost; +#[cfg(test)] +pub mod test_remote_chain_splitter; +#[cfg(test)] +pub mod test_single_party_covenant; +#[cfg(test)] +pub mod test_single_party_holder; +#[cfg(test)] +pub mod test_swap_covenant; +#[cfg(test)] +pub mod test_swap_holder; +#[cfg(test)] +pub mod test_two_party_covenant; +#[cfg(test)] +pub mod test_two_party_pol_holder; diff --git a/unit-tests/src/setup/astro_contracts.rs b/unit-tests/src/setup/astro_contracts.rs new file mode 100644 index 00000000..5d6937be --- /dev/null +++ b/unit-tests/src/setup/astro_contracts.rs @@ -0,0 +1,327 @@ +use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, Reply}; +use cw_multi_test::{Contract, ContractWrapper}; +use neutron_sdk::bindings::{msg::NeutronMsg, query::NeutronQuery}; + +use super::contracts::{execute_into_neutron, get_empty_deps, get_empty_depsmut}; + +pub fn astro_token_contract() -> Box> { + let exec = + |deps: DepsMut, env: Env, info: MessageInfo, msg: cw20::Cw20ExecuteMsg| { + execute_into_neutron(astroport_token::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::token::InstantiateMsg| { + execute_into_neutron(astroport_token::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, env: Env, msg: cw20_base::msg::QueryMsg| { + astroport_token::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = |deps: DepsMut, env: Env, msg: astroport::token::MigrateMsg| { + execute_into_neutron(astroport_token::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + Box::new(ContractWrapper::new(exec, init, query).with_migrate(migrate)) +} + +pub fn astro_whitelist_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: cw1_whitelist::msg::ExecuteMsg| { + execute_into_neutron(astroport_whitelist::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: cw1_whitelist::msg::InstantiateMsg| { + execute_into_neutron(astroport_whitelist::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, env: Env, msg: cw1_whitelist::msg::QueryMsg| { + astroport_whitelist::contract::query(get_empty_deps(deps), env, msg) + }; + + Box::new(ContractWrapper::new(exec, init, query)) +} + +pub fn astro_factory_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::factory::ExecuteMsg| { + execute_into_neutron(astroport_factory::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::factory::InstantiateMsg| { + execute_into_neutron(astroport_factory::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, env: Env, msg: astroport::factory::QueryMsg| { + astroport_factory::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = |deps: DepsMut, env: Env, msg: astroport::factory::MigrateMsg| { + execute_into_neutron(astroport_factory::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let reply = |deps: DepsMut, env: Env, reply: Reply| { + execute_into_neutron(astroport_factory::contract::reply( + get_empty_depsmut(deps), + env, + reply, + )) + }; + + Box::new( + ContractWrapper::new(exec, init, query) + .with_migrate(migrate) + .with_reply(reply), + ) +} + +pub fn astro_pair_stable_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::pair::ExecuteMsg| { + execute_into_neutron(astroport_pair_stable::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::pair::InstantiateMsg| { + execute_into_neutron(astroport_pair_stable::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, env: Env, msg: astroport::pair::QueryMsg| { + astroport_pair_stable::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = |deps: DepsMut, env: Env, msg: astroport::pair::MigrateMsg| { + execute_into_neutron(astroport_pair_stable::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let reply = |deps: DepsMut, env: Env, reply: Reply| { + execute_into_neutron(astroport_pair_stable::contract::reply( + get_empty_depsmut(deps), + env, + reply, + )) + }; + + Box::new( + ContractWrapper::new(exec, init, query) + .with_migrate(migrate) + .with_reply(reply), + ) +} + +pub fn astro_pair_custom_concentrated_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::pair::ExecuteMsg| { + execute_into_neutron(astroport_pair_concentrated::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::pair::InstantiateMsg| { + execute_into_neutron(astroport_pair_concentrated::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = + |deps: Deps, env: Env, msg: astroport::pair_concentrated::QueryMsg| { + astroport_pair_concentrated::queries::query(get_empty_deps(deps), env, msg) + }; + + let migrate = + |deps: DepsMut, env: Env, msg: astroport::pair_concentrated::MigrateMsg| { + execute_into_neutron(astroport_pair_concentrated::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let reply = |deps: DepsMut, env: Env, reply: Reply| { + execute_into_neutron(astroport_pair_concentrated::contract::reply( + get_empty_depsmut(deps), + env, + reply, + )) + }; + + Box::new( + ContractWrapper::new(exec, init, query) + .with_migrate(migrate) + .with_reply(reply), + ) +} + +pub fn astro_pair_xyk_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::pair::ExecuteMsg| { + execute_into_neutron(astroport_pair::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::pair::InstantiateMsg| { + execute_into_neutron(astroport_pair::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, env: Env, msg: astroport::pair::QueryMsg| { + astroport_pair::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = |deps: DepsMut, env: Env, msg: astroport::pair::MigrateMsg| { + execute_into_neutron(astroport_pair::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let reply = |deps: DepsMut, env: Env, reply: Reply| { + execute_into_neutron(astroport_pair::contract::reply( + get_empty_depsmut(deps), + env, + reply, + )) + }; + + Box::new( + ContractWrapper::new(exec, init, query) + .with_migrate(migrate) + .with_reply(reply), + ) +} + +pub fn astro_coin_registry_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::native_coin_registry::ExecuteMsg| { + execute_into_neutron(astroport_native_coin_registry::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: astroport::native_coin_registry::InstantiateMsg| { + execute_into_neutron(astroport_native_coin_registry::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, env: Env, msg: cw20_base::msg::QueryMsg| { + astroport_token::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = |deps: DepsMut, + env: Env, + msg: astroport::native_coin_registry::MigrateMsg| { + execute_into_neutron(astroport_native_coin_registry::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + Box::new(ContractWrapper::new(exec, init, query).with_migrate(migrate)) +} diff --git a/unit-tests/src/setup/base_suite.rs b/unit-tests/src/setup/base_suite.rs new file mode 100644 index 00000000..ab005ad8 --- /dev/null +++ b/unit-tests/src/setup/base_suite.rs @@ -0,0 +1,82 @@ +use cosmwasm_std::{Addr, Coin}; +use cw_multi_test::{AppResponse, Executor}; + +use super::{CustomApp, ADMIN}; + +pub trait BaseSuiteMut { + fn get_app(&mut self) -> &mut CustomApp; + fn get_clock_addr(&mut self) -> Addr; + fn get_faucet_addr(&mut self) -> Addr; + + fn tick_clock_debug(&mut self) { + let clock_addr = self.get_clock_addr(); + let app = self.get_app(); + + let res = app + .execute_contract( + app.api().addr_make(ADMIN), + clock_addr, + &valence_clock::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap(); + + println!("res: {:?}", res); + } + + fn tick(&mut self, msg: &str) -> AppResponse { + println!("Tick: {}", msg); + let clock_addr = self.get_clock_addr(); + let app = self.get_app(); + + let res = app + .execute_contract( + app.api().addr_make(ADMIN), + clock_addr, + &valence_clock::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap(); + res + } + + fn tick_contract(&mut self, contract: Addr) -> AppResponse { + let clock_addr = self.get_clock_addr(); + let app = self.get_app(); + + app.execute_contract( + clock_addr, + contract, + &valence_clock::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap() + } + + fn fund_contract(&mut self, amount: &[Coin], to: Addr) { + let faucet = self.get_faucet_addr().clone(); + let app = self.get_app(); + + app.send_tokens(faucet, to, amount).unwrap(); + } +} + +pub trait BaseSuite { + fn get_app(&self) -> &CustomApp; + + fn query_balance(&self, addr: &Addr, denom: &str) -> Coin { + let app = self.get_app(); + app.wrap().query_balance(addr, denom).unwrap() + } + + fn query_all_balances(&self, addr: &Addr) -> Vec { + let app = self.get_app(); + app.wrap().query_all_balances(addr).unwrap() + } + + fn assert_balance(&self, addr: impl Into, coin: Coin) { + let app = self.get_app(); + let bal = app.wrap().query_balance(addr, &coin.denom).unwrap(); + assert_eq!(bal, coin); + } +} diff --git a/unit-tests/src/setup/contracts.rs b/unit-tests/src/setup/contracts.rs new file mode 100644 index 00000000..beed17e0 --- /dev/null +++ b/unit-tests/src/setup/contracts.rs @@ -0,0 +1,596 @@ +use std::fmt::Display; + +use cosmwasm_std::{ + CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, StdError, SubMsg, +}; +use cw_multi_test::{Contract, ContractWrapper}; +use neutron_sdk::bindings::{msg::NeutronMsg, query::NeutronQuery}; + +/// Turn a neutron response into an empty response +/// This is fine because the contract return an empty response, but our testing enviroment expects a neutron response +/// the contract that uses this function will never emit a neutron response anyways +pub(crate) fn execute_into_neutron( + into: Result, +) -> Result, E> { + into.map(|r| { + let mut res: Response = Response::::default(); + res.data = r.data; + res.messages = r + .messages + .into_iter() + .map(|m| { + let msg: CosmosMsg = match m.msg { + CosmosMsg::Bank(b) => CosmosMsg::::Bank(b), + CosmosMsg::Staking(s) => CosmosMsg::::Staking(s), + CosmosMsg::Distribution(d) => CosmosMsg::::Distribution(d), + CosmosMsg::Stargate { type_url, value } => { + CosmosMsg::::Stargate { type_url, value } + } + CosmosMsg::Ibc(ibc) => CosmosMsg::::Ibc(ibc), + CosmosMsg::Wasm(w) => CosmosMsg::::Wasm(w), + CosmosMsg::Gov(g) => CosmosMsg::::Gov(g), + _ => CosmosMsg::::Custom(NeutronMsg::RemoveSchedule { + name: "".to_string(), + }), + }; + + SubMsg:: { + id: m.id, + msg, + gas_limit: m.gas_limit, + reply_on: m.reply_on, + } + }) + .collect(); + res.attributes = r.attributes; + res + }) + // .map_err(|e| NeutronError::Std(StdError::GenericErr { msg: e.to_string() })) +} + +/// Turn neutron DepsMut into empty DepsMut +pub(crate) fn get_empty_depsmut(deps: DepsMut) -> DepsMut<'_, Empty> { + DepsMut { + storage: deps.storage, + api: deps.api, + querier: deps.querier.into_empty(), + } +} + +/// Turn neutron Deps into empty Deps +pub(crate) fn get_empty_deps(deps: Deps) -> Deps<'_, Empty> { + Deps { + storage: deps.storage, + api: deps.api, + querier: deps.querier.into_empty(), + } +} + +pub fn clock_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_clock::msg::ExecuteMsg| { + execute_into_neutron(valence_clock::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_clock::msg::InstantiateMsg| { + execute_into_neutron(valence_clock::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, env: Env, msg: valence_clock::msg::QueryMsg| { + valence_clock::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = |deps: DepsMut, env: Env, msg: valence_clock::msg::MigrateMsg| { + execute_into_neutron(valence_clock::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let reply = |deps: DepsMut, env: Env, reply: Reply| { + execute_into_neutron(valence_clock::contract::reply( + get_empty_depsmut(deps), + env, + reply, + )) + }; + + let contract = ContractWrapper::new(exec, init, query) + .with_migrate(migrate) + .with_reply(reply); + Box::new(contract) +} + +pub fn ibc_forwarder_contract() -> Box> { + let contract = ContractWrapper::new( + valence_ibc_forwarder::contract::execute, + valence_ibc_forwarder::contract::instantiate, + valence_ibc_forwarder::contract::query, + ) + .with_reply(valence_ibc_forwarder::contract::reply) + .with_sudo(valence_ibc_forwarder::contract::sudo) + .with_migrate(valence_ibc_forwarder::contract::migrate); + Box::new(contract) +} + +pub fn interchain_router_contract() -> Box> { + let contract = ContractWrapper::new( + valence_interchain_router::contract::execute, + valence_interchain_router::contract::instantiate, + valence_interchain_router::contract::query, + ) + .with_migrate(valence_interchain_router::contract::migrate); + Box::new(contract) +} + +pub fn remote_splitter_contract() -> Box> { + let contract = ContractWrapper::new( + valence_remote_chain_splitter::contract::execute, + valence_remote_chain_splitter::contract::instantiate, + valence_remote_chain_splitter::contract::query, + ) + .with_reply(valence_remote_chain_splitter::contract::reply) + .with_sudo(valence_remote_chain_splitter::contract::sudo) + .with_migrate(valence_remote_chain_splitter::contract::migrate); + Box::new(contract) +} + +pub fn osmo_lp_outpost_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_outpost_osmo_liquid_pooler::msg::ExecuteMsg| { + execute_into_neutron(valence_outpost_osmo_liquid_pooler::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_outpost_osmo_liquid_pooler::msg::InstantiateMsg| { + execute_into_neutron(valence_outpost_osmo_liquid_pooler::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let reply = |deps: DepsMut, env: Env, reply: Reply| { + execute_into_neutron(valence_outpost_osmo_liquid_pooler::contract::reply( + get_empty_depsmut(deps), + env, + reply, + )) + }; + + let query = |deps: Deps, + env: Env, + msg: valence_outpost_osmo_liquid_pooler::msg::QueryMsg| { + valence_outpost_osmo_liquid_pooler::contract::query(get_empty_deps(deps), env, msg) + }; + + Box::new(ContractWrapper::new(exec, init, query).with_reply(reply)) +} + +pub fn native_router_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_native_router::msg::ExecuteMsg| { + execute_into_neutron(valence_native_router::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_native_router::msg::InstantiateMsg| { + execute_into_neutron(valence_native_router::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, env: Env, msg: valence_native_router::msg::QueryMsg| { + valence_native_router::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = + |deps: DepsMut, env: Env, msg: valence_native_router::msg::MigrateMsg| { + execute_into_neutron(valence_native_router::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let contract = ContractWrapper::new(exec, init, query).with_migrate(migrate); + Box::new(contract) +} + +pub fn native_splitter_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_native_splitter::msg::ExecuteMsg| { + execute_into_neutron(valence_native_splitter::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_native_splitter::msg::InstantiateMsg| { + execute_into_neutron(valence_native_splitter::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = + |deps: Deps, env: Env, msg: valence_native_splitter::msg::QueryMsg| { + valence_native_splitter::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = + |deps: DepsMut, env: Env, msg: valence_native_splitter::msg::MigrateMsg| { + execute_into_neutron(valence_native_splitter::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let contract = ContractWrapper::new(exec, init, query).with_migrate(migrate); + Box::new(contract) +} + +pub fn single_party_covenant_contract() -> Box> { + let exec = |_deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty| + -> Result, StdError> { + Err(StdError::generic_err("Execute msg is not implemented")) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_covenant_single_party_pol::msg::InstantiateMsg| { + execute_into_neutron(valence_covenant_single_party_pol::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, + env: Env, + msg: valence_covenant_single_party_pol::msg::QueryMsg| { + valence_covenant_single_party_pol::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = |deps: DepsMut, + env: Env, + msg: valence_covenant_single_party_pol::msg::MigrateMsg| { + execute_into_neutron(valence_covenant_single_party_pol::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let contract = ContractWrapper::new(exec, init, query).with_migrate(migrate); + Box::new(contract) +} + +pub fn single_party_holder_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_single_party_pol_holder::msg::ExecuteMsg| { + execute_into_neutron(valence_single_party_pol_holder::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_single_party_pol_holder::msg::InstantiateMsg| { + execute_into_neutron(valence_single_party_pol_holder::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, + env: Env, + msg: valence_single_party_pol_holder::msg::QueryMsg| { + valence_single_party_pol_holder::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = |deps: DepsMut, + env: Env, + msg: valence_single_party_pol_holder::msg::MigrateMsg| { + execute_into_neutron(valence_single_party_pol_holder::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let contract = ContractWrapper::new(exec, init, query).with_migrate(migrate); + Box::new(contract) +} + +pub fn stride_lser_contract() -> Box> { + let contract = ContractWrapper::new( + valence_stride_liquid_staker::contract::execute, + valence_stride_liquid_staker::contract::instantiate, + valence_stride_liquid_staker::contract::query, + ) + .with_reply(valence_stride_liquid_staker::contract::reply) + .with_sudo(valence_stride_liquid_staker::contract::sudo) + .with_migrate(valence_stride_liquid_staker::contract::migrate); + Box::new(contract) +} + +pub fn swap_covenant_contract() -> Box> { + let exec = |_deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty| + -> Result, StdError> { + Err(StdError::generic_err("Execute msg is not implemented")) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_covenant_swap::msg::InstantiateMsg| { + execute_into_neutron(valence_covenant_swap::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, env: Env, msg: valence_covenant_swap::msg::QueryMsg| { + valence_covenant_swap::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = + |deps: DepsMut, env: Env, msg: valence_covenant_swap::msg::MigrateMsg| { + execute_into_neutron(valence_covenant_swap::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let contract = ContractWrapper::new(exec, init, query).with_migrate(migrate); + Box::new(contract) +} + +pub fn swap_holder_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_swap_holder::msg::ExecuteMsg| { + execute_into_neutron(valence_swap_holder::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_swap_holder::msg::InstantiateMsg| { + execute_into_neutron(valence_swap_holder::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, env: Env, msg: valence_swap_holder::msg::QueryMsg| { + valence_swap_holder::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = + |deps: DepsMut, env: Env, msg: valence_swap_holder::msg::MigrateMsg| { + execute_into_neutron(valence_swap_holder::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let contract = ContractWrapper::new(exec, init, query).with_migrate(migrate); + Box::new(contract) +} + +pub fn two_party_covenant_contract() -> Box> { + let exec = |_deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty| + -> Result, StdError> { + Err(StdError::generic_err("Execute msg is not implemented")) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_covenant_two_party_pol::msg::InstantiateMsg| { + execute_into_neutron(valence_covenant_two_party_pol::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = + |deps: Deps, env: Env, msg: valence_covenant_two_party_pol::msg::QueryMsg| { + valence_covenant_two_party_pol::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = |deps: DepsMut, + env: Env, + msg: valence_covenant_two_party_pol::msg::MigrateMsg| { + execute_into_neutron(valence_covenant_two_party_pol::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let contract = ContractWrapper::new(exec, init, query).with_migrate(migrate); + Box::new(contract) +} + +pub fn two_party_holder_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_two_party_pol_holder::msg::ExecuteMsg| { + execute_into_neutron(valence_two_party_pol_holder::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_two_party_pol_holder::msg::InstantiateMsg| { + execute_into_neutron(valence_two_party_pol_holder::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = + |deps: Deps, env: Env, msg: valence_two_party_pol_holder::msg::QueryMsg| { + valence_two_party_pol_holder::contract::query(get_empty_deps(deps), env, msg) + }; + + let migrate = |deps: DepsMut, + env: Env, + msg: valence_two_party_pol_holder::msg::MigrateMsg| { + execute_into_neutron(valence_two_party_pol_holder::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let contract = ContractWrapper::new(exec, init, query).with_migrate(migrate); + Box::new(contract) +} + +pub fn astroport_pooler_contract() -> Box> { + let exec = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_astroport_liquid_pooler::msg::ExecuteMsg| { + execute_into_neutron(valence_astroport_liquid_pooler::contract::execute( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let init = |deps: DepsMut, + env: Env, + info: MessageInfo, + msg: valence_astroport_liquid_pooler::msg::InstantiateMsg| { + execute_into_neutron(valence_astroport_liquid_pooler::contract::instantiate( + get_empty_depsmut(deps), + env, + info, + msg, + )) + }; + + let query = |deps: Deps, + env: Env, + msg: valence_astroport_liquid_pooler::msg::QueryMsg| { + valence_astroport_liquid_pooler::contract::query(get_empty_deps(deps), env, msg) + }; + + let reply = |deps: DepsMut, env: Env, reply: Reply| { + execute_into_neutron(valence_astroport_liquid_pooler::contract::reply( + get_empty_depsmut(deps), + env, + reply, + )) + }; + + let migrate = |deps: DepsMut, + env: Env, + msg: valence_astroport_liquid_pooler::msg::MigrateMsg| { + execute_into_neutron(valence_astroport_liquid_pooler::contract::migrate( + get_empty_depsmut(deps), + env, + msg, + )) + }; + + let contract = ContractWrapper::new(exec, init, query) + .with_reply(reply) + .with_migrate(migrate); + Box::new(contract) +} diff --git a/unit-tests/src/setup/custom_keepers.rs b/unit-tests/src/setup/custom_keepers.rs new file mode 100644 index 00000000..70328405 --- /dev/null +++ b/unit-tests/src/setup/custom_keepers.rs @@ -0,0 +1,199 @@ +use cosmwasm_schema::serde::de::DeserializeOwned; +use cosmwasm_schema::serde::Serialize; +use cosmwasm_std::{ + from_json, to_json_binary, Addr, Api, Binary, BlockInfo, CustomMsg, CustomQuery, Querier, + Storage, +}; +use covenant_utils::ica::{Params, QueryParamsResponse}; +use cw_multi_test::error::{AnyError, AnyResult}; +use cw_multi_test::{AppResponse, CosmosRouter, Module, StargateQuery}; +use osmosis_std::types::cosmos::base::v1beta1::Coin; +use osmosis_std::types::osmosis::gamm::v1beta1::{ + PoolAsset, QueryCalcExitPoolCoinsFromSharesResponse, QueryCalcJoinPoolNoSwapSharesResponse, + QueryCalcJoinPoolSharesResponse, QueryPoolResponse, +}; +use prost::Message; + +use std::fmt::Debug; +use std::marker::PhantomData; + +use crate::setup::DENOM_LS_ATOM_ON_NTRN; + +use super::{DENOM_ATOM, DENOM_FALLBACK}; + +pub struct CustomStargateKeeper( + PhantomData<(ExecT, QueryT, SudoT)>, + &'static str, + &'static str, + &'static str, +); + +impl CustomStargateKeeper { + pub fn new(execute_msg: &'static str, query_msg: &'static str, sudo_msg: &'static str) -> Self { + Self(Default::default(), execute_msg, query_msg, sudo_msg) + } +} + +impl Module for CustomStargateKeeper +where + ExecT: Debug + Serialize, + QueryT: Debug + Serialize, + SudoT: Debug, +{ + type ExecT = ExecT; + type QueryT = QueryT; + type SudoT = SudoT; + + fn execute( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _sender: Addr, + _msg: Self::ExecT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + Ok(AppResponse::default()) + } + + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _msg: Self::SudoT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + Ok(AppResponse::default()) + } + + fn query( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + _block: &BlockInfo, + request: QueryT, + ) -> AnyResult { + let query: StargateQuery = from_json(to_json_binary(&request).unwrap()).unwrap(); + // TODO: these mocks should be configurable on top-level SuiteBuilder config, pre build + if query.path == "/neutron.interchaintxs.v1.Query/Params" { + let response = QueryParamsResponse { + params: Params { + msg_submit_tx_max_messages: cosmwasm_std::Uint64::new(1000), + register_fee: vec![cosmwasm_std::Coin { + amount: cosmwasm_std::Uint128::new(1000000), + denom: "untrn".to_string(), + }], + }, + }; + + return Ok(to_json_binary(&response).unwrap()); + } + + if query.path == "/osmosis.gamm.v1beta1.Query/Pool" { + let pool = osmosis_std::types::osmosis::gamm::v1beta1::Pool { + address: "address".to_string(), + id: 1, + pool_params: None, + future_pool_governor: "governor".to_string(), + total_shares: Some(Coin { + amount: "101010".to_string(), + denom: DENOM_FALLBACK.to_string(), + }), + pool_assets: vec![ + PoolAsset { + token: Some(Coin { + amount: "100".to_string(), + denom: DENOM_ATOM.to_string(), + }), + weight: "50".to_string(), + }, + PoolAsset { + token: Some(Coin { + amount: "100".to_string(), + denom: DENOM_LS_ATOM_ON_NTRN.to_string(), + }), + weight: "50".to_string(), + }, + ], + total_weight: "123123".to_string(), + }; + + let pool_shim = osmosis_std::shim::Any { + type_url: "/osmosis.gamm.v1beta1.Pool".to_string(), + value: pool.encode_to_vec(), + }; + + let response = QueryPoolResponse { + pool: Some(pool_shim), + }; + + return Ok(to_json_binary(&response).unwrap()); + } + + if query.path == "/osmosis.gamm.v1beta1.Query/CalcExitPoolCoinsFromShares" { + let tokens_out = vec![ + Coin { + amount: "1".to_string(), + denom: DENOM_ATOM.to_string(), + }, + Coin { + amount: "1".to_string(), + denom: DENOM_LS_ATOM_ON_NTRN.to_string(), + }, + ]; + let response = QueryCalcExitPoolCoinsFromSharesResponse { tokens_out }; + + return Ok(to_json_binary(&response).unwrap()); + } + + if query.path == "/osmosis.gamm.v1beta1.Query/CalcJoinPoolShares" { + let tokens_out = vec![ + Coin { + amount: "1".to_string(), + denom: DENOM_ATOM.to_string(), + }, + Coin { + amount: "1".to_string(), + denom: DENOM_LS_ATOM_ON_NTRN.to_string(), + }, + ]; + let response = QueryCalcJoinPoolSharesResponse { + tokens_out, + share_out_amount: "1".to_string(), + }; + + return Ok(to_json_binary(&response).unwrap()); + } + + if query.path == "/osmosis.gamm.v1beta1.Query/CalcJoinPoolNoSwapShares" { + let tokens_out = vec![ + Coin { + amount: "1".to_string(), + denom: DENOM_ATOM.to_string(), + }, + Coin { + amount: "1".to_string(), + denom: DENOM_LS_ATOM_ON_NTRN.to_string(), + }, + ]; + let response = QueryCalcJoinPoolNoSwapSharesResponse { + tokens_out, + shares_out: "1".to_string(), + }; + + return Ok(to_json_binary(&response).unwrap()); + } + + Err(AnyError::msg(self.2)) + } +} diff --git a/unit-tests/src/setup/custom_module.rs b/unit-tests/src/setup/custom_module.rs new file mode 100644 index 00000000..466b8755 --- /dev/null +++ b/unit-tests/src/setup/custom_module.rs @@ -0,0 +1,872 @@ +use std::{collections::BTreeMap, str::FromStr}; + +use cosmwasm_std::{ + coin, coins, from_json, to_json_binary, to_json_string, Addr, BalanceResponse, BankMsg, + BankQuery, StdError, StdResult, Storage, Uint128, +}; +use cw_multi_test::{ + addons::MockApiBech32, + error::{bail, AnyError}, + prefixed_storage::{prefixed, prefixed_read}, + AppResponse, BankSudo, Module, WasmSudo, +}; +use cw_storage_plus::Map; +use neutron_sdk::{ + bindings::{ + msg::{MsgSubmitTxResponse, NeutronMsg}, + query::NeutronQuery, + }, + interchain_txs::helpers::get_port_id, + query::min_ibc_fee::MinIbcFeeResponse, + sudo::msg::RequestPacket, +}; +use prost::Message; +use valence_ibc_forwarder::helpers::MsgTransfer; +use valence_stride_liquid_staker::helpers::Autopilot; + +use super::DENOM_NTRN; + +pub const CHAIN_PREFIX: &str = "cosmos"; + +/// Namespace for neutron storage +pub const NAMESPACE_NEUTRON: &[u8] = b"neutron_storage"; + +/// Map for (sender, conn_id) => account_id +const ACCOUNTS: Map<(&Addr, String, String), Addr> = Map::new("accounts"); + +const LOCAL_CHANNELS: Map = Map::new("local_channels"); +const LOCAL_CHANNELS_VALUES: Map = Map::new("local_channels_values"); + +const REMOTE_CHANNELS: Map = Map::new("remote_channels"); +const REMOTE_CHANNELS_VALUES: Map = Map::new("remote_channels_values"); + +pub trait Neutron: + Module +{ +} + +pub struct NeutronKeeper { + api: MockApiBech32, + account_timeout: bool, +} + +impl Neutron for NeutronKeeper {} + +impl NeutronKeeper { + pub fn new(prefix: &'static str) -> Self { + Self { + api: MockApiBech32::new(prefix), + account_timeout: false, + } + } + + /// Sets our timeout flag, so the next message will return a timeout response instead of a successful response + pub fn set_timeout(&mut self, timeout: bool) { + self.account_timeout = timeout; + } + + pub fn add_local_channel( + &mut self, + storage: &mut dyn Storage, + source_channel: &str, + other_channel: &str, + ) -> Result<(), StdError> { + LOCAL_CHANNELS.save( + storage, + source_channel.to_string(), + &other_channel.to_string(), + )?; + LOCAL_CHANNELS_VALUES.save( + storage, + other_channel.to_string(), + &source_channel.to_string(), + )?; + Ok(()) + } + + pub fn add_remote_channel( + &mut self, + storage: &mut dyn Storage, + some_channel: &str, + other_channel: &str, + ) -> Result<(), StdError> { + REMOTE_CHANNELS.save( + storage, + some_channel.to_string(), + &other_channel.to_string(), + )?; + REMOTE_CHANNELS_VALUES.save( + storage, + other_channel.to_string(), + &some_channel.to_string(), + )?; + Ok(()) + } +} + +impl NeutronKeeper { + fn register_account( + &self, + storage: &mut dyn Storage, + sender: Addr, + conn_id: String, + account_id: String, + ) -> Result<(), AnyError> { + let mut ntrn_storage = prefixed(storage, NAMESPACE_NEUTRON); + + if ACCOUNTS.has( + &ntrn_storage, + (&sender, conn_id.clone(), account_id.clone()), + ) { + bail!("Account already registered"); + } + + let addr = self + .api + .addr_make(format!("{sender}_{conn_id}_{account_id}").as_str()); + + ACCOUNTS + .save( + &mut ntrn_storage, + (&sender, conn_id.clone(), account_id.clone()), + &addr, + ) + .unwrap(); + Ok(()) + } + + fn remove_account( + &self, + storage: &mut dyn Storage, + sender: &Addr, + conn_id: String, + account_id: String, + ) { + let mut ntrn_storage = prefixed(storage, NAMESPACE_NEUTRON); + + ACCOUNTS.remove(&mut ntrn_storage, (sender, conn_id, account_id)) + } + + fn get_account( + &self, + storage: &dyn Storage, + sender: &Addr, + conn_id: &str, + account_id: &str, + ) -> StdResult { + let ntrn_storage = prefixed_read(storage, NAMESPACE_NEUTRON); + + ACCOUNTS.load( + &ntrn_storage, + (sender, conn_id.to_string(), account_id.to_string()), + ) + } +} + +impl Module for NeutronKeeper { + type ExecT = NeutronMsg; + type QueryT = NeutronQuery; + type SudoT = neutron_sdk::sudo::msg::SudoMsg; + + /// Currently we only implement register ICA and ibcTransfer and SubmitTx, + /// maybe we should implement other stuff as well? + fn execute( + &self, + api: &dyn cosmwasm_std::Api, + storage: &mut dyn cosmwasm_std::Storage, + router: &dyn cw_multi_test::CosmosRouter, + block: &cosmwasm_std::BlockInfo, + sender: cosmwasm_std::Addr, + msg: Self::ExecT, + ) -> cw_multi_test::error::AnyResult + where + ExecC: std::fmt::Debug + + Clone + + PartialEq + + cosmwasm_schema::schemars::JsonSchema + + cosmwasm_schema::serde::de::DeserializeOwned + + 'static, + QueryC: cosmwasm_std::CustomQuery + cosmwasm_schema::serde::de::DeserializeOwned + 'static, + { + match msg { + NeutronMsg::RegisterInterchainAccount { + connection_id, + interchain_account_id, + register_fee, + } => { + // Send fees to fee burner + // we do it mainly to make sure fees are deducted in our tests + let fee = match register_fee { + Some(fee) => fee, + None => bail!("No register fee specified"), + }; + + let fee_msg = cosmwasm_std::BankMsg::Burn { amount: fee }; + + router.execute(api, storage, block, sender.clone(), fee_msg.into())?; + + // Save the account in our storage for later use + self.register_account( + storage, + sender.clone(), + connection_id.clone(), + interchain_account_id.clone(), + )?; + + // Complete the registration by calling the sudo entry on the contract + router.sudo( + api, + storage, + block, + cw_multi_test::SudoMsg::Wasm(WasmSudo { + contract_addr: sender.clone(), + msg: to_json_binary(&neutron_sdk::sudo::msg::SudoMsg::OpenAck { + port_id: get_port_id(sender.to_string(), interchain_account_id.clone()), + channel_id: "channel-1".to_string(), + counterparty_channel_id: "channel-1".to_string(), + counterparty_version: to_json_string( + &covenant_utils::neutron::OpenAckVersion { + version: "ica".to_string(), + controller_connection_id: connection_id.clone(), + host_connection_id: connection_id.clone(), + address: self + .api + .addr_make( + format!( + "{sender}_{connection_id}_{interchain_account_id}" + ) + .as_str(), + ) + .to_string(), + encoding: "encoding".to_string(), + tx_type: "tx_type".to_string(), + }, + ) + .unwrap(), + }) + .unwrap(), + }), + )?; + + Ok(AppResponse::default()) + } + // TODO: Handle multiple PFM hops + NeutronMsg::IbcTransfer { + source_port: _, + source_channel, + token, + sender: local_sender, + receiver, + timeout_height: _, + timeout_timestamp: _, + memo, + fee, + } => { + let local_sender = api.addr_validate(&local_sender)?; + + // Burn fees first + router.execute( + api, + storage, + block, + local_sender.clone(), + BankMsg::Burn { + amount: fee.ack_fee, + } + .into(), + )?; + + // Get the counterparty channel of the local channel if it exists + let Ok(counterparty_channel) = LOCAL_CHANNELS.load(storage, source_channel.clone()) + else { + bail!("Local channel doesn't exist") + }; + + // Handle the denom to include or remove the correct prefix + let mut handled_denom = match try_pop_denom_prefix(&source_channel, &token.denom) { + Some(poped_denom) => poped_denom.to_string(), + None => format!("{counterparty_channel}/{}", token.denom), + }; + + // Handle pfm stuff here, mainly handle denom and get the receiver + let receiver = if let Ok(pfm) = from_json::(memo) { + match pfm.forward { + Some(forward) => { + let new_prefix = + if LOCAL_CHANNELS_VALUES.has(storage, forward.channel.clone()) { + let local_string = LOCAL_CHANNELS_VALUES + .load(storage, forward.channel.clone())?; + + // Make sure the current string doesn't equal to the string we send on, + if local_string == source_channel { + bail!("PFM target channel is equal to the sending channel") + } + + local_string + } else if REMOTE_CHANNELS.has(storage, forward.channel.clone()) { + REMOTE_CHANNELS.load(storage, forward.channel.clone())? + } else { + REMOTE_CHANNELS_VALUES.load(storage, forward.channel.clone())? + }; + + // Add prefix if needed + handled_denom = + match try_pop_denom_prefix(&forward.channel, &handled_denom) { + Some(poped_denom) => poped_denom.to_string(), + None => format!("{new_prefix}/{}", handled_denom), + }; + + forward.receiver + } + None => receiver, + } + } else { + receiver + }; + + // Burn the existing tokens + router.execute( + api, + storage, + block, + local_sender.clone(), + BankMsg::Burn { + amount: vec![token.clone()], + } + .into(), + )?; + + // We mint the IBC token to the sender, so we can do a transfer alter + router.sudo( + api, + storage, + block, + BankSudo::Mint { + to_address: local_sender.to_string(), + amount: coins(token.amount.u128(), handled_denom.clone()), + } + .into(), + )?; + + // Do the bank transfer + router.execute( + api, + storage, + block, + local_sender, + BankMsg::Send { + to_address: receiver, + amount: coins(token.amount.u128(), handled_denom), + } + .into(), + )?; + + Ok(AppResponse::default()) + } + NeutronMsg::SubmitTx { + connection_id, + interchain_account_id, + msgs, + memo: _, + timeout: _, + fee, + } => { + // Return timeout response if we have a timeout flag + if self.account_timeout { + router.sudo( + api, + storage, + block, + cw_multi_test::SudoMsg::Wasm(WasmSudo { + contract_addr: sender.clone(), + msg: to_json_binary(&neutron_sdk::sudo::msg::SudoMsg::Timeout { + request: RequestPacket { + sequence: Some(1), + source_port: None, + source_channel: Some("some_channel".to_string()), + destination_port: None, + destination_channel: None, + data: None, + timeout_height: None, + timeout_timestamp: None, + }, + }) + .unwrap(), + }), + )?; + + self.remove_account(storage, &sender, connection_id, interchain_account_id); + + return Ok(AppResponse { + data: Some( + to_json_binary(&MsgSubmitTxResponse { + sequence_id: 1, + channel: "some_channel".to_string(), + }) + .unwrap(), + ), + events: vec![], + }); + } + + let account = self.get_account( + storage, + &sender, + connection_id.as_str(), + interchain_account_id.as_str(), + )?; + + // // Burn fees first + router.execute( + api, + storage, + block, + sender.clone(), + BankMsg::Burn { + amount: fee.ack_fee, + } + .into(), + )?; + + for msg in msgs { + match msg.type_url.as_str() { + "/ibc.applications.transfer.v1.MsgTransfer" => { + let msg: MsgTransfer = + Message::decode(msg.value.clone().as_slice()).unwrap(); + + let token = match msg.token { + Some(token) => Ok(coin( + Uint128::from_str(token.amount.as_str()).unwrap().u128(), + token.denom, + )), + None => Err(StdError::generic_err("No token specified")), + } + .unwrap(); + + let prefix = + if LOCAL_CHANNELS_VALUES.has(storage, msg.source_channel.clone()) { + LOCAL_CHANNELS_VALUES + .load(storage, msg.source_channel.clone()) + .unwrap() + } else if REMOTE_CHANNELS.has(storage, msg.source_channel.clone()) { + REMOTE_CHANNELS + .load(storage, msg.source_channel.clone()) + .unwrap() + } else { + REMOTE_CHANNELS_VALUES + .load(storage, msg.source_channel.clone()) + .unwrap() + }; + + let handled_denom = match try_pop_denom_prefix( + &msg.source_channel, + token.denom.as_str(), + ) { + Some(denom) => denom.to_string(), + None => format!("{prefix}/{}", token.denom), + }; + + // TODO: Handle PFM + let mut receiver = msg.receiver; + + // We handle stride autopilot here, no denom change to ls token + // we just assume that if the token passed stride, it's a ls token + // This allows us to skip staking/unstaking the token, and just assume + // the ibc token is the ls token + if let Ok(autopilot) = from_json::(msg.memo) { + receiver = autopilot.autopilot.stakeibc.stride_address; + } + + // Burn the existing tokens + if let Err(err) = router.execute( + api, + storage, + block, + account.clone(), + BankMsg::Burn { + amount: vec![token.clone()], + } + .into(), + ) { + router + .sudo( + api, + storage, + block, + cw_multi_test::SudoMsg::Wasm(WasmSudo { + contract_addr: sender.clone(), + msg: to_json_binary( + &neutron_sdk::sudo::msg::SudoMsg::Error { + request: RequestPacket { + sequence: Some(1), + source_port: None, + source_channel: Some( + "some_channel".to_string(), + ), + destination_port: None, + destination_channel: None, + data: None, + timeout_height: None, + timeout_timestamp: None, + }, + details: err.to_string(), + }, + ) + .unwrap(), + }), + ) + .unwrap(); + + return Ok(AppResponse { + data: Some( + to_json_binary(&MsgSubmitTxResponse { + sequence_id: 1, + channel: "some_channel".to_string(), + }) + .unwrap(), + ), + events: vec![], + }); + }; + + // We mint the IBC token to the sender, so we can do a transfer later + router + .sudo( + api, + storage, + block, + BankSudo::Mint { + to_address: sender.to_string(), + amount: coins(token.amount.u128(), handled_denom.clone()), + } + .into(), + ) + .unwrap(); + + // Do the bank transfer + router + .execute( + api, + storage, + block, + sender.clone(), + BankMsg::Send { + to_address: receiver, + amount: coins(token.amount.u128(), handled_denom), + } + .into(), + ) + .unwrap(); + + Ok(()) + } + + "/cosmos.bank.v1beta1.MsgSend" => { + let msg: cosmos_sdk_proto::cosmos::bank::v1beta1::MsgSend = + Message::decode(msg.value.clone().as_slice()).unwrap(); + + if let Err(err) = router + .execute( + api, + storage, + block, + account.clone(), + BankMsg::Send { + to_address: msg.to_address.clone(), + amount: msg.amount + .iter() + .map(|c| { + coin( + Uint128::from_str(&c.amount) + .unwrap() + .u128(), + c.denom.clone(), + ) + }) + .collect(), + } + .into(), + ) { + router + .sudo( + api, + storage, + block, + cw_multi_test::SudoMsg::Wasm(WasmSudo { + contract_addr: sender.clone(), + msg: to_json_binary( + &neutron_sdk::sudo::msg::SudoMsg::Error { + request: RequestPacket { + sequence: Some(1), + source_port: None, + source_channel: Some( + "some_channel".to_string(), + ), + destination_port: None, + destination_channel: None, + data: None, + timeout_height: None, + timeout_timestamp: None, + }, + details: err.to_string(), + }, + ) + .unwrap(), + }), + ) + .unwrap(); + + return Ok(AppResponse { + data: Some( + to_json_binary(&MsgSubmitTxResponse { + sequence_id: 1, + channel: "some_channel".to_string(), + }) + .unwrap(), + ), + events: vec![], + }); + }; + + Ok(()) + }, + "/cosmos.bank.v1beta1.MsgMultiSend" => { + let msg: cosmos_sdk_proto::cosmos::bank::v1beta1::MsgMultiSend = + Message::decode(msg.value.clone().as_slice()).unwrap(); + + // first verify we have enough funds to send it all + let mut needed_amount: BTreeMap = BTreeMap::new(); + + for output in msg.outputs.clone() { + for coin in output.coins { + let amount = Uint128::from_str(&coin.amount).unwrap(); + + match needed_amount.get_key_value(&coin.denom) { + Some((_, a)) => { + needed_amount.insert( + coin.denom, + amount.checked_add(*a).unwrap(), + ); + } + None => { + needed_amount.insert(coin.denom, amount); + } + } + } + } + + for (denom, am) in needed_amount { + let curr_balance = from_json::( + router + .query( + api, + storage, + block, + BankQuery::Balance { + address: account.to_string(), + denom, + } + .into(), + ) + .unwrap(), + ) + .unwrap(); + + if curr_balance.amount.amount < am { + router + .sudo( + api, + storage, + block, + cw_multi_test::SudoMsg::Wasm(WasmSudo { + contract_addr: sender.clone(), + msg: to_json_binary( + &neutron_sdk::sudo::msg::SudoMsg::Error { + request: RequestPacket { + sequence: Some(1), + source_port: None, + source_channel: Some( + "some_channel".to_string(), + ), + destination_port: None, + destination_channel: None, + data: None, + timeout_height: None, + timeout_timestamp: None, + }, + details: format!("Not enough balance: Current = {curr_balance:?} | Required = {am}", ), + }, + ) + .unwrap(), + }), + ) + .unwrap(); + + return Ok(AppResponse { + data: Some( + to_json_binary(&MsgSubmitTxResponse { + sequence_id: 1, + channel: "some_channel".to_string(), + }) + .unwrap(), + ), + events: vec![], + }); + } + } + + // After we verified we have enough funds, we can safely transfer them + for output in msg.outputs { + router + .execute( + api, + storage, + block, + account.clone(), + BankMsg::Send { + to_address: output.address.clone(), + amount: output + .coins + .iter() + .map(|c| { + coin( + Uint128::from_str(&c.amount) + .unwrap() + .u128(), + c.denom.clone(), + ) + }) + .collect(), + } + .into(), + ) + .unwrap(); + } + Ok(()) + } + _ => Err(StdError::generic_err("Unknown message type")), + } + .unwrap(); + } + + // Complete the registration by calling the sudo entry on the contract + router.sudo( + api, + storage, + block, + cw_multi_test::SudoMsg::Wasm(WasmSudo { + contract_addr: sender.clone(), + msg: to_json_binary(&neutron_sdk::sudo::msg::SudoMsg::Response { + request: RequestPacket { + sequence: Some(1), + source_port: None, + source_channel: Some("some_channel".to_string()), + destination_port: None, + destination_channel: None, + data: Some(to_json_binary("").unwrap()), + timeout_height: None, + timeout_timestamp: None, + }, + data: to_json_binary("").unwrap(), + }) + .unwrap(), + }), + )?; + + Ok(AppResponse { + data: Some( + to_json_binary(&MsgSubmitTxResponse { + sequence_id: 1, + channel: "some_channel".to_string(), + }) + .unwrap(), + ), + events: vec![], + }) + } + + NeutronMsg::RegisterInterchainQuery { .. } => unimplemented!(), + NeutronMsg::UpdateInterchainQuery { .. } => unimplemented!(), + NeutronMsg::RemoveInterchainQuery { .. } => unimplemented!(), + NeutronMsg::SubmitAdminProposal { .. } => unimplemented!(), + NeutronMsg::CreateDenom { .. } => unimplemented!(), + NeutronMsg::ChangeAdmin { .. } => unimplemented!(), + NeutronMsg::MintTokens { .. } => unimplemented!(), + NeutronMsg::BurnTokens { .. } => unimplemented!(), + NeutronMsg::SetBeforeSendHook { .. } => unimplemented!(), + NeutronMsg::AddSchedule { .. } => unimplemented!(), + NeutronMsg::RemoveSchedule { .. } => unimplemented!(), + NeutronMsg::ResubmitFailure { .. } => unimplemented!(), + } + } + + fn query( + &self, + _api: &dyn cosmwasm_std::Api, + storage: &dyn cosmwasm_std::Storage, + _querier: &dyn cosmwasm_std::Querier, + _block: &cosmwasm_std::BlockInfo, + request: Self::QueryT, + ) -> cw_multi_test::error::AnyResult { + match request { + NeutronQuery::InterchainAccountAddress { + owner_address, + interchain_account_id, + connection_id, + } => Ok(to_json_binary( + &self + .get_account( + storage, + &Addr::unchecked(owner_address), + connection_id.as_str(), + interchain_account_id.as_str(), + ) + .unwrap(), + ) + .unwrap()), + NeutronQuery::MinIbcFee {} => Ok(to_json_binary(&MinIbcFeeResponse { + min_fee: neutron_sdk::bindings::msg::IbcFee { + recv_fee: vec![], + ack_fee: vec![coin(10000, DENOM_NTRN)], + timeout_fee: vec![coin(10000, DENOM_NTRN)], + }, + }) + .unwrap()), + NeutronQuery::InterchainQueryResult { .. } => unimplemented!(), + NeutronQuery::RegisteredInterchainQueries { .. } => unimplemented!(), + NeutronQuery::RegisteredInterchainQuery { .. } => unimplemented!(), + NeutronQuery::TotalBurnedNeutronsAmount {} => unimplemented!(), + NeutronQuery::FullDenom { .. } => unimplemented!(), + NeutronQuery::DenomAdmin { .. } => unimplemented!(), + NeutronQuery::BeforeSendHook { .. } => unimplemented!(), + NeutronQuery::Failures { .. } => unimplemented!(), + } + } + + fn sudo( + &self, + _api: &dyn cosmwasm_std::Api, + _storage: &mut dyn cosmwasm_std::Storage, + _router: &dyn cw_multi_test::CosmosRouter, + _block: &cosmwasm_std::BlockInfo, + _msg: Self::SudoT, + ) -> cw_multi_test::error::AnyResult + where + ExecC: std::fmt::Debug + + Clone + + PartialEq + + cosmwasm_schema::schemars::JsonSchema + + cosmwasm_schema::serde::de::DeserializeOwned + + 'static, + QueryC: cosmwasm_std::CustomQuery + cosmwasm_schema::serde::de::DeserializeOwned + 'static, + { + bail!("No sudo messages") + } +} + +/// Try to remove the channel prefix from a denom +/// If removed, it means denom came from the channel (ibc denom) +/// If not removed, it means the denom didn't came from the channel +fn try_pop_denom_prefix<'a>(prefix: &'a str, denom: &'a str) -> Option<&'a str> { + denom.strip_prefix(format!("{prefix}/").as_str()) +} diff --git a/unit-tests/src/setup/instantiates/astro_liquid_pooler.rs b/unit-tests/src/setup/instantiates/astro_liquid_pooler.rs new file mode 100644 index 00000000..c78c92de --- /dev/null +++ b/unit-tests/src/setup/instantiates/astro_liquid_pooler.rs @@ -0,0 +1,114 @@ +use astroport::factory::PairType; +use cosmwasm_std::{Decimal, Uint128}; +use covenant_utils::{PoolPriceConfig, SingleSideLpLimits}; + +use crate::setup::{DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN}; + +#[derive(Clone)] +pub struct AstroLiquidPoolerInstantiate { + pub msg: valence_astroport_liquid_pooler::msg::InstantiateMsg, +} + +impl From for valence_astroport_liquid_pooler::msg::InstantiateMsg { + fn from(value: AstroLiquidPoolerInstantiate) -> Self { + value.msg + } +} + +impl AstroLiquidPoolerInstantiate { + pub fn new( + pool_address: String, + clock_address: String, + slippage_tolerance: Option, + assets: valence_astroport_liquid_pooler::msg::AssetData, + single_side_lp_limits: SingleSideLpLimits, + pool_price_config: PoolPriceConfig, + pair_type: PairType, + holder_address: String, + ) -> Self { + Self { + msg: valence_astroport_liquid_pooler::msg::InstantiateMsg { + pool_address, + clock_address, + slippage_tolerance, + assets, + single_side_lp_limits, + pool_price_config, + pair_type, + holder_address, + }, + } + } + + pub fn with_pool_address(&mut self, pool_address: String) -> &mut Self { + self.msg.pool_address = pool_address; + self + } + + pub fn with_clock_address(&mut self, clock_address: String) -> &mut Self { + self.msg.clock_address = clock_address; + self + } + + pub fn with_slippage_tolerance(&mut self, slippage_tolerance: Option) -> &mut Self { + self.msg.slippage_tolerance = slippage_tolerance; + self + } + + pub fn with_assets( + &mut self, + assets: valence_astroport_liquid_pooler::msg::AssetData, + ) -> &mut Self { + self.msg.assets = assets; + self + } + + pub fn with_single_side_lp_limits( + &mut self, + single_side_lp_limits: SingleSideLpLimits, + ) -> &mut Self { + self.msg.single_side_lp_limits = single_side_lp_limits; + self + } + + pub fn with_pool_price_config(&mut self, pool_price_config: PoolPriceConfig) -> &mut Self { + self.msg.pool_price_config = pool_price_config; + self + } + + pub fn with_pair_type(&mut self, pair_type: PairType) -> &mut Self { + self.msg.pair_type = pair_type; + self + } + + pub fn with_holder_address(&mut self, holder_address: String) -> &mut Self { + self.msg.holder_address = holder_address; + self + } +} + +impl AstroLiquidPoolerInstantiate { + pub fn default(pool_address: String, clock_address: String, holder_address: String) -> Self { + Self { + msg: valence_astroport_liquid_pooler::msg::InstantiateMsg { + pool_address, + clock_address, + slippage_tolerance: None, + assets: valence_astroport_liquid_pooler::msg::AssetData { + asset_a_denom: DENOM_ATOM_ON_NTRN.to_string(), + asset_b_denom: DENOM_LS_ATOM_ON_NTRN.to_string(), + }, + single_side_lp_limits: SingleSideLpLimits { + asset_a_limit: Uint128::new(100000), + asset_b_limit: Uint128::new(100000), + }, + pool_price_config: PoolPriceConfig { + expected_spot_price: Decimal::one(), + acceptable_price_spread: Decimal::from_ratio(Uint128::one(), Uint128::new(2)), + }, + pair_type: PairType::Stable {}, + holder_address, + }, + } + } +} diff --git a/unit-tests/src/setup/instantiates/clock.rs b/unit-tests/src/setup/instantiates/clock.rs new file mode 100644 index 00000000..5eeda271 --- /dev/null +++ b/unit-tests/src/setup/instantiates/clock.rs @@ -0,0 +1,50 @@ +use cosmwasm_std::Uint64; + +use crate::setup::suite_builder::SuiteBuilder; + +#[derive(Clone)] +pub struct ClockInstantiate { + pub msg: valence_clock::msg::InstantiateMsg, +} + +impl From for valence_clock::msg::InstantiateMsg { + fn from(value: ClockInstantiate) -> Self { + value.msg + } +} + +impl ClockInstantiate { + pub fn new(tick_max_gas: Option, whitelist: Vec) -> Self { + Self { + msg: valence_clock::msg::InstantiateMsg { + tick_max_gas, + whitelist, + }, + } + } + + pub fn with_tick_max_gas(&mut self, gas: Uint64) -> &mut Self { + self.msg.tick_max_gas = Some(gas); + self + } + + pub fn with_whitelist(&mut self, whitelist: Vec) -> &mut Self { + self.msg.whitelist = whitelist; + self + } +} + +impl ClockInstantiate { + pub fn default( + _builder: &SuiteBuilder, + tick_max_gas: Option, + whitelist: Vec, + ) -> Self { + Self { + msg: valence_clock::msg::InstantiateMsg { + tick_max_gas, + whitelist, + }, + } + } +} diff --git a/unit-tests/src/setup/instantiates/ibc_forwarder.rs b/unit-tests/src/setup/instantiates/ibc_forwarder.rs new file mode 100644 index 00000000..2474bf0b --- /dev/null +++ b/unit-tests/src/setup/instantiates/ibc_forwarder.rs @@ -0,0 +1,104 @@ +use cosmwasm_std::{Uint128, Uint64}; + +use crate::setup::{DENOM_ATOM_ON_NTRN, NTRN_HUB_CHANNEL}; + +pub struct IbcForwarderInstantiate { + pub msg: valence_ibc_forwarder::msg::InstantiateMsg, +} + +impl From for valence_ibc_forwarder::msg::InstantiateMsg { + fn from(value: IbcForwarderInstantiate) -> Self { + value.msg + } +} + +impl IbcForwarderInstantiate { + pub fn new( + clock_address: String, + next_contract: String, + remote_chain_connection_id: String, + remote_chain_channel_id: String, + denom: String, + amount: Uint128, + ibc_transfer_timeout: Uint64, + ica_timeout: Uint64, + fallback_address: Option, + ) -> Self { + Self { + msg: valence_ibc_forwarder::msg::InstantiateMsg { + clock_address, + next_contract, + remote_chain_connection_id, + remote_chain_channel_id, + denom, + amount, + ibc_transfer_timeout, + ica_timeout, + fallback_address, + }, + } + } + + pub fn with_clock_address(&mut self, addr: String) -> &mut Self { + self.msg.clock_address = addr; + self + } + + pub fn with_next_contract(&mut self, addr: String) -> &mut Self { + self.msg.next_contract = addr; + self + } + + pub fn with_remote_chain_connection_id(&mut self, addr: String) -> &mut Self { + self.msg.remote_chain_connection_id = addr; + self + } + + pub fn with_remote_chain_channel_id(&mut self, addr: String) -> &mut Self { + self.msg.remote_chain_channel_id = addr; + self + } + + pub fn with_denom(&mut self, addr: String) -> &mut Self { + self.msg.denom = addr; + self + } + + pub fn with_amount(&mut self, addr: Uint128) -> &mut Self { + self.msg.amount = addr; + self + } + + pub fn with_fallback_address(&mut self, addr: String) -> &mut Self { + self.msg.fallback_address = Some(addr); + self + } + + pub fn with_ibc_transfer_timeout(&mut self, addr: Uint64) -> &mut Self { + self.msg.ibc_transfer_timeout = addr; + self + } + + pub fn with_ica_timeout(&mut self, addr: Uint64) -> &mut Self { + self.msg.ica_timeout = addr; + self + } +} + +impl IbcForwarderInstantiate { + pub fn default(clock_address: String, next_contract: String) -> Self { + Self { + msg: valence_ibc_forwarder::msg::InstantiateMsg { + clock_address, + next_contract, + remote_chain_connection_id: "connection-todo".to_string(), + remote_chain_channel_id: NTRN_HUB_CHANNEL.1.to_string(), + denom: DENOM_ATOM_ON_NTRN.to_string(), + amount: Uint128::new(100_000), + ica_timeout: Uint64::from(100u64), + ibc_transfer_timeout: Uint64::from(100u64), + fallback_address: None, + }, + } + } +} diff --git a/unit-tests/src/setup/instantiates/interchain_router.rs b/unit-tests/src/setup/instantiates/interchain_router.rs new file mode 100644 index 00000000..cee697fd --- /dev/null +++ b/unit-tests/src/setup/instantiates/interchain_router.rs @@ -0,0 +1,62 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use cosmwasm_std::{Addr, Uint64}; +use covenant_utils::DestinationConfig; + +use crate::setup::{DENOM_ATOM_ON_NTRN, NTRN_HUB_CHANNEL}; + +pub struct InterchainRouterInstantiate { + pub msg: valence_interchain_router::msg::InstantiateMsg, +} + +impl From for valence_interchain_router::msg::InstantiateMsg { + fn from(value: InterchainRouterInstantiate) -> Self { + value.msg + } +} + +impl InterchainRouterInstantiate { + pub fn new( + clock_address: Addr, + destination_config: DestinationConfig, + denoms: BTreeSet, + ) -> Self { + Self { + msg: valence_interchain_router::msg::InstantiateMsg { + clock_address: clock_address.to_string(), + destination_config, + denoms, + }, + } + } + + pub fn with_clock_address(&mut self, addr: String) -> &mut Self { + self.msg.clock_address = addr; + self + } + + pub fn with_destination_config(&mut self, destination_config: DestinationConfig) -> &mut Self { + self.msg.destination_config = destination_config; + self + } + + pub fn with_denoms(&mut self, denoms: BTreeSet) -> &mut Self { + self.msg.denoms = denoms; + self + } +} + +impl InterchainRouterInstantiate { + pub fn default(clock_address: Addr, party_receiver: String) -> Self { + let denoms = BTreeSet::from_iter(vec![DENOM_ATOM_ON_NTRN.to_string()]); + + let destination_config = DestinationConfig { + local_to_destination_chain_channel_id: NTRN_HUB_CHANNEL.0.to_string(), + destination_receiver_addr: party_receiver, + ibc_transfer_timeout: Uint64::new(1000), + denom_to_pfm_map: BTreeMap::new(), + }; + + Self::new(clock_address, destination_config, denoms) + } +} diff --git a/unit-tests/src/setup/instantiates/mod.rs b/unit-tests/src/setup/instantiates/mod.rs new file mode 100644 index 00000000..d54ed3f6 --- /dev/null +++ b/unit-tests/src/setup/instantiates/mod.rs @@ -0,0 +1,14 @@ +pub mod astro_liquid_pooler; +pub mod clock; +pub mod ibc_forwarder; +pub mod interchain_router; +pub mod native_router; +pub mod native_splitter; +pub mod osmo_lp_outpost; +pub mod remote_chain_splitter; +pub mod single_party_covenant; +pub mod single_party_holder; +pub mod swap_covenant; +pub mod swap_holder; +pub mod two_party_covenant; +pub mod two_party_pol_holder; diff --git a/unit-tests/src/setup/instantiates/native_router.rs b/unit-tests/src/setup/instantiates/native_router.rs new file mode 100644 index 00000000..ead1371a --- /dev/null +++ b/unit-tests/src/setup/instantiates/native_router.rs @@ -0,0 +1,50 @@ +use std::collections::BTreeSet; + +use cosmwasm_std::Addr; + +use crate::setup::DENOM_ATOM_ON_NTRN; + +pub struct NativeRouterInstantiate { + pub msg: valence_native_router::msg::InstantiateMsg, +} + +impl From for valence_native_router::msg::InstantiateMsg { + fn from(value: NativeRouterInstantiate) -> Self { + value.msg + } +} + +impl NativeRouterInstantiate { + pub fn new(clock_address: Addr, receiver_address: Addr, denoms: BTreeSet) -> Self { + Self { + msg: valence_native_router::msg::InstantiateMsg { + clock_address: clock_address.to_string(), + receiver_address: receiver_address.to_string(), + denoms, + }, + } + } + + pub fn with_clock_address(&mut self, addr: String) -> &mut Self { + self.msg.clock_address = addr; + self + } + + pub fn with_receiver_address(&mut self, addr: String) -> &mut Self { + self.msg.receiver_address = addr; + self + } + + pub fn with_denoms(&mut self, denoms: BTreeSet) -> &mut Self { + self.msg.denoms = denoms; + self + } +} + +impl NativeRouterInstantiate { + pub fn default(clock_address: Addr, receiver_address: Addr) -> Self { + let denoms = BTreeSet::from_iter(vec![DENOM_ATOM_ON_NTRN.to_string()]); + + Self::new(clock_address, receiver_address, denoms) + } +} diff --git a/unit-tests/src/setup/instantiates/native_splitter.rs b/unit-tests/src/setup/instantiates/native_splitter.rs new file mode 100644 index 00000000..0ed5a4e1 --- /dev/null +++ b/unit-tests/src/setup/instantiates/native_splitter.rs @@ -0,0 +1,68 @@ +use std::{collections::BTreeMap, str::FromStr}; + +use cosmwasm_std::Decimal; +use covenant_utils::split::SplitConfig; + +use crate::setup::{DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN}; + +pub struct NativeSplitterInstantiate { + pub msg: valence_native_splitter::msg::InstantiateMsg, +} + +impl From for valence_native_splitter::msg::InstantiateMsg { + fn from(value: NativeSplitterInstantiate) -> Self { + value.msg + } +} + +impl NativeSplitterInstantiate { + pub fn new( + clock_address: String, + splits: BTreeMap, + fallback_split: Option, + ) -> Self { + Self { + msg: valence_native_splitter::msg::InstantiateMsg { + clock_address, + splits, + fallback_split, + }, + } + } + + pub fn with_clock_address(&mut self, addr: String) -> &mut Self { + self.msg.clock_address = addr; + self + } + + pub fn with_splits(&mut self, splits: BTreeMap) -> &mut Self { + self.msg.splits = splits; + self + } + + pub fn with_fallback_split(&mut self, fallback_split: Option) -> &mut Self { + self.msg.fallback_split = fallback_split; + self + } +} + +impl NativeSplitterInstantiate { + pub fn default(clock_address: String, party_a_addr: String, party_b_addr: String) -> Self { + let mut splits = BTreeMap::new(); + splits.insert(party_a_addr, Decimal::from_str("0.5").unwrap()); + splits.insert(party_b_addr, Decimal::from_str("0.5").unwrap()); + + let split_config = SplitConfig { receivers: splits }; + let mut denom_to_split_config_map = BTreeMap::new(); + denom_to_split_config_map.insert(DENOM_ATOM_ON_NTRN.to_string(), split_config.clone()); + denom_to_split_config_map.insert(DENOM_LS_ATOM_ON_NTRN.to_string(), split_config.clone()); + + Self { + msg: valence_native_splitter::msg::InstantiateMsg { + clock_address, + splits: denom_to_split_config_map, + fallback_split: Some(split_config), + }, + } + } +} diff --git a/unit-tests/src/setup/instantiates/osmo_lp_outpost.rs b/unit-tests/src/setup/instantiates/osmo_lp_outpost.rs new file mode 100644 index 00000000..8d8adaec --- /dev/null +++ b/unit-tests/src/setup/instantiates/osmo_lp_outpost.rs @@ -0,0 +1,23 @@ +pub struct OsmoLpOutpostInstantiate { + pub msg: valence_outpost_osmo_liquid_pooler::msg::InstantiateMsg, +} + +impl From for valence_outpost_osmo_liquid_pooler::msg::InstantiateMsg { + fn from(value: OsmoLpOutpostInstantiate) -> Self { + value.msg + } +} + +impl OsmoLpOutpostInstantiate { + pub fn new() -> Self { + Self { + msg: valence_outpost_osmo_liquid_pooler::msg::InstantiateMsg {}, + } + } +} + +impl Default for OsmoLpOutpostInstantiate { + fn default() -> Self { + Self::new() + } +} diff --git a/unit-tests/src/setup/instantiates/remote_chain_splitter.rs b/unit-tests/src/setup/instantiates/remote_chain_splitter.rs new file mode 100644 index 00000000..29bc8c3a --- /dev/null +++ b/unit-tests/src/setup/instantiates/remote_chain_splitter.rs @@ -0,0 +1,114 @@ +use std::{collections::BTreeMap, str::FromStr}; + +use cosmwasm_std::{Decimal, Uint128, Uint64}; +use covenant_utils::split::SplitConfig; + +use crate::setup::{DENOM_ATOM_ON_NTRN, NTRN_HUB_CHANNEL}; + +pub struct RemoteChainSplitterInstantiate { + pub msg: valence_remote_chain_splitter::msg::InstantiateMsg, +} +impl From for valence_remote_chain_splitter::msg::InstantiateMsg { + fn from(value: RemoteChainSplitterInstantiate) -> Self { + value.msg + } +} + +impl RemoteChainSplitterInstantiate { + pub fn new( + clock_address: String, + remote_chain_connection_id: String, + remote_chain_channel_id: String, + denom: String, + amount: Uint128, + splits: BTreeMap, + ica_timeout: Uint64, + ibc_transfer_timeout: Uint64, + fallback_address: Option, + ) -> Self { + Self { + msg: valence_remote_chain_splitter::msg::InstantiateMsg { + clock_address, + remote_chain_connection_id, + remote_chain_channel_id, + denom, + amount, + splits, + ica_timeout, + ibc_transfer_timeout, + fallback_address, + }, + } + } + + pub fn with_clock_address(&mut self, addr: String) -> &mut Self { + self.msg.clock_address = addr; + self + } + + pub fn with_remote_chain_connection_id(&mut self, id: String) -> &mut Self { + self.msg.remote_chain_connection_id = id; + self + } + + pub fn with_remote_chain_channel_id(&mut self, id: String) -> &mut Self { + self.msg.remote_chain_channel_id = id; + self + } + + pub fn with_denom(&mut self, denom: String) -> &mut Self { + self.msg.denom = denom; + self + } + + pub fn with_amount(&mut self, amount: Uint128) -> &mut Self { + self.msg.amount = amount; + self + } + + pub fn with_splits(&mut self, splits: BTreeMap) -> &mut Self { + self.msg.splits = splits; + self + } + + pub fn with_ica_timeout(&mut self, ica_timeout: Uint64) -> &mut Self { + self.msg.ica_timeout = ica_timeout; + self + } + + pub fn with_fallback_address(&mut self, fallback_address: Option) -> &mut Self { + self.msg.fallback_address = fallback_address; + self + } + + pub fn with_ibc_transfer_timeout(&mut self, ibc_transfer_timeout: Uint64) -> &mut Self { + self.msg.ibc_transfer_timeout = ibc_transfer_timeout; + self + } +} + +impl RemoteChainSplitterInstantiate { + pub fn default(clock_address: String, party_a_addr: String, party_b_addr: String) -> Self { + let mut splits = BTreeMap::new(); + splits.insert(party_a_addr.to_string(), Decimal::from_str("0.5").unwrap()); + splits.insert(party_b_addr.to_string(), Decimal::from_str("0.5").unwrap()); + + let split_config = SplitConfig { receivers: splits }; + let mut denom_to_split_config_map = BTreeMap::new(); + denom_to_split_config_map.insert(DENOM_ATOM_ON_NTRN.to_string(), split_config.clone()); + + Self { + msg: valence_remote_chain_splitter::msg::InstantiateMsg { + clock_address, + remote_chain_connection_id: "connection-0".to_string(), + remote_chain_channel_id: NTRN_HUB_CHANNEL.0.to_string(), + denom: DENOM_ATOM_ON_NTRN.to_string(), + amount: Uint128::from(10000u128), + splits: denom_to_split_config_map, + ica_timeout: Uint64::from(100u64), + ibc_transfer_timeout: Uint64::from(100u64), + fallback_address: None, + }, + } + } +} diff --git a/unit-tests/src/setup/instantiates/single_party_covenant.rs b/unit-tests/src/setup/instantiates/single_party_covenant.rs new file mode 100644 index 00000000..cef8a50f --- /dev/null +++ b/unit-tests/src/setup/instantiates/single_party_covenant.rs @@ -0,0 +1,258 @@ +use std::collections::BTreeMap; + +use cosmwasm_std::{coin, Addr, Decimal, Uint128, Uint64}; +use cw_utils::Expiration; + +use crate::setup::{ + suite_builder::SuiteBuilder, DENOM_LS_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_STRIDE, + NTRN_STRIDE_CHANNEL, +}; + +#[derive(Clone)] +pub struct SinglePartyCovenantInstantiate { + pub msg: valence_covenant_single_party_pol::msg::InstantiateMsg, +} + +impl From + for valence_covenant_single_party_pol::msg::InstantiateMsg +{ + fn from(value: SinglePartyCovenantInstantiate) -> Self { + value.msg + } +} + +impl SinglePartyCovenantInstantiate { + pub fn default( + builder: &SuiteBuilder, + ls_forwarder_config: valence_covenant_single_party_pol::msg::CovenantPartyConfig, + lp_forwarder_config: valence_covenant_single_party_pol::msg::CovenantPartyConfig, + remote_splitter: valence_covenant_single_party_pol::msg::RemoteChainSplitterConfig, + covenant_party: covenant_utils::InterchainCovenantParty, + pooler_config: valence_covenant_single_party_pol::msg::LiquidPoolerConfig, + pool_price_config: covenant_utils::PoolPriceConfig, + ) -> Self { + let contract_codes = valence_covenant_single_party_pol::msg::CovenantContractCodeIds { + ibc_forwarder_code: builder.ibc_forwarder_code_id, + interchain_router_code: builder.interchain_router_code_id, + holder_code: builder.single_party_holder_code_id, + clock_code: builder.clock_code_id, + remote_chain_splitter_code: builder.remote_splitter_code_id, + liquid_pooler_code: builder.astro_pooler_code_id, + liquid_staker_code: builder.stride_staker_code_id, + }; + + Self::new( + "single_party_covenant".to_string(), + valence_covenant_single_party_pol::msg::Timeouts { + ica_timeout: 1000_u64.into(), + ibc_transfer_timeout: 1000_u64.into(), + }, + contract_codes, + None, + Expiration::AtHeight(builder.app.block_info().height + 100000), + valence_covenant_single_party_pol::msg::LsInfo { + ls_denom: DENOM_LS_ATOM_ON_STRIDE.to_string(), + ls_denom_on_neutron: DENOM_LS_ATOM_ON_NTRN.to_string(), + ls_chain_to_neutron_channel_id: NTRN_STRIDE_CHANNEL.1.to_string(), + ls_neutron_connection_id: "conn-1".to_string(), + }, + ls_forwarder_config, + lp_forwarder_config, + pool_price_config, + remote_splitter, + None, + covenant_party, + pooler_config, + ) + } + + pub fn get_covenant_party( + remote_recevier: &Addr, + local_recevier: &Addr, + remote_denom: &str, + local_denom: &str, + local_to_remote_channel_id: &str, + remote_to_local_channel_id: &str, + amount: u128, + denom_to_pfm_map: BTreeMap, + ) -> covenant_utils::InterchainCovenantParty { + covenant_utils::InterchainCovenantParty { + party_receiver_addr: remote_recevier.to_string(), + party_chain_connection_id: "conn-1".to_string(), + ibc_transfer_timeout: 1000_u64.into(), + party_to_host_chain_channel_id: remote_to_local_channel_id.to_string(), + host_to_party_chain_channel_id: local_to_remote_channel_id.to_string(), + remote_chain_denom: remote_denom.to_string(), + addr: local_recevier.to_string(), + native_denom: local_denom.to_string(), + contribution: coin(amount, remote_denom), + denom_to_pfm_map, + fallback_address: None, + } + } + + pub fn get_forwarder_config_interchain( + remote_recevier: &Addr, + local_recevier: &Addr, + remote_denom: &str, + local_denom: &str, + local_to_remote_channel_id: &str, + remote_to_local_channel_id: &str, + amount: u128, + ) -> valence_covenant_single_party_pol::msg::CovenantPartyConfig { + valence_covenant_single_party_pol::msg::CovenantPartyConfig::Interchain( + SinglePartyCovenantInstantiate::get_covenant_party( + remote_recevier, + local_recevier, + remote_denom, + local_denom, + local_to_remote_channel_id, + remote_to_local_channel_id, + amount, + BTreeMap::new(), + ), + ) + } + + pub fn get_forwarder_config_native( + recevier: &Addr, + denom: &str, + amount: u128, + ) -> valence_covenant_single_party_pol::msg::CovenantPartyConfig { + valence_covenant_single_party_pol::msg::CovenantPartyConfig::Native( + covenant_utils::NativeCovenantParty { + party_receiver_addr: recevier.to_string(), + native_denom: denom.to_string(), + addr: recevier.to_string(), + contribution: coin(amount, denom), + }, + ) + } + + pub fn get_remote_splitter_config( + channel_id: impl Into, + denom: impl Into, + amount: impl Into, + ls_share: Decimal, + native_share: Decimal, + ) -> valence_covenant_single_party_pol::msg::RemoteChainSplitterConfig { + valence_covenant_single_party_pol::msg::RemoteChainSplitterConfig { + channel_id: channel_id.into(), + connection_id: "conn-1".to_string(), + denom: denom.into(), + amount: amount.into(), + ls_share, + native_share, + fallback_address: None, + } + } + + pub fn get_astro_pooler_config( + denom_a: impl Into, + denom_b: impl Into, + pool_addr: &Addr, + pool_pair_type: astroport::factory::PairType, + single_side_lp_limits: covenant_utils::SingleSideLpLimits, + ) -> valence_covenant_single_party_pol::msg::LiquidPoolerConfig { + valence_covenant_single_party_pol::msg::LiquidPoolerConfig::Astroport( + valence_astroport_liquid_pooler::msg::AstroportLiquidPoolerConfig { + pool_pair_type, + pool_address: pool_addr.to_string(), + asset_a_denom: denom_a.into(), + asset_b_denom: denom_b.into(), + single_side_lp_limits, + }, + ) + } + + pub fn get_pool_price_config( + expected_spot_price: Decimal, + acceptable_price_spread: Decimal, + ) -> covenant_utils::PoolPriceConfig { + covenant_utils::PoolPriceConfig { + expected_spot_price, + acceptable_price_spread, + } + } +} + +impl SinglePartyCovenantInstantiate { + #[allow(clippy::too_many_arguments)] + pub fn new( + label: String, + timeouts: valence_covenant_single_party_pol::msg::Timeouts, + contract_codes: valence_covenant_single_party_pol::msg::CovenantContractCodeIds, + clock_tick_max_gas: Option, + lockup_period: Expiration, + ls_info: valence_covenant_single_party_pol::msg::LsInfo, + ls_forwarder_config: valence_covenant_single_party_pol::msg::CovenantPartyConfig, + lp_forwarder_config: valence_covenant_single_party_pol::msg::CovenantPartyConfig, + pool_price_config: covenant_utils::PoolPriceConfig, + remote_chain_splitter_config: valence_covenant_single_party_pol::msg::RemoteChainSplitterConfig, + emergency_committee: Option, + covenant_party_config: covenant_utils::InterchainCovenantParty, + liquid_pooler_config: valence_covenant_single_party_pol::msg::LiquidPoolerConfig, + ) -> Self { + Self { + msg: valence_covenant_single_party_pol::msg::InstantiateMsg { + label, + timeouts, + contract_codes, + clock_tick_max_gas, + lockup_period, + ls_info, + ls_forwarder_config, + lp_forwarder_config, + pool_price_config, + remote_chain_splitter_config, + emergency_committee, + covenant_party_config, + liquid_pooler_config, + }, + } + } + + /* Change functions */ + pub fn with_label(&mut self, label: &str) -> &mut Self { + self.msg.label = label.to_string(); + self + } + + pub fn with_timeouts( + &mut self, + ica_timeout: impl Into, + ibc_transfer_timeout: impl Into, + ) -> &mut Self { + self.msg.timeouts = valence_covenant_single_party_pol::msg::Timeouts { + ica_timeout: ica_timeout.into(), + ibc_transfer_timeout: ibc_transfer_timeout.into(), + }; + self + } + + pub fn with_contract_codes( + &mut self, + codes: valence_covenant_single_party_pol::msg::CovenantContractCodeIds, + ) -> &mut Self { + self.msg.contract_codes = codes; + self + } + + pub fn with_clock_tick_max_gas(&mut self, clock_tick_max_gas: Option) -> &mut Self { + self.msg.clock_tick_max_gas = clock_tick_max_gas; + self + } + + pub fn with_lockup_period(&mut self, lockup_period: Expiration) -> &mut Self { + self.msg.lockup_period = lockup_period; + self + } + + pub fn with_emergency_committee( + &mut self, + emergency_committee: impl Into, + ) -> &mut Self { + self.msg.emergency_committee = Some(emergency_committee.into()); + self + } +} diff --git a/unit-tests/src/setup/instantiates/single_party_holder.rs b/unit-tests/src/setup/instantiates/single_party_holder.rs new file mode 100644 index 00000000..3bcf1fbf --- /dev/null +++ b/unit-tests/src/setup/instantiates/single_party_holder.rs @@ -0,0 +1,70 @@ +use cw_utils::Expiration; + +pub struct SinglePartyHolderInstantiate { + pub msg: valence_single_party_pol_holder::msg::InstantiateMsg, +} + +impl From for valence_single_party_pol_holder::msg::InstantiateMsg { + fn from(value: SinglePartyHolderInstantiate) -> Self { + value.msg + } +} + +impl SinglePartyHolderInstantiate { + pub fn new( + withdrawer: String, + withdraw_to: String, + emergency_committee_addr: Option, + pooler_address: String, + lockup_period: Expiration, + ) -> Self { + Self { + msg: valence_single_party_pol_holder::msg::InstantiateMsg { + withdrawer, + withdraw_to, + emergency_committee_addr, + pooler_address, + lockup_period, + }, + } + } + + pub fn with_withdrawer(&mut self, addr: String) -> &mut Self { + self.msg.withdrawer = addr; + self + } + + pub fn with_withdraw_to(&mut self, addr: String) -> &mut Self { + self.msg.withdraw_to = addr; + self + } + + pub fn with_emergency_committee_addr(&mut self, addr: Option) -> &mut Self { + self.msg.emergency_committee_addr = addr; + self + } + + pub fn with_pooler_address(&mut self, addr: &str) -> &mut Self { + self.msg.pooler_address = addr.to_string(); + self + } + + pub fn with_lockup_period(&mut self, period: Expiration) -> &mut Self { + self.msg.lockup_period = period; + self + } +} + +impl SinglePartyHolderInstantiate { + pub fn default(pooler_address: String) -> Self { + Self { + msg: valence_single_party_pol_holder::msg::InstantiateMsg { + withdrawer: pooler_address.to_string(), + withdraw_to: pooler_address.to_string(), + emergency_committee_addr: Some(pooler_address.to_string()), + pooler_address, + lockup_period: Expiration::AtHeight(100000), + }, + } + } +} diff --git a/unit-tests/src/setup/instantiates/swap_covenant.rs b/unit-tests/src/setup/instantiates/swap_covenant.rs new file mode 100644 index 00000000..1d094c32 --- /dev/null +++ b/unit-tests/src/setup/instantiates/swap_covenant.rs @@ -0,0 +1,217 @@ +use std::collections::BTreeMap; + +use cosmwasm_std::{coin, testing::mock_env, Addr, Decimal, Uint64}; +use cw_utils::Expiration; + +use crate::setup::suite_builder::SuiteBuilder; + +#[derive(Clone)] +pub struct SwapCovenantInstantiate { + pub msg: valence_covenant_swap::msg::InstantiateMsg, +} + +impl From for valence_covenant_swap::msg::InstantiateMsg { + fn from(value: SwapCovenantInstantiate) -> Self { + value.msg + } +} + +impl SwapCovenantInstantiate { + pub fn default( + builder: &SuiteBuilder, + party_a_config: valence_covenant_swap::msg::CovenantPartyConfig, + party_b_config: valence_covenant_swap::msg::CovenantPartyConfig, + splits: BTreeMap, + ) -> Self { + let contract_codes = valence_covenant_swap::msg::SwapCovenantContractCodeIds { + ibc_forwarder_code: builder.ibc_forwarder_code_id, + interchain_router_code: builder.interchain_router_code_id, + native_router_code: builder.native_router_code_id, + splitter_code: builder.native_splitter_code_id, + holder_code: builder.swap_holder_code_id, + clock_code: builder.clock_code_id, + }; + + Self::new( + "swap_covenant".to_string(), + valence_covenant_swap::msg::Timeouts { + ica_timeout: 1000_u64.into(), + ibc_transfer_timeout: 1000_u64.into(), + }, + contract_codes, + None, + Expiration::AtHeight(mock_env().block.height + 1000), + party_a_config, + party_b_config, + splits, + None, + None, + ) + } + + pub fn get_party_config_interchain( + remote_recevier: &Addr, + local_recevier: &Addr, + remote_denom: &str, + local_denom: &str, + local_to_remote_channel_id: &str, + remote_to_local_channel_id: &str, + amount: u128, + ) -> valence_covenant_swap::msg::CovenantPartyConfig { + valence_covenant_swap::msg::CovenantPartyConfig::Interchain( + covenant_utils::InterchainCovenantParty { + party_receiver_addr: remote_recevier.to_string(), + party_chain_connection_id: "conn-1".to_string(), + ibc_transfer_timeout: 1000_u64.into(), + party_to_host_chain_channel_id: remote_to_local_channel_id.to_string(), + host_to_party_chain_channel_id: local_to_remote_channel_id.to_string(), + remote_chain_denom: remote_denom.to_string(), + addr: local_recevier.to_string(), + native_denom: local_denom.to_string(), + contribution: coin(amount, remote_denom), + denom_to_pfm_map: BTreeMap::new(), + fallback_address: None, + }, + ) + } + + pub fn get_party_config_native( + recevier: &Addr, + denom: &str, + amount: u128, + ) -> valence_covenant_swap::msg::CovenantPartyConfig { + valence_covenant_swap::msg::CovenantPartyConfig::Native( + covenant_utils::NativeCovenantParty { + party_receiver_addr: recevier.to_string(), + native_denom: denom.to_string(), + addr: recevier.to_string(), + contribution: coin(amount, denom), + }, + ) + } + + pub fn get_split_custom( + splits: Vec<(&str, &Vec<(&Addr, Decimal)>)>, + ) -> BTreeMap { + let mut map = BTreeMap::new(); + + splits.iter().for_each(|(denom, split)| { + let mut receivers = BTreeMap::new(); + + split.iter().for_each(|(receiver, amount)| { + receivers.insert(receiver.to_string(), *amount); + }); + + let split = covenant_utils::split::SplitConfig { receivers }; + + map.insert(denom.to_string(), split); + }); + map + } +} + +impl SwapCovenantInstantiate { + #[allow(clippy::too_many_arguments)] + pub fn new( + label: String, + timeouts: valence_covenant_swap::msg::Timeouts, + contract_codes: valence_covenant_swap::msg::SwapCovenantContractCodeIds, + clock_tick_max_gas: Option, + lockup_config: Expiration, + party_a_config: valence_covenant_swap::msg::CovenantPartyConfig, + party_b_config: valence_covenant_swap::msg::CovenantPartyConfig, + splits: BTreeMap, + fallback_split: Option, + fallback_address: Option, + ) -> Self { + Self { + msg: valence_covenant_swap::msg::InstantiateMsg { + label, + timeouts, + contract_codes, + clock_tick_max_gas, + lockup_config, + party_a_config, + party_b_config, + splits, + fallback_split, + fallback_address, + }, + } + } + + /* Change functions */ + pub fn with_label(&mut self, label: &str) -> &mut Self { + self.msg.label = label.to_string(); + self + } + + pub fn with_timeouts( + &mut self, + ica_timeout: impl Into, + ibc_transfer_timeout: impl Into, + ) -> &mut Self { + self.msg.timeouts = valence_covenant_swap::msg::Timeouts { + ica_timeout: ica_timeout.into(), + ibc_transfer_timeout: ibc_transfer_timeout.into(), + }; + self + } + + pub fn with_contract_codes( + &mut self, + codes: valence_covenant_swap::msg::SwapCovenantContractCodeIds, + ) -> &mut Self { + self.msg.contract_codes = codes; + self + } + + pub fn with_clock_tick_max_gas(&mut self, clock_tick_max_gas: Option) -> &mut Self { + self.msg.clock_tick_max_gas = clock_tick_max_gas; + self + } + + pub fn with_lockup_config(&mut self, lockup_config: Expiration) -> &mut Self { + self.msg.lockup_config = lockup_config; + self + } + + pub fn with_party_a_config( + &mut self, + config: valence_covenant_swap::msg::CovenantPartyConfig, + ) -> &mut Self { + self.msg.party_a_config = config; + self + } + + pub fn with_party_b_config( + &mut self, + config: valence_covenant_swap::msg::CovenantPartyConfig, + ) -> &mut Self { + self.msg.party_b_config = config; + self + } + + pub fn with_splits( + &mut self, + splits: BTreeMap, + ) -> &mut Self { + self.msg.splits = splits; + self + } + + pub fn with_fallback_split(&mut self, split: &[(&Addr, Decimal)]) -> &mut Self { + let mut receivers = BTreeMap::new(); + split.iter().for_each(|(receiver, amount)| { + receivers.insert(receiver.to_string(), *amount); + }); + + self.msg.fallback_split = Some(covenant_utils::split::SplitConfig { receivers }); + self + } + + pub fn with_fallback_address(&mut self, addr: String) -> &mut Self { + self.msg.fallback_address = Some(addr); + self + } +} diff --git a/unit-tests/src/setup/instantiates/swap_holder.rs b/unit-tests/src/setup/instantiates/swap_holder.rs new file mode 100644 index 00000000..a7200333 --- /dev/null +++ b/unit-tests/src/setup/instantiates/swap_holder.rs @@ -0,0 +1,107 @@ +use cosmwasm_std::{Addr, Uint128}; +use covenant_utils::{CovenantPartiesConfig, CovenantParty, CovenantTerms, ReceiverConfig}; +use cw_utils::Expiration; +use valence_swap_holder::msg::RefundConfig; + +use crate::setup::{DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN}; + +pub struct SwapHolderInstantiate { + pub msg: valence_swap_holder::msg::InstantiateMsg, +} + +impl From for valence_swap_holder::msg::InstantiateMsg { + fn from(value: SwapHolderInstantiate) -> Self { + value.msg + } +} + +impl SwapHolderInstantiate { + pub fn new( + clock_address: String, + next_contract: String, + lockup_config: Expiration, + covenant_terms: CovenantTerms, + parties_config: CovenantPartiesConfig, + refund_config: RefundConfig, + ) -> Self { + Self { + msg: valence_swap_holder::msg::InstantiateMsg { + clock_address, + next_contract, + lockup_config, + covenant_terms, + parties_config, + refund_config, + }, + } + } + + pub fn with_clock_address(&mut self, addr: &str) -> &mut Self { + self.msg.clock_address = addr.to_string(); + self + } + + pub fn with_next_contract(&mut self, addr: &str) -> &mut Self { + self.msg.next_contract = addr.to_string(); + self + } + + pub fn with_lockup_config(&mut self, period: Expiration) -> &mut Self { + self.msg.lockup_config = period; + self + } + + pub fn with_covenant_terms(&mut self, terms: CovenantTerms) -> &mut Self { + self.msg.covenant_terms = terms; + self + } + + pub fn with_parties_config(&mut self, config: CovenantPartiesConfig) -> &mut Self { + self.msg.parties_config = config; + self + } + + pub fn with_refund_config(&mut self, config: RefundConfig) -> &mut Self { + self.msg.refund_config = config; + self + } +} + +impl SwapHolderInstantiate { + pub fn default( + clock_address: String, + next_contract: String, + party_a_addr: Addr, + party_b_addr: Addr, + party_a_refund_address: String, + party_b_refund_address: String, + ) -> Self { + Self { + msg: valence_swap_holder::msg::InstantiateMsg { + clock_address, + next_contract, + lockup_config: Expiration::AtHeight(1000000), + covenant_terms: CovenantTerms::TokenSwap(covenant_utils::SwapCovenantTerms { + party_a_amount: Uint128::new(100000), + party_b_amount: Uint128::new(100000), + }), + parties_config: CovenantPartiesConfig { + party_a: CovenantParty { + addr: party_a_addr.to_string(), + native_denom: DENOM_ATOM_ON_NTRN.to_string(), + receiver_config: ReceiverConfig::Native(party_a_addr.to_string()), + }, + party_b: CovenantParty { + addr: party_b_addr.to_string(), + native_denom: DENOM_LS_ATOM_ON_NTRN.to_string(), + receiver_config: ReceiverConfig::Native(party_b_addr.to_string()), + }, + }, + refund_config: RefundConfig { + party_a_refund_address, + party_b_refund_address, + }, + }, + } + } +} diff --git a/unit-tests/src/setup/instantiates/two_party_covenant.rs b/unit-tests/src/setup/instantiates/two_party_covenant.rs new file mode 100644 index 00000000..0cfe7c91 --- /dev/null +++ b/unit-tests/src/setup/instantiates/two_party_covenant.rs @@ -0,0 +1,200 @@ +use std::{collections::BTreeMap, str::FromStr}; + +use cosmwasm_std::{coin, Addr, Decimal, Uint128, Uint64}; +use covenant_utils::{ + split::SplitConfig, NativeCovenantParty, PoolPriceConfig, SingleSideLpLimits, +}; +use cw_utils::Expiration; +use valence_astroport_liquid_pooler::msg::AstroportLiquidPoolerConfig; +use valence_covenant_two_party_pol::msg::{CovenantPartyConfig, Timeouts}; + +use crate::setup::{suite_builder::SuiteBuilder, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN}; + +#[derive(Clone)] +pub struct TwoPartyCovenantInstantiate { + pub msg: valence_covenant_two_party_pol::msg::InstantiateMsg, +} + +impl From for valence_covenant_two_party_pol::msg::InstantiateMsg { + fn from(value: TwoPartyCovenantInstantiate) -> Self { + value.msg + } +} + +impl TwoPartyCovenantInstantiate { + pub fn with_timeouts(&mut self, timeouts: Timeouts) -> &mut Self { + self.msg.timeouts = timeouts; + self + } + + pub fn with_contract_codes( + &mut self, + contract_codes: valence_covenant_two_party_pol::msg::CovenantContractCodeIds, + ) -> &mut Self { + self.msg.contract_codes = contract_codes; + self + } + + pub fn with_clock_tick_max_gas(&mut self, clock_tick_max_gas: Option) -> &mut Self { + self.msg.clock_tick_max_gas = clock_tick_max_gas; + self + } + + pub fn with_lockup_config(&mut self, lockup_config: Expiration) -> &mut Self { + self.msg.lockup_config = lockup_config; + self + } + + pub fn with_ragequit_config( + &mut self, + ragequit_config: Option, + ) -> &mut Self { + self.msg.ragequit_config = ragequit_config; + self + } + + pub fn with_deposit_deadline(&mut self, deposit_deadline: Expiration) -> &mut Self { + self.msg.deposit_deadline = deposit_deadline; + self + } + + pub fn with_party_a_config( + &mut self, + party_a_config: valence_covenant_two_party_pol::msg::CovenantPartyConfig, + ) -> &mut Self { + self.msg.party_a_config = party_a_config; + self + } + + pub fn with_party_b_config( + &mut self, + party_b_config: valence_covenant_two_party_pol::msg::CovenantPartyConfig, + ) -> &mut Self { + self.msg.party_b_config = party_b_config; + self + } + + pub fn with_covenant_type( + &mut self, + covenant_type: valence_two_party_pol_holder::msg::CovenantType, + ) -> &mut Self { + self.msg.covenant_type = covenant_type; + self + } + + pub fn with_party_a_share(&mut self, party_a_share: Decimal) -> &mut Self { + self.msg.party_a_share = party_a_share; + self + } + + pub fn with_party_b_share(&mut self, party_b_share: Decimal) -> &mut Self { + self.msg.party_b_share = party_b_share; + self + } + + pub fn with_pool_price_config(&mut self, pool_price_config: PoolPriceConfig) -> &mut Self { + self.msg.pool_price_config = pool_price_config; + self + } + + pub fn with_splits(&mut self, splits: BTreeMap) -> &mut Self { + self.msg.splits = splits; + self + } + + pub fn with_fallback_split(&mut self, fallback_split: Option) -> &mut Self { + self.msg.fallback_split = fallback_split; + self + } + + pub fn with_emergency_committee(&mut self, emergency_committee: Option) -> &mut Self { + self.msg.emergency_committee = emergency_committee; + self + } + + pub fn with_liquid_pooler_config( + &mut self, + liquid_pooler_config: valence_covenant_two_party_pol::msg::LiquidPoolerConfig, + ) -> &mut Self { + self.msg.liquid_pooler_config = liquid_pooler_config; + self + } +} + +impl TwoPartyCovenantInstantiate { + pub fn default( + builder: &SuiteBuilder, + party_a_addr: Addr, + party_b_addr: Addr, + pool_address: Addr, + ) -> Self { + let contract_codes = valence_covenant_two_party_pol::msg::CovenantContractCodeIds { + ibc_forwarder_code: builder.ibc_forwarder_code_id, + interchain_router_code: builder.interchain_router_code_id, + holder_code: builder.two_party_holder_code_id, + clock_code: builder.clock_code_id, + liquid_pooler_code: builder.astro_pooler_code_id, + native_router_code: builder.native_router_code_id, + }; + + let mut splits = BTreeMap::new(); + splits.insert(party_a_addr.to_string(), Decimal::from_str("0.5").unwrap()); + splits.insert(party_b_addr.to_string(), Decimal::from_str("0.5").unwrap()); + + let split_config = SplitConfig { receivers: splits }; + let mut denom_to_split_config_map = BTreeMap::new(); + denom_to_split_config_map.insert(DENOM_ATOM_ON_NTRN.to_string(), split_config.clone()); + denom_to_split_config_map.insert(DENOM_LS_ATOM_ON_NTRN.to_string(), split_config.clone()); + + Self { + msg: valence_covenant_two_party_pol::msg::InstantiateMsg { + label: "valence_covenant_two_party_pol".to_string(), + timeouts: Timeouts { + ica_timeout: Uint64::new(100), + ibc_transfer_timeout: Uint64::new(100), + }, + contract_codes, + clock_tick_max_gas: None, + lockup_config: Expiration::AtHeight(200000), + ragequit_config: None, + deposit_deadline: Expiration::AtHeight(100000), + party_a_config: CovenantPartyConfig::Native(NativeCovenantParty { + party_receiver_addr: party_a_addr.to_string(), + native_denom: DENOM_ATOM_ON_NTRN.to_string(), + addr: party_a_addr.to_string(), + contribution: coin(10_000, DENOM_ATOM_ON_NTRN), + }), + party_b_config: CovenantPartyConfig::Native(NativeCovenantParty { + party_receiver_addr: party_b_addr.to_string(), + native_denom: DENOM_LS_ATOM_ON_NTRN.to_string(), + addr: party_b_addr.to_string(), + contribution: coin(10_000, DENOM_LS_ATOM_ON_NTRN), + }), + covenant_type: valence_two_party_pol_holder::msg::CovenantType::Share {}, + party_a_share: Decimal::from_str("0.5").unwrap(), + party_b_share: Decimal::from_str("0.5").unwrap(), + pool_price_config: PoolPriceConfig { + expected_spot_price: Decimal::from_str("1.0").unwrap(), + acceptable_price_spread: Decimal::from_str("0.1").unwrap(), + }, + splits: denom_to_split_config_map, + fallback_split: None, + emergency_committee: None, + liquid_pooler_config: + valence_covenant_two_party_pol::msg::LiquidPoolerConfig::Astroport( + AstroportLiquidPoolerConfig { + pool_pair_type: astroport::factory::PairType::Stable {}, + pool_address: pool_address.to_string(), + asset_a_denom: DENOM_ATOM_ON_NTRN.to_string(), + asset_b_denom: DENOM_LS_ATOM_ON_NTRN.to_string(), + single_side_lp_limits: SingleSideLpLimits { + asset_a_limit: Uint128::new(10_000), + asset_b_limit: Uint128::new(10_000), + }, + }, + ), + fallback_address: None, + }, + } + } +} diff --git a/unit-tests/src/setup/instantiates/two_party_pol_holder.rs b/unit-tests/src/setup/instantiates/two_party_pol_holder.rs new file mode 100644 index 00000000..afb8ed20 --- /dev/null +++ b/unit-tests/src/setup/instantiates/two_party_pol_holder.rs @@ -0,0 +1,147 @@ +use std::{collections::BTreeMap, str::FromStr}; + +use cosmwasm_std::{coin, Addr, Decimal}; +use covenant_utils::split::SplitConfig; +use cw_utils::Expiration; + +use crate::setup::{DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN}; + +#[derive(Clone)] +pub struct TwoPartyHolderInstantiate { + pub msg: valence_two_party_pol_holder::msg::InstantiateMsg, +} + +impl From for valence_two_party_pol_holder::msg::InstantiateMsg { + fn from(value: TwoPartyHolderInstantiate) -> Self { + value.msg + } +} + +impl TwoPartyHolderInstantiate { + #[allow(clippy::too_many_arguments)] + pub fn new( + clock_address: String, + next_contract: String, + lockup_config: Expiration, + ragequit_config: valence_two_party_pol_holder::msg::RagequitConfig, + deposit_deadline: Expiration, + covenant_config: valence_two_party_pol_holder::msg::TwoPartyPolCovenantConfig, + splits: BTreeMap, + fallback_split: Option, + emergency_committee_addr: Option, + ) -> Self { + Self { + msg: valence_two_party_pol_holder::msg::InstantiateMsg { + clock_address, + next_contract, + lockup_config, + ragequit_config, + deposit_deadline, + covenant_config, + splits, + fallback_split, + emergency_committee_addr, + }, + } + } + + /* Change functions */ + pub fn with_clock(&mut self, addr: &str) -> &mut Self { + self.msg.clock_address = addr.to_string(); + self + } + + pub fn with_next_contract(&mut self, addr: &str) -> &mut Self { + self.msg.next_contract = addr.to_string(); + self + } + + pub fn with_lockup_config(&mut self, config: Expiration) -> &mut Self { + self.msg.lockup_config = config; + self + } + + pub fn with_ragequit_config( + &mut self, + config: valence_two_party_pol_holder::msg::RagequitConfig, + ) -> &mut Self { + self.msg.ragequit_config = config; + self + } + + pub fn with_deposit_deadline(&mut self, config: Expiration) -> &mut Self { + self.msg.deposit_deadline = config; + self + } + + pub fn with_covenant_config( + &mut self, + config: valence_two_party_pol_holder::msg::TwoPartyPolCovenantConfig, + ) -> &mut Self { + self.msg.covenant_config = config; + self + } + + pub fn with_splits(&mut self, splits: BTreeMap) -> &mut Self { + self.msg.splits = splits; + self + } + + pub fn with_fallback_split(&mut self, split: SplitConfig) -> &mut Self { + self.msg.fallback_split = Some(split); + self + } + + pub fn with_emergency_committee(&mut self, addr: &str) -> &mut Self { + self.msg.emergency_committee_addr = Some(addr.to_string()); + self + } +} + +impl TwoPartyHolderInstantiate { + pub fn default( + clock_address: String, + next_contract: String, + party_a_addr: Addr, + party_b_addr: Addr, + ) -> Self { + let mut splits = BTreeMap::new(); + splits.insert(party_a_addr.to_string(), Decimal::from_str("0.5").unwrap()); + splits.insert(party_b_addr.to_string(), Decimal::from_str("0.5").unwrap()); + + let split_config = SplitConfig { receivers: splits }; + let mut denom_to_split_config_map = BTreeMap::new(); + denom_to_split_config_map.insert(DENOM_ATOM_ON_NTRN.to_string(), split_config.clone()); + denom_to_split_config_map.insert(DENOM_LS_ATOM_ON_NTRN.to_string(), split_config.clone()); + + Self { + msg: valence_two_party_pol_holder::msg::InstantiateMsg { + clock_address, + next_contract, + lockup_config: Expiration::AtHeight(200000), + ragequit_config: valence_two_party_pol_holder::msg::RagequitConfig::Disabled {}, + deposit_deadline: Expiration::AtHeight(100000), + covenant_config: valence_two_party_pol_holder::msg::TwoPartyPolCovenantConfig { + party_a: valence_two_party_pol_holder::msg::TwoPartyPolCovenantParty { + contribution: coin(10_000, DENOM_ATOM_ON_NTRN), + host_addr: party_a_addr.to_string(), + controller_addr: party_a_addr.to_string(), + allocation: Decimal::from_str("0.5").unwrap(), + router: party_a_addr.to_string(), + }, + party_b: valence_two_party_pol_holder::msg::TwoPartyPolCovenantParty { + contribution: coin(10_000, DENOM_LS_ATOM_ON_NTRN), + host_addr: party_b_addr.to_string(), + controller_addr: party_b_addr.to_string(), + allocation: Decimal::from_str("0.5").unwrap(), + router: party_b_addr.to_string(), + }, + covenant_type: valence_two_party_pol_holder::msg::CovenantType::Share {}, + }, + splits: denom_to_split_config_map, + fallback_split: None, + emergency_committee_addr: None, + }, + } + } +} diff --git a/unit-tests/src/setup/mod.rs b/unit-tests/src/setup/mod.rs new file mode 100644 index 00000000..01f65430 --- /dev/null +++ b/unit-tests/src/setup/mod.rs @@ -0,0 +1,110 @@ +use const_format::concatcp; +use cosmwasm_std::{Empty, MemoryStorage}; +use cw_multi_test::{ + addons::MockApiBech32, App, BankKeeper, DistributionKeeper, GovFailingModule, IbcFailingModule, + StakeKeeper, StargateMsg, StargateQuery, WasmKeeper, +}; +use neutron_sdk::bindings::{msg::NeutronMsg, query::NeutronQuery}; + +use self::{custom_keepers::CustomStargateKeeper, custom_module::NeutronKeeper}; + +pub mod astro_contracts; +pub mod base_suite; +pub mod contracts; +pub mod custom_keepers; +pub mod custom_module; +pub mod instantiates; +pub mod suite_builder; + +pub type CustomApp = App< + BankKeeper, + MockApiBech32, + MemoryStorage, + NeutronKeeper, + WasmKeeper, + StakeKeeper, + DistributionKeeper, + IbcFailingModule, + GovFailingModule, + CustomStargateKeeper, +>; + +// TODO: Notes +// 1. The ls/lp forwader config in the single party is really confusing, because you only use like half of the fields in the actual contract, I think a special config +// that is needed. +// 2. forwarder config is very confusing, local means the receiving end, while remote means the sending end, while this makes sense for when the forwarder +// forwards funds to neutron (so neutron is local in this case), it doesn't when it comes to ls config, where local is stride and remote is atom. + +// Denoms +pub const DENOM_FALLBACK: &str = "ufallback"; +pub const DENOM_ATOM: &str = "uatom"; +pub const DENOM_NTRN: &str = "untrn"; +pub const DENOM_OSMO: &str = "uosmo"; + +/// This is used to fund the fuacet with all possible denoms we have. +/// so funding accounts and addresses can be done using the transfer msg +/// To fund the fuacet with another denom, just add the denom to the array +pub const ALL_DENOMS: &[&str] = &[ + DENOM_ATOM, + DENOM_NTRN, + DENOM_OSMO, + DENOM_FALLBACK, + DENOM_ATOM_ON_NTRN, + DENOM_LS_ATOM_ON_NTRN, + DENOM_FALLBACK_ON_HUB, + DENOM_OSMO_ON_HUB_FROM_NTRN, +]; + +// Addrs +pub const FAUCET: &str = "faucet_addr"; +pub const ADMIN: &str = "admin_addr"; + +// Salts for easier use (can append a number if more then 1 contract is needed) +pub const CLOCK_SALT: &str = "clock"; +pub const SWAP_COVENANT_SALT: &str = "swap_covenant"; +pub const SINGLE_PARTY_COVENANT_SALT: &str = "single_party_covenant"; +pub const TWO_PARTY_COVENANT_SALT: &str = "two_party_covenant"; +pub const SWAP_HOLDER_SALT: &str = "swap_holder"; +pub const TWO_PARTY_HOLDER_SALT: &str = "two_party_holder"; +pub const SINGLE_PARTY_HOLDER_SALT: &str = "single_party_holder"; +pub const ASTRO_LIQUID_POOLER_SALT: &str = "astro_liquid_pooler"; +pub const NATIVE_SPLITTER_SALT: &str = "native_splitter"; +pub const REMOTE_CHAIN_SPLITTER_SALT: &str = "remote_chain_splitter"; +pub const INTERCHAIN_ROUTER_SALT: &str = "interchain_router"; +pub const NATIVE_ROUTER_SALT: &str = "native_router"; +pub const IBC_FORWARDER_SALT: &str = "ibc_forwarder"; + +// Channels between the chains +pub const NTRN_HUB_CHANNEL: (&str, &str) = ("channel-1", "channel-100"); +pub const NTRN_OSMO_CHANNEL: (&str, &str) = ("channel-2", "channel-200"); +pub const HUB_OSMO_CHANNEL: (&str, &str) = ("channel-3", "channel-300"); +pub const HUB_STRIDE_CHANNEL: (&str, &str) = ("channel-4", "channel-400"); +pub const NTRN_STRIDE_CHANNEL: (&str, &str) = ("channel-5", "channel-500"); + +// IBC denoms + +/// ntrn -> osmo +pub const DENOM_FALLBACK_ON_OSMO: &str = concatcp!(NTRN_OSMO_CHANNEL.1, "/", DENOM_FALLBACK); +pub const DENOM_FALLBACK_ON_HUB: &str = concatcp!(NTRN_HUB_CHANNEL.1, "/", DENOM_FALLBACK); + +// LS tokens on stride +/// lsATOM on stride +pub const DENOM_LS_ATOM_ON_STRIDE: &str = concatcp!(HUB_STRIDE_CHANNEL.1, "/", DENOM_ATOM); + +/// hub -> ntrn +pub const DENOM_ATOM_ON_NTRN: &str = concatcp!(NTRN_HUB_CHANNEL.0, "/", DENOM_ATOM); +/// ntrn -> hub +pub const DENOM_NTRN_ON_HUB: &str = concatcp!(NTRN_HUB_CHANNEL.1, "/", DENOM_NTRN); +/// osmo -> ntrn +pub const DENOM_OSMO_ON_NTRN: &str = concatcp!(NTRN_OSMO_CHANNEL.0, "/", DENOM_OSMO); +/// ntrn -> osmo +pub const DENOM_NTRN_ON_OSMO: &str = concatcp!(NTRN_OSMO_CHANNEL.1, "/", DENOM_NTRN); +/// lsATOM -> ntrn +pub const DENOM_LS_ATOM_ON_NTRN: &str = + concatcp!(NTRN_STRIDE_CHANNEL.0, "/", DENOM_LS_ATOM_ON_STRIDE); +/// osmo -> ntrn -> hub +pub const DENOM_OSMO_ON_HUB_FROM_NTRN: &str = + concatcp!(NTRN_HUB_CHANNEL.1, "/", DENOM_OSMO_ON_NTRN); +/// hub -> ntrn -> osmo +pub const DENOM_HUB_ON_OSMO_FROM_NTRN: &str = + concatcp!(NTRN_OSMO_CHANNEL.1, "/", DENOM_ATOM_ON_NTRN); diff --git a/unit-tests/src/setup/suite_builder.rs b/unit-tests/src/setup/suite_builder.rs new file mode 100644 index 00000000..f4188cc1 --- /dev/null +++ b/unit-tests/src/setup/suite_builder.rs @@ -0,0 +1,463 @@ +use std::str::FromStr; + +use astroport::pair_concentrated::ConcentratedPoolParams; +use cosmwasm_schema::serde::Serialize; +use cosmwasm_std::{ + coin, coins, instantiate2_address, to_json_binary, Addr, Api, CodeInfoResponse, Coin, Decimal, + Empty, +}; +use cw_multi_test::{ + addons::{MockAddressGenerator, MockApiBech32}, + BasicAppBuilder, Executor, Stargate, StargateMsg, StargateQuery, WasmKeeper, +}; + +use sha2::{Digest, Sha256}; + +use super::{ + astro_contracts::{ + astro_coin_registry_contract, astro_factory_contract, + astro_pair_custom_concentrated_contract, astro_pair_stable_contract, + astro_pair_xyk_contract, astro_token_contract, astro_whitelist_contract, + }, + contracts::{ + astroport_pooler_contract, clock_contract, ibc_forwarder_contract, + interchain_router_contract, native_router_contract, native_splitter_contract, + osmo_lp_outpost_contract, remote_splitter_contract, single_party_covenant_contract, + single_party_holder_contract, stride_lser_contract, swap_covenant_contract, + swap_holder_contract, two_party_covenant_contract, two_party_holder_contract, + }, + custom_keepers::CustomStargateKeeper, + custom_module::{NeutronKeeper, CHAIN_PREFIX}, + CustomApp, ADMIN, ALL_DENOMS, DENOM_NTRN, FAUCET, HUB_OSMO_CHANNEL, HUB_STRIDE_CHANNEL, + NTRN_HUB_CHANNEL, NTRN_OSMO_CHANNEL, NTRN_STRIDE_CHANNEL, +}; + +pub type StargateKeeper = CustomStargateKeeper; +impl Stargate for StargateKeeper {} + +pub struct SuiteBuilder { + pub faucet: Addr, + pub admin: Addr, + + pub app: CustomApp, + + pub addr_counter: u64, + + // Covenant contracts code ids + pub swap_covenant_code_id: u64, + pub single_party_covenant_code_id: u64, + pub two_party_covenant_code_id: u64, + + // Modules code ids + pub clock_code_id: u64, + pub swap_holder_code_id: u64, + pub single_party_holder_code_id: u64, + pub ibc_forwarder_code_id: u64, + pub native_router_code_id: u64, + pub interchain_router_code_id: u64, + pub remote_splitter_code_id: u64, + pub native_splitter_code_id: u64, + pub astro_pooler_code_id: u64, + pub stride_staker_code_id: u64, + pub two_party_holder_code_id: u64, + pub osmo_lp_outpost_code_id: u64, + + // astro contracts + pub astro_token_code_id: u64, + pub astro_whitelist_code_id: u64, + pub astro_factory_code_id: u64, + pub astro_pair_stable_code_id: u64, + pub astro_pair_xyk_code_id: u64, + pub astro_coin_registry_code_id: u64, + pub astro_pair_concentrated_code_id: u64, +} +impl Default for SuiteBuilder { + fn default() -> Self { + Self::new() + } +} + +impl SuiteBuilder { + pub fn new() -> Self { + let mut app = BasicAppBuilder::new_custom() + .with_custom(NeutronKeeper::new(CHAIN_PREFIX)) + .with_stargate(StargateKeeper::new("execute", "query", "sudo")) + .with_api(MockApiBech32::new(CHAIN_PREFIX)) + .with_wasm(WasmKeeper::default().with_address_generator(MockAddressGenerator)) + .build(|r, _, s| { + let balances: Vec = ALL_DENOMS + .iter() + .map(|d| coin(1_000_000_000_000_000_000_000_000_u128, d.to_string())) + .collect(); + + r.bank + .init_balance( + s, + &MockApiBech32::new(CHAIN_PREFIX).addr_make(FAUCET), + balances, + ) + .unwrap(); + + r.custom + .add_local_channel(s, NTRN_HUB_CHANNEL.0, NTRN_HUB_CHANNEL.1) + .unwrap(); + r.custom + .add_local_channel(s, NTRN_OSMO_CHANNEL.0, NTRN_OSMO_CHANNEL.1) + .unwrap(); + r.custom + .add_local_channel(s, NTRN_STRIDE_CHANNEL.0, NTRN_STRIDE_CHANNEL.1) + .unwrap(); + + r.custom + .add_remote_channel(s, HUB_OSMO_CHANNEL.0, HUB_OSMO_CHANNEL.1) + .unwrap(); + + r.custom + .add_remote_channel(s, HUB_STRIDE_CHANNEL.0, HUB_STRIDE_CHANNEL.1) + .unwrap(); + }); + + let swap_covenant_code_id = app.store_code(swap_covenant_contract()); + let single_party_covenant_code_id = app.store_code(single_party_covenant_contract()); + + let clock_code_id = app.store_code(clock_contract()); + let swap_holder_code_id = app.store_code(swap_holder_contract()); + let single_party_holder_code_id = app.store_code(single_party_holder_contract()); + let remote_splitter_code_id = app.store_code(remote_splitter_contract()); + let native_splitter_code_id = app.store_code(native_splitter_contract()); + let interchain_router_code_id = app.store_code(interchain_router_contract()); + let native_router_code_id = app.store_code(native_router_contract()); + let ibc_forwarder_code_id = app.store_code(ibc_forwarder_contract()); + let astro_pooler_code_id = app.store_code(astroport_pooler_contract()); + let stride_staker_code_id = app.store_code(stride_lser_contract()); + let two_party_holder_code_id = app.store_code(two_party_holder_contract()); + let osmo_lp_outpost_code_id = app.store_code(osmo_lp_outpost_contract()); + + let astro_token_code_id = app.store_code(astro_token_contract()); + let astro_whitelist_code_id = app.store_code(astro_whitelist_contract()); + let astro_factory_code_id = app.store_code(astro_factory_contract()); + let astro_pair_stable_code_id = app.store_code(astro_pair_stable_contract()); + let astro_pair_xyk_code_id = app.store_code(astro_pair_xyk_contract()); + let astro_pair_concentrated_code_id = + app.store_code(astro_pair_custom_concentrated_contract()); + let astro_coin_registry_code_id = app.store_code(astro_coin_registry_contract()); + + let two_party_covenant_code_id = app.store_code(two_party_covenant_contract()); + + Self { + faucet: app.api().addr_make(FAUCET), + admin: app.api().addr_make(ADMIN), + + app, + addr_counter: 0, + + swap_covenant_code_id, + single_party_covenant_code_id, + two_party_covenant_code_id, + + clock_code_id, + swap_holder_code_id, + single_party_holder_code_id, + ibc_forwarder_code_id, + native_router_code_id, + interchain_router_code_id, + remote_splitter_code_id, + native_splitter_code_id, + astro_pooler_code_id, + stride_staker_code_id, + two_party_holder_code_id, + osmo_lp_outpost_code_id, + + astro_token_code_id, + astro_whitelist_code_id, + astro_factory_code_id, + astro_pair_stable_code_id, + astro_pair_xyk_code_id, + astro_coin_registry_code_id, + astro_pair_concentrated_code_id, + } + } + + // Init pool and return the addr + pub fn init_astro_pool( + &mut self, + pair_type: astroport::factory::PairType, + coin_a: Coin, + coin_b: Coin, + ) -> (Addr, Addr) { + let registery_init = astroport::native_coin_registry::InstantiateMsg { + owner: self.admin.to_string(), + }; + let coin_registry_addr = self + .app + .instantiate_contract( + self.astro_coin_registry_code_id, + self.admin.clone(), + ®istery_init, + &[], + "native coin registry", + None, + ) + .unwrap(); + self.app.update_block(|b| b.height += 5); + + // Add coins to the registery + self.app + .execute_contract( + self.admin.clone(), + coin_registry_addr.clone(), + &astroport::native_coin_registry::ExecuteMsg::Add { + native_coins: vec![(coin_a.denom.clone(), 6), (coin_b.denom.clone(), 6)], + }, + &[], + ) + .unwrap(); + self.app.update_block(|b| b.height += 5); + + //init factory + let factory_init = astroport::factory::InstantiateMsg { + pair_configs: vec![ + astroport::factory::PairConfig { + code_id: self.astro_pair_stable_code_id, + pair_type: astroport::factory::PairType::Stable {}, + total_fee_bps: 0, + maker_fee_bps: 0, + is_disabled: false, + is_generator_disabled: true, + }, + astroport::factory::PairConfig { + code_id: self.astro_pair_xyk_code_id, + pair_type: astroport::factory::PairType::Xyk {}, + total_fee_bps: 0, + maker_fee_bps: 0, + is_disabled: false, + is_generator_disabled: true, + }, + astroport::factory::PairConfig { + code_id: self.astro_pair_concentrated_code_id, + pair_type: astroport::factory::PairType::Custom("concentrated".to_string()), + total_fee_bps: 0, + maker_fee_bps: 0, + is_disabled: false, + is_generator_disabled: false, + }, + ], + token_code_id: self.astro_token_code_id, + fee_address: None, + generator_address: None, + owner: self.admin.to_string(), + whitelist_code_id: self.astro_whitelist_code_id, + coin_registry_address: coin_registry_addr.to_string(), + }; + let factory_addr = self + .app + .instantiate_contract( + self.astro_factory_code_id, + self.admin.clone(), + &factory_init, + &[], + "factory", + None, + ) + .unwrap(); + self.app.update_block(|b| b.height += 5); + + let asset_infos = vec![ + astroport::asset::AssetInfo::NativeToken { + denom: coin_a.denom.clone(), + }, + astroport::asset::AssetInfo::NativeToken { + denom: coin_b.denom.clone(), + }, + ]; + + let init_params = match &pair_type { + astroport::factory::PairType::Stable {} => { + to_json_binary(&astroport::pair::StablePoolParams { + amp: 1, + owner: Some(self.admin.to_string()), + }) + .unwrap() + } + astroport::factory::PairType::Xyk {} => { + to_json_binary(&astroport::pair::XYKPoolParams { + track_asset_balances: None, + }) + .unwrap() + } + astroport::factory::PairType::Custom(_) => { + let default_params = ConcentratedPoolParams { + amp: Decimal::from_ratio(40u128, 1u128), + gamma: Decimal::from_ratio(145u128, 1000000u128), + mid_fee: Decimal::from_str("0.0026").unwrap(), + out_fee: Decimal::from_str("0.0045").unwrap(), + fee_gamma: Decimal::from_ratio(23u128, 100000u128), + repeg_profit_threshold: Decimal::from_ratio(2u128, 1000000u128), + min_price_scale_delta: Decimal::from_ratio(146u128, 1000000u128), + price_scale: Decimal::one(), + ma_half_time: 600, + track_asset_balances: None, + }; + to_json_binary(&default_params).unwrap() + } + }; + + let init_pair_msg = astroport::factory::ExecuteMsg::CreatePair { + pair_type: pair_type.clone(), + asset_infos: asset_infos.clone(), + init_params: Some(init_params), + }; + self.app + .execute_contract( + self.admin.clone(), + factory_addr.clone(), + &init_pair_msg, + &[], + ) + .unwrap(); + self.app.update_block(|b| b.height += 5); + let pool_info = self + .app + .wrap() + .query_wasm_smart::( + factory_addr.clone(), + &astroport::factory::QueryMsg::Pair { asset_infos }, + ) + .unwrap(); + + // provide liquidity to the pool + let balances = vec![coin_a.clone(), coin_b.clone()]; + + let assets = vec![ + astroport::asset::Asset { + info: astroport::asset::AssetInfo::NativeToken { + denom: coin_a.denom.to_string(), + }, + amount: coin_a.amount, + }, + astroport::asset::Asset { + info: astroport::asset::AssetInfo::NativeToken { + denom: coin_b.denom, + }, + amount: coin_b.amount, + }, + ]; + + let provide_liquidity_msg = astroport::pair::ExecuteMsg::ProvideLiquidity { + assets, + slippage_tolerance: None, + auto_stake: Some(false), + receiver: Some(self.faucet.to_string()), + }; + + self.app + .execute_contract( + self.faucet.clone(), + pool_info.contract_addr.clone(), + &provide_liquidity_msg, + &balances, + ) + .unwrap(); + + (pool_info.contract_addr, pool_info.liquidity_token) + } + /// Add IBC channels for the neutron module + pub fn add_channels( + &mut self, + local: Vec<(&str, &str)>, + remote: Vec<(&str, &str)>, + ) -> &mut Self { + self.app.init_modules(|r, _, s| { + local + .iter() + .for_each(|(source, other)| r.custom.add_local_channel(s, source, other).unwrap()); + remote + .iter() + .for_each(|(some, other)| r.custom.add_remote_channel(s, some, other).unwrap()); + }); + self + } + + pub fn fund_with_ntrn(&mut self, addr: &Addr, amount: u128) { + self.app + .send_tokens( + self.faucet.clone(), + addr.clone(), + &coins(amount, DENOM_NTRN), + ) + .unwrap(); + } + + // Consume the builder and return the app + pub fn build(self) -> CustomApp { + self.app + } +} + +impl SuiteBuilder { + pub fn get_random_addr(&mut self) -> Addr { + self.addr_counter += 1; + self.app + .api() + .addr_make(format!("random_addr-{}", self.addr_counter).as_str()) + } + + pub fn get_contract_addr(&mut self, code_id: u64, salt: &str) -> Addr { + let mut hasher = Sha256::new(); + hasher.update(salt); + let salt = hasher.finalize().to_vec(); + + let canonical_creator = self + .app + .api() + .addr_canonicalize(self.app.api().addr_make(ADMIN).as_str()) + .unwrap(); + let CodeInfoResponse { checksum, .. } = + self.app.wrap().query_wasm_code_info(code_id).unwrap(); + let canonical_addr = instantiate2_address(&checksum, &canonical_creator, &salt).unwrap(); + self.app.api().addr_humanize(&canonical_addr).unwrap() + } + + pub fn contract_init( + &mut self, + code_id: u64, + label: String, + init_msg: &M, + funds: &[Coin], + ) -> Addr { + self.app + .instantiate_contract( + code_id, + self.app.api().addr_make(ADMIN), + &init_msg, + funds, + label, + Some(ADMIN.to_string()), + ) + .unwrap() + } + + pub fn contract_init2( + &mut self, + code_id: u64, + salt: &str, + init_msg: &M, + funds: &[Coin], + ) -> Addr { + let mut hasher = Sha256::new(); + hasher.update(salt); + let hashed_salt = hasher.finalize().to_vec(); + + self.app + .instantiate2_contract( + code_id, + self.app.api().addr_make(ADMIN), + &init_msg, + funds, + salt.to_string(), + Some(ADMIN.to_string()), + hashed_salt, + ) + .unwrap() + } +} diff --git a/contracts/lper/src/suite_test/mod.rs b/unit-tests/src/test_astroport_liquid_pooler/mod.rs similarity index 100% rename from contracts/lper/src/suite_test/mod.rs rename to unit-tests/src/test_astroport_liquid_pooler/mod.rs diff --git a/unit-tests/src/test_astroport_liquid_pooler/suite.rs b/unit-tests/src/test_astroport_liquid_pooler/suite.rs new file mode 100644 index 00000000..ea888954 --- /dev/null +++ b/unit-tests/src/test_astroport_liquid_pooler/suite.rs @@ -0,0 +1,314 @@ +use astroport::factory::PairType; +use cosmwasm_std::{coin, Addr, Coin, Decimal}; +use covenant_utils::{PoolPriceConfig, SingleSideLpLimits}; +use cw_multi_test::{AppResponse, Executor}; +use cw_utils::Expiration; +use valence_astroport_liquid_pooler::msg::{LpConfig, ProvidedLiquidityInfo, QueryMsg}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::astro_liquid_pooler::AstroLiquidPoolerInstantiate, + suite_builder::SuiteBuilder, + CustomApp, ASTRO_LIQUID_POOLER_SALT, CLOCK_SALT, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, + SINGLE_PARTY_HOLDER_SALT, +}; + +pub struct AstroLiquidPoolerBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: AstroLiquidPoolerInstantiate, +} + +impl Default for AstroLiquidPoolerBuilder { + fn default() -> Self { + let mut builder = SuiteBuilder::new(); + + // init astro pools + let (pool_addr, _lp_token_addr) = builder.init_astro_pool( + astroport::factory::PairType::Stable {}, + coin(10_000_000_000_000, DENOM_ATOM_ON_NTRN), + coin(10_000_000_000_000, DENOM_LS_ATOM_ON_NTRN), + ); + + let clock_addr = builder.get_contract_addr(builder.clock_code_id, CLOCK_SALT); + let liquid_pooler_addr = + builder.get_contract_addr(builder.astro_pooler_code_id, ASTRO_LIQUID_POOLER_SALT); + + let holder_addr = builder.get_contract_addr( + builder.single_party_holder_code_id, + SINGLE_PARTY_HOLDER_SALT, + ); + + let holder_instantiate_msg = valence_single_party_pol_holder::msg::InstantiateMsg { + withdrawer: clock_addr.to_string(), + withdraw_to: holder_addr.to_string(), + emergency_committee_addr: None, + pooler_address: liquid_pooler_addr.to_string(), + lockup_period: cw_utils::Expiration::AtHeight(123665), + }; + + let clock_instantiate_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: None, + whitelist: vec![liquid_pooler_addr.to_string()], + }; + + builder.contract_init2( + builder.clock_code_id, + CLOCK_SALT, + &clock_instantiate_msg, + &[], + ); + builder.contract_init2( + builder.single_party_holder_code_id, + SINGLE_PARTY_HOLDER_SALT, + &holder_instantiate_msg, + &[], + ); + + let liquid_pooler_instantiate = AstroLiquidPoolerInstantiate::default( + pool_addr.to_string(), + clock_addr.to_string(), + holder_addr.to_string(), + ); + + AstroLiquidPoolerBuilder { + builder, + instantiate_msg: liquid_pooler_instantiate, + } + } +} + +#[allow(dead_code)] +impl AstroLiquidPoolerBuilder { + pub fn with_custom_astroport_pool( + mut self, + pair_type: PairType, + coin_a: Coin, + coin_b: Coin, + ) -> Self { + let (pool_addr, _lp_token_addr) = self.builder.init_astro_pool(pair_type, coin_a, coin_b); + self.instantiate_msg + .with_pool_address(pool_addr.to_string()); + self + } + + pub fn with_pool_address(mut self, pool_address: String) -> Self { + self.instantiate_msg.with_pool_address(pool_address); + self + } + + pub fn with_clock_address(mut self, clock_address: String) -> Self { + self.instantiate_msg.with_clock_address(clock_address); + self + } + + pub fn with_slippage_tolerance(mut self, slippage_tolerance: Option) -> Self { + self.instantiate_msg + .with_slippage_tolerance(slippage_tolerance); + self + } + + pub fn with_assets(mut self, assets: valence_astroport_liquid_pooler::msg::AssetData) -> Self { + self.instantiate_msg.with_assets(assets); + self + } + + pub fn with_single_side_lp_limits(mut self, single_side_lp_limits: SingleSideLpLimits) -> Self { + self.instantiate_msg + .with_single_side_lp_limits(single_side_lp_limits); + self + } + + pub fn with_pool_price_config(mut self, pool_price_config: PoolPriceConfig) -> Self { + self.instantiate_msg + .with_pool_price_config(pool_price_config); + self + } + + pub fn with_pair_type(mut self, pair_type: PairType) -> Self { + self.instantiate_msg.with_pair_type(pair_type); + self + } + + pub fn with_holder_address(mut self, holder_address: String) -> Self { + self.instantiate_msg.with_holder_address(holder_address); + self + } + + pub fn build(mut self) -> Suite { + let liquid_pooler_address = self.builder.contract_init2( + self.builder.astro_pooler_code_id, + ASTRO_LIQUID_POOLER_SALT, + &self.instantiate_msg.msg, + &[], + ); + + let clock_addr: Addr = self + .builder + .app + .wrap() + .query_wasm_smart( + liquid_pooler_address.to_string(), + &QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let holder_addr: Addr = self + .builder + .app + .wrap() + .query_wasm_smart( + liquid_pooler_address.to_string(), + &QueryMsg::HolderAddress {}, + ) + .unwrap(); + + let lp_config: LpConfig = self + .builder + .app + .wrap() + .query_wasm_smart(liquid_pooler_address.to_string(), &QueryMsg::LpConfig {}) + .unwrap(); + + let provided_liquidity_info: ProvidedLiquidityInfo = self + .builder + .app + .wrap() + .query_wasm_smart( + liquid_pooler_address.to_string(), + &QueryMsg::ProvidedLiquidityInfo {}, + ) + .unwrap(); + + let faucet = self.builder.faucet.clone(); + let admin = self.builder.admin.clone(); + + Suite { + faucet, + admin, + liquid_pooler_addr: liquid_pooler_address.clone(), + clock_addr: clock_addr.clone(), + holder_addr: holder_addr.clone(), + lp_config: lp_config.clone(), + provided_liquidity_info: provided_liquidity_info.clone(), + app: self.builder.build(), + } + } +} + +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + + pub liquid_pooler_addr: Addr, + pub clock_addr: Addr, + pub holder_addr: Addr, + pub lp_config: LpConfig, + pub provided_liquidity_info: ProvidedLiquidityInfo, +} + +#[allow(dead_code)] +impl Suite { + pub(crate) fn withdraw(&mut self, sender: &Addr, _percentage: Option) -> AppResponse { + let holder = self.holder_addr.clone(); + let app = self.get_app(); + app.execute_contract( + sender.clone(), + holder, + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap() + } + + pub(crate) fn expire_lockup(&mut self) { + let holder = self.holder_addr.clone(); + let expiration: Expiration = self + .app + .wrap() + .query_wasm_smart( + holder.to_string(), + &valence_single_party_pol_holder::msg::QueryMsg::LockupConfig {}, + ) + .unwrap(); + let app = self.get_app(); + app.update_block(|b| match expiration { + Expiration::AtHeight(h) => b.height = h + 1, + Expiration::AtTime(t) => b.time = t, + Expiration::Never {} => (), + }) + } + + pub(crate) fn query_provided_liquidity_info(&self) -> ProvidedLiquidityInfo { + self.get_app() + .wrap() + .query_wasm_smart( + self.liquid_pooler_addr.clone(), + &valence_astroport_liquid_pooler::msg::QueryMsg::ProvidedLiquidityInfo {}, + ) + .unwrap() + } + + pub(crate) fn query_contract_state( + &self, + ) -> valence_astroport_liquid_pooler::msg::ContractState { + self.get_app() + .wrap() + .query_wasm_smart( + self.liquid_pooler_addr.clone(), + &valence_astroport_liquid_pooler::msg::QueryMsg::ContractState {}, + ) + .unwrap() + } + + pub(crate) fn query_clock_address(&self) -> Addr { + self.get_app() + .wrap() + .query_wasm_smart( + self.liquid_pooler_addr.clone(), + &valence_astroport_liquid_pooler::msg::QueryMsg::ClockAddress {}, + ) + .unwrap() + } + + pub(crate) fn query_holder_address(&self) -> Addr { + self.get_app() + .wrap() + .query_wasm_smart( + self.liquid_pooler_addr.clone(), + &valence_astroport_liquid_pooler::msg::QueryMsg::HolderAddress {}, + ) + .unwrap() + } + + pub(crate) fn query_lp_config(&self) -> LpConfig { + self.get_app() + .wrap() + .query_wasm_smart( + self.liquid_pooler_addr.clone(), + &valence_astroport_liquid_pooler::msg::QueryMsg::LpConfig {}, + ) + .unwrap() + } +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} diff --git a/unit-tests/src/test_astroport_liquid_pooler/tests.rs b/unit-tests/src/test_astroport_liquid_pooler/tests.rs new file mode 100644 index 00000000..ad920c15 --- /dev/null +++ b/unit-tests/src/test_astroport_liquid_pooler/tests.rs @@ -0,0 +1,825 @@ +use std::str::FromStr; + +use cosmwasm_std::{coin, coins, Addr, Decimal, Event, Uint128}; +use covenant_utils::PoolPriceConfig; +use cw_multi_test::Executor; +use valence_astroport_liquid_pooler::msg::{AssetData, ProvidedLiquidityInfo}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + ADMIN, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, +}; + +use super::suite::AstroLiquidPoolerBuilder; + +#[test] +#[should_panic] +fn test_instantiate_validates_clock_address() { + AstroLiquidPoolerBuilder::default() + .with_clock_address("not a clock".to_string()) + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_pool_address() { + AstroLiquidPoolerBuilder::default() + .with_pool_address("not a pool".to_string()) + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_holder_address() { + AstroLiquidPoolerBuilder::default() + .with_holder_address("not a holder".to_string()) + .build(); +} + +#[test] +#[should_panic(expected = "Cannot Sub with 1 and 2")] +fn test_instantiate_validates_pool_price_config_upper_bound() { + AstroLiquidPoolerBuilder::default() + .with_pool_price_config(PoolPriceConfig { + expected_spot_price: Decimal::from_str("1.0").unwrap(), + acceptable_price_spread: Decimal::from_str("2.0").unwrap(), + }) + .build(); +} + +#[test] +#[should_panic(expected = "Cannot Sub with 0.5 and 0.6")] +fn test_instantiate_validates_pool_price_config_lower_bound() { + AstroLiquidPoolerBuilder::default() + .with_pool_price_config(PoolPriceConfig { + expected_spot_price: Decimal::from_str("0.5").unwrap(), + acceptable_price_spread: Decimal::from_str("0.6").unwrap(), + }) + .build(); +} + +#[test] +#[should_panic(expected = "Pair type mismatch")] +fn test_instantiate_validates_pool_pair_type() { + AstroLiquidPoolerBuilder::default() + .with_custom_astroport_pool( + astroport::factory::PairType::Xyk {}, + coin(1_000_000, DENOM_ATOM_ON_NTRN), + coin(1_000_000, DENOM_LS_ATOM_ON_NTRN), + ) + .build(); +} + +#[test] +#[should_panic(expected = "Withdraw percentage range must belong to range (0.0, 1.0]")] +fn test_withdraw_validates_percentage_range_ceiling() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + + suite.fund_contract( + &coins(1_000_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(500_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); + suite.expire_lockup(); + let holder: Addr = suite.holder_addr.clone(); + suite + .app + .execute_contract( + holder.clone(), + suite.liquid_pooler_addr.clone(), + &valence_astroport_liquid_pooler::msg::ExecuteMsg::Withdraw { + percentage: Some(Decimal::from_str("101.0").unwrap()), + }, + &[], + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "Withdraw percentage range must belong to range (0.0, 1.0]")] +fn test_withdraw_validates_percentage_range_floor() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + + suite.fund_contract( + &coins(1_000_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(500_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + let holder = suite.holder_addr.clone(); + suite + .app + .execute_contract( + holder.clone(), + suite.liquid_pooler_addr.clone(), + &valence_astroport_liquid_pooler::msg::ExecuteMsg::Withdraw { + percentage: Some(Decimal::from_str("0.0").unwrap()), + }, + &[], + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "Only holder can withdraw the position")] +fn test_withdraw_validates_holder() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + let not_the_holder = suite.faucet.clone(); + + suite + .app + .execute_contract( + not_the_holder, + suite.liquid_pooler_addr.clone(), + &valence_astroport_liquid_pooler::msg::ExecuteMsg::Withdraw { percentage: None }, + &[], + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "no covenant denom or lp tokens available")] +fn test_withdraw_no_lp_or_covenant_denoms() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + let withdrawer = suite.clock_addr.clone(); + suite.expire_lockup(); + suite.withdraw(&withdrawer, None); +} + +#[test] +fn test_withdraw_no_lp_tokens_withdraws_covenant_assets() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + suite.fund_contract( + &coins(500_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(500_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + let withdrawer = suite.clock_addr.clone(); + + suite.assert_balance(suite.holder_addr.clone(), coin(0, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(suite.holder_addr.clone(), coin(0, DENOM_LS_ATOM_ON_NTRN)); + suite.expire_lockup(); + suite.withdraw(&withdrawer, None); + + suite.assert_balance(suite.holder_addr.clone(), coin(500_000, DENOM_ATOM_ON_NTRN)); + suite.assert_balance( + suite.holder_addr.clone(), + coin(500_000, DENOM_LS_ATOM_ON_NTRN), + ); +} + +#[test] +fn test_withdraw_no_percentage_defaults_to_full_position() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + let withdrawer = suite.clock_addr.clone(); + let holder = suite.holder_addr.clone(); + + suite.fund_contract( + &coins(500_001, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(500_001, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); + suite.expire_lockup(); + suite.withdraw(&withdrawer, None); + + suite.assert_balance(&holder, coin(500_000, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&holder, coin(500_000, DENOM_LS_ATOM_ON_NTRN)); +} + +#[test] +#[should_panic(expected = "Caller is not the clock, only clock can tick contracts")] +fn test_tick_unauthorized() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + let unauthorized_sender = suite.admin.clone(); + + suite + .app + .execute_contract( + unauthorized_sender, + suite.liquid_pooler_addr.clone(), + &valence_clock::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "Pair type mismatch")] +fn test_provide_liquidity_validates_pair_type() { + let mut suite = AstroLiquidPoolerBuilder::default() + .with_pair_type(astroport::factory::PairType::Xyk {}) + .build(); + + suite.fund_contract( + &coins(1_000_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(1_000_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); +} + +#[test] +#[should_panic(expected = "all pool assets must be non-zero")] +fn test_provide_liquidity_determine_pool_ratio_asset_b_denom_invalid() { + let mut suite = AstroLiquidPoolerBuilder::default() + .with_assets(AssetData { + asset_a_denom: DENOM_ATOM_ON_NTRN.to_string(), + asset_b_denom: "invalid denom".to_string(), + }) + .build(); + + suite.fund_contract( + &coins(1_000_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(1_000_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); +} + +#[test] +#[should_panic(expected = "Price range error")] +fn test_provide_liquidity_validates_pool_ratio() { + let mut suite = AstroLiquidPoolerBuilder::default() + .with_pool_price_config(PoolPriceConfig { + expected_spot_price: Decimal::from_str("3").unwrap(), + acceptable_price_spread: Decimal::from_str("0.1").unwrap(), + }) + .build(); + + suite.fund_contract( + &coins(1_000_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(1_000_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); +} + +#[test] +fn test_provide_liquidity_no_assets() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + + suite + .tick_contract(suite.liquid_pooler_addr.clone()) + .assert_event(&Event::new("wasm").add_attribute("status", "not enough funds")); +} + +#[test] +fn test_provide_stable_liquidity_single_side_asset_a() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + + suite.fund_contract( + &coins(570_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(500_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(70_000, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); + + suite + .tick_contract(suite.liquid_pooler_addr.clone()) + .assert_event(&Event::new("wasm").add_attribute("method", "single_side_lp")); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(570_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); +} + +#[test] +fn test_provide_custom_concentrated_liquidity_single_side_asset_a() { + let custom_concentrated_pair_type = + astroport::factory::PairType::Custom("concentrated".to_string()); + let mut suite = AstroLiquidPoolerBuilder::default() + .with_custom_astroport_pool( + custom_concentrated_pair_type.clone(), + coin(1_000_000_000, DENOM_ATOM_ON_NTRN), + coin(1_000_000_000, DENOM_LS_ATOM_ON_NTRN), + ) + .with_pair_type(custom_concentrated_pair_type) + .build(); + + suite.fund_contract( + &coins(570_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(500_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(70_000, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); + + suite + .tick_contract(suite.liquid_pooler_addr.clone()) + .assert_event(&Event::new("wasm").add_attribute("method", "single_side_lp")); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(570_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); +} + +#[test] +fn test_provide_xyk_liquidity_single_side_asset_a() { + let mut suite = AstroLiquidPoolerBuilder::default() + .with_custom_astroport_pool( + astroport::factory::PairType::Xyk {}, + coin(1_000_000_000, DENOM_ATOM_ON_NTRN), + coin(1_000_000_000, DENOM_LS_ATOM_ON_NTRN), + ) + .with_pair_type(astroport::factory::PairType::Xyk {}) + .build(); + + suite.fund_contract( + &coins(570_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(500_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + // first tick double-side lps + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(70_000, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); + + // second tick swaps and then double side lps + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + + let provided_liquidity_info = suite.query_provided_liquidity_info(); + assert_eq!( + provided_liquidity_info.provided_coin_a.amount, + Uint128::new(500_000 + 70_000 / 2) + ); + // minus fees + assert!(provided_liquidity_info.provided_coin_b.amount > Uint128::new(534_000)); +} + +#[test] +fn test_provide_xyk_liquidity_single_side_asset_b() { + let mut suite = AstroLiquidPoolerBuilder::default() + .with_custom_astroport_pool( + astroport::factory::PairType::Xyk {}, + coin(1_000_000_000, DENOM_ATOM_ON_NTRN), + coin(1_000_000_000, DENOM_LS_ATOM_ON_NTRN), + ) + .with_pair_type(astroport::factory::PairType::Xyk {}) + .build(); + + suite.fund_contract( + &coins(570_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(500_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + // first tick double-side lps + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(70_000, DENOM_LS_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); + + // second tick swaps and then double side lps + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + + let provided_liquidity_info = suite.query_provided_liquidity_info(); + assert_eq!( + provided_liquidity_info.provided_coin_b.amount, + Uint128::new(500_000 + 70_000 / 2) + ); + // minus fees + assert!(provided_liquidity_info.provided_coin_a.amount > Uint128::new(534_000)); +} + +#[test] +#[should_panic(expected = "Single side LP limit exceeded")] +fn test_provide_liquidity_single_side_asset_a_exceeds_limits() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + + suite.fund_contract( + &coins(1_000_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(500_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(500_000, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); +} + +#[test] +fn test_provide_liquidity_single_side_validates_single_side_limits() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + + suite.fund_contract( + &coins(500_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(570_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + let double_sided_response = suite.tick_contract(suite.liquid_pooler_addr.clone()); + double_sided_response + .assert_event(&Event::new("wasm").add_attribute("method", "double_side_lp")); + double_sided_response + .assert_event(&Event::new("wasm").add_attribute("method", "handle_double_sided_reply_id")); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(70_000, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); + + let app_response = suite.tick_contract(suite.liquid_pooler_addr.clone()); + app_response.assert_event(&Event::new("wasm").add_attribute("method", "single_side_lp")); + app_response + .assert_event(&Event::new("wasm").add_attribute("method", "handle_single_sided_reply_id")); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(570_000, DENOM_LS_ATOM_ON_NTRN) + } + ); +} + +#[test] +fn test_provide_liquidity_single_side_asset_b() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + + suite.fund_contract( + &coins(500_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(570_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + let double_sided_response = suite.tick_contract(suite.liquid_pooler_addr.clone()); + double_sided_response + .assert_event(&Event::new("wasm").add_attribute("method", "double_side_lp")); + double_sided_response + .assert_event(&Event::new("wasm").add_attribute("method", "handle_double_sided_reply_id")); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(70_000, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); + + let app_response = suite.tick_contract(suite.liquid_pooler_addr.clone()); + app_response.assert_event(&Event::new("wasm").add_attribute("method", "single_side_lp")); + app_response + .assert_event(&Event::new("wasm").add_attribute("method", "handle_single_sided_reply_id")); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(570_000, DENOM_LS_ATOM_ON_NTRN) + } + ); +} + +#[test] +#[should_panic(expected = "Single side LP limit exceeded")] +fn test_provide_liquidity_single_side_asset_b_exceeds_limits() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + + suite.fund_contract( + &coins(500_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(1_000_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(500_000, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); +} + +#[test] +fn test_provide_liquidity_double_side_excess_a_denom() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + + suite.fund_contract( + &coins(1_000_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(500_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(500_000, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); +} + +#[test] +fn test_provide_liquidity_double_side_excess_b_denom() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + + suite.fund_contract( + &coins(500_000, DENOM_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + suite.fund_contract( + &coins(1_000_000, DENOM_LS_ATOM_ON_NTRN), + suite.liquid_pooler_addr.clone(), + ); + + suite.tick_contract(suite.liquid_pooler_addr.clone()); + + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(0, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + suite.liquid_pooler_addr.clone(), + coin(500_000, DENOM_LS_ATOM_ON_NTRN), + ); + assert_eq!( + suite.query_provided_liquidity_info(), + ProvidedLiquidityInfo { + provided_coin_a: coin(500_000, DENOM_ATOM_ON_NTRN), + provided_coin_b: coin(500_000, DENOM_LS_ATOM_ON_NTRN) + } + ); +} + +#[test] +fn test_migrate_update_config() { + let mut suite = AstroLiquidPoolerBuilder::default().build(); + let liquid_pooler = suite.liquid_pooler_addr.clone(); + let clock = suite.clock_addr.clone(); + let holder = suite.holder_addr.clone(); + let mut lp_config = suite.lp_config.clone(); + lp_config.pair_type = astroport::factory::PairType::Xyk {}; + + // swap clock & holder, and update pair type + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + liquid_pooler, + &valence_astroport_liquid_pooler::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(holder.to_string()), + holder_address: Some(clock.to_string()), + lp_config: Some(Box::new(lp_config)), + }, + 11, + ) + .unwrap(); + + let lp_config = suite.query_lp_config(); + let holder_address = suite.query_holder_address(); + let clock_address = suite.query_clock_address(); + let contract_state = suite.query_contract_state(); + + assert_eq!(lp_config.pair_type, astroport::factory::PairType::Xyk {}); + assert_eq!(holder_address, clock); + assert_eq!(clock_address, holder); + assert_eq!( + contract_state, + valence_astroport_liquid_pooler::msg::ContractState::Instantiated {} + ); +} diff --git a/contracts/ls/src/suite_test/mod.rs b/unit-tests/src/test_ibc_forwarder/mod.rs similarity index 100% rename from contracts/ls/src/suite_test/mod.rs rename to unit-tests/src/test_ibc_forwarder/mod.rs diff --git a/unit-tests/src/test_ibc_forwarder/suite.rs b/unit-tests/src/test_ibc_forwarder/suite.rs new file mode 100644 index 00000000..ce78c945 --- /dev/null +++ b/unit-tests/src/test_ibc_forwarder/suite.rs @@ -0,0 +1,302 @@ +use std::str::FromStr; + +use cosmwasm_std::{Addr, Coin, Uint128, Uint64}; +use covenant_utils::neutron::RemoteChainInfo; +use cw_multi_test::{AppResponse, Executor}; +use cw_storage_plus::KeyDeserialize; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::ibc_forwarder::IbcForwarderInstantiate, + suite_builder::SuiteBuilder, + CustomApp, CLOCK_SALT, IBC_FORWARDER_SALT, +}; + +pub struct IbcForwarderBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: IbcForwarderInstantiate, +} + +#[allow(dead_code)] +impl IbcForwarderBuilder { + pub fn default() -> Self { + let mut builder = SuiteBuilder::new(); + + let clock_addr = builder.get_contract_addr(builder.clock_code_id, CLOCK_SALT); + let ibc_forwarder_addr = + builder.get_contract_addr(builder.ibc_forwarder_code_id, IBC_FORWARDER_SALT); + let next_contract_addr = + builder.get_contract_addr(builder.ibc_forwarder_code_id, "deposit_forwarder"); + + let clock_instantiate_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: None, + whitelist: vec![ + ibc_forwarder_addr.to_string(), + next_contract_addr.to_string(), + ], + }; + + builder.contract_init2( + builder.clock_code_id, + CLOCK_SALT, + &clock_instantiate_msg, + &[], + ); + + let next_contract_instantiate = + IbcForwarderInstantiate::default(clock_addr.to_string(), clock_addr.to_string()); + builder.contract_init2( + builder.ibc_forwarder_code_id, + "deposit_forwarder", + &next_contract_instantiate.msg, + &[], + ); + + let ibc_forwarder_instantiate = IbcForwarderInstantiate::default( + clock_addr.to_string(), + next_contract_addr.to_string(), + ); + + IbcForwarderBuilder { + builder, + instantiate_msg: ibc_forwarder_instantiate, + } + } + + pub fn with_denom(mut self, denom: String) -> Self { + self.instantiate_msg.with_denom(denom); + self + } + + pub fn with_amount(mut self, amount: Uint128) -> Self { + self.instantiate_msg.with_amount(amount); + self + } + + pub fn with_fallback_address(mut self, fallback_address: String) -> Self { + self.instantiate_msg.with_fallback_address(fallback_address); + self + } + + pub fn with_ibc_transfer_timeout(mut self, ibc_transfer_timeout: Uint64) -> Self { + self.instantiate_msg + .with_ibc_transfer_timeout(ibc_transfer_timeout); + self + } + + pub fn with_ica_timeout(mut self, ica_timeout: Uint64) -> Self { + self.instantiate_msg.with_ica_timeout(ica_timeout); + self + } + + pub fn with_next_contract(mut self, next_contract: String) -> Self { + self.instantiate_msg.with_next_contract(next_contract); + self + } + + pub fn with_clock_address(mut self, clock_address: String) -> Self { + self.instantiate_msg.with_clock_address(clock_address); + self + } + + pub fn with_remote_chain_connection_id(mut self, remote_chain_connection_id: String) -> Self { + self.instantiate_msg + .with_remote_chain_connection_id(remote_chain_connection_id); + self + } + + pub fn with_remote_chain_channel_id(mut self, remote_chain_channel_id: String) -> Self { + self.instantiate_msg + .with_remote_chain_channel_id(remote_chain_channel_id); + self + } + + pub fn build(mut self) -> Suite { + let ibc_forwarder_address = self.builder.contract_init2( + self.builder.ibc_forwarder_code_id, + IBC_FORWARDER_SALT, + &self.instantiate_msg.msg, + &[], + ); + + let clock_addr = self + .builder + .app + .wrap() + .query_wasm_smart( + ibc_forwarder_address.clone(), + &valence_ibc_forwarder::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let remote_chain_info = self + .builder + .app + .wrap() + .query_wasm_smart( + ibc_forwarder_address.clone(), + &valence_ibc_forwarder::msg::QueryMsg::RemoteChainInfo {}, + ) + .unwrap(); + + let deposit_address = self + .builder + .app + .wrap() + .query_wasm_smart( + ibc_forwarder_address.clone(), + &valence_ibc_forwarder::msg::QueryMsg::DepositAddress {}, + ) + .unwrap(); + + Suite { + app: self.builder.app, + faucet: self.builder.faucet, + admin: self.builder.admin, + clock_addr, + ibc_forwarder: ibc_forwarder_address, + remote_chain_info, + deposit_address, + } + } +} + +#[allow(dead_code)] +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + pub clock_addr: Addr, + pub ibc_forwarder: Addr, + pub remote_chain_info: RemoteChainInfo, + pub deposit_address: Option, +} + +impl Suite { + pub(crate) fn query_deposit_address(&mut self) -> String { + self.app + .wrap() + .query_wasm_smart( + self.ibc_forwarder.clone(), + &valence_ibc_forwarder::msg::QueryMsg::DepositAddress {}, + ) + .unwrap() + } + + pub(crate) fn query_remote_chain_info(&mut self) -> RemoteChainInfo { + self.app + .wrap() + .query_wasm_smart( + self.ibc_forwarder.clone(), + &valence_ibc_forwarder::msg::QueryMsg::RemoteChainInfo {}, + ) + .unwrap() + } + + pub(crate) fn query_clock_address(&mut self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.ibc_forwarder.clone(), + &valence_ibc_forwarder::msg::QueryMsg::ClockAddress {}, + ) + .unwrap() + } + + pub(crate) fn query_contract_state(&mut self) -> valence_ibc_forwarder::msg::ContractState { + self.app + .wrap() + .query_wasm_smart( + self.ibc_forwarder.clone(), + &valence_ibc_forwarder::msg::QueryMsg::ContractState {}, + ) + .unwrap() + } + + pub(crate) fn query_ica_address(&mut self, addr: Addr) -> Addr { + self.app + .wrap() + .query_wasm_smart(addr, &valence_ibc_forwarder::msg::QueryMsg::IcaAddress {}) + .unwrap() + } + + // temp fix until we add a query + pub(crate) fn query_next_contract(&mut self) -> Addr { + let resp = self + .app + .wrap() + .query_wasm_raw(self.ibc_forwarder.clone(), "next_contract".as_bytes()) + .unwrap(); + + let mut val = resp.unwrap().split_off(1); + val.truncate(val.len() - 1); + Addr::from_slice(&val).unwrap() + } + + pub fn query_fallback_address(&mut self) -> Option { + self.app + .wrap() + .query_wasm_smart( + self.ibc_forwarder.clone(), + &valence_ibc_forwarder::msg::QueryMsg::FallbackAddress {}, + ) + .unwrap() + } + + // temp fix until we add a query + pub(crate) fn query_transfer_amount(&mut self) -> Uint128 { + let resp = self + .app + .wrap() + .query_wasm_raw(self.ibc_forwarder.clone(), "transfer_amount".as_bytes()) + .unwrap(); + + let mut val = resp.unwrap().split_off(1); + val.truncate(val.len() - 1); + + let transfer_amount = String::from_vec(val).unwrap(); + + Uint128::from_str(&transfer_amount).unwrap() + } + + pub fn query_admin_address(&mut self) -> String { + self.app + .wrap() + .query_wasm_contract_info(self.ibc_forwarder.to_string()) + .unwrap() + .admin + .unwrap() + } + + pub fn distribute_fallback(&mut self, coins: Vec, funds: Vec) -> AppResponse { + self.app + .execute_contract( + self.faucet.clone(), + self.ibc_forwarder.clone(), + &valence_ibc_forwarder::msg::ExecuteMsg::DistributeFallback { coins }, + &funds, + ) + .unwrap() + } +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} diff --git a/unit-tests/src/test_ibc_forwarder/tests.rs b/unit-tests/src/test_ibc_forwarder/tests.rs new file mode 100644 index 00000000..048ebf13 --- /dev/null +++ b/unit-tests/src/test_ibc_forwarder/tests.rs @@ -0,0 +1,430 @@ +use cosmwasm_std::{coin, coins, Addr, Uint128}; +use cw_multi_test::Executor; +use valence_ibc_forwarder::msg::{ContractState, FallbackAddressUpdateConfig}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + ADMIN, DENOM_ATOM_ON_NTRN, DENOM_FALLBACK_ON_HUB, DENOM_NTRN, DENOM_OSMO_ON_HUB_FROM_NTRN, +}; + +use super::suite::IbcForwarderBuilder; + +#[test] +#[should_panic] +fn test_instantiate_validates_next_contract_addr() { + IbcForwarderBuilder::default() + .with_next_contract("some contract".to_string()) + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_clock_addr() { + IbcForwarderBuilder::default() + .with_clock_address("some contract".to_string()) + .build(); +} + +#[test] +#[should_panic(expected = "not the clock")] +fn test_tick_validates_clock() { + let mut suite = IbcForwarderBuilder::default().build(); + let forwarder_addr = suite.ibc_forwarder.clone(); + suite + .app + .execute_contract( + forwarder_addr.clone(), + forwarder_addr.clone(), + &valence_ibc_forwarder::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "Cannot Sub with 0 and 1000000")] +fn test_ica_registration_takes_fee() { + let mut suite = IbcForwarderBuilder::default().build(); + let forwarder_addr = suite.ibc_forwarder.clone(); + suite.tick_contract(forwarder_addr); +} + +#[test] +fn test_ica_registration() { + let mut suite = IbcForwarderBuilder::default().build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + suite.fund_contract(&coins(1_000_000, DENOM_NTRN), forwarder_addr.clone()); + + assert_eq!(suite.query_contract_state(), ContractState::Instantiated {}); + + suite.tick_contract(forwarder_addr.clone()); + + let ica_addr = suite.query_ica_address(forwarder_addr); + assert!(!ica_addr.to_string().is_empty()); + assert_eq!(suite.query_contract_state(), ContractState::IcaCreated {}); +} + +#[test] +#[should_panic] +fn test_forward_funds_next_contract_does_not_implement_deposit_address_query() { + let mut suite = IbcForwarderBuilder::default() + .with_next_contract( + "cosmos10a6yf8khw53pvmafngsq2vjgqgu3p9kjsgpzpa2vm9ceg0c70eysqg42pu".to_string(), + ) + .build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + suite.fund_contract(&coins(1_000_000, DENOM_NTRN), forwarder_addr.clone()); + + // register ica + suite.tick_contract(forwarder_addr.clone()); + + // try to forward + suite.tick_contract(forwarder_addr); +} + +#[test] +#[should_panic(expected = "Next contract is not ready for receiving the funds yet")] +fn test_forward_funds_next_contract_not_ready() { + let mut suite = IbcForwarderBuilder::default().build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + suite.fund_contract(&coins(1_000_000, DENOM_NTRN), forwarder_addr.clone()); + + // register ica + suite.tick_contract(forwarder_addr.clone()); + + // try to forward + suite.tick_contract(forwarder_addr); +} + +#[test] +fn test_forward_funds_insufficient() { + let mut suite = IbcForwarderBuilder::default().build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + let next_contract = suite.query_next_contract(); + + // fund both contracts to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), forwarder_addr.clone()); + suite.fund_contract(&coins(1_000_000, DENOM_NTRN), next_contract.clone()); + + // register ica + suite.tick_contract(forwarder_addr.clone()); + suite.tick_contract(next_contract.clone()); + + let forwarder_ica = suite.query_ica_address(forwarder_addr.clone()); + + // fund the ica with insufficient amount of DENOM_ATOM_ON_NTRN + suite.fund_contract(&coins(99_000, DENOM_ATOM_ON_NTRN), forwarder_ica.clone()); + + // try to forward + suite.tick_contract(forwarder_addr); + + // assert that the funds were not forwarded + suite.assert_balance(&forwarder_ica, coin(99_000, DENOM_ATOM_ON_NTRN)); +} + +#[test] +fn test_forward_funds_happy() { + let mut suite = IbcForwarderBuilder::default().build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + let next_contract = suite.query_next_contract(); + + // fund both contracts to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), forwarder_addr.clone()); + suite.fund_contract(&coins(1_000_000, DENOM_NTRN), next_contract.clone()); + + // register ica + suite.tick_contract(forwarder_addr.clone()); + suite.tick_contract(next_contract.clone()); + + let forwarder_ica = suite.query_ica_address(forwarder_addr.clone()); + let next_contract_deposit_addr = suite.query_ica_address(next_contract.clone()); + + // fund the ica with sufficient amount of DENOM_ATOM_ON_NTRN + suite.fund_contract(&coins(100_000, DENOM_ATOM_ON_NTRN), forwarder_ica.clone()); + + // try to forward + suite.tick_contract(forwarder_addr); + + // assert that the funds were in fact forwarded + suite.assert_balance(&forwarder_ica, coin(0, DENOM_ATOM_ON_NTRN)); + // hacky ibc denom assertion + suite.assert_balance( + next_contract_deposit_addr, + coin(100_000, "channel-1/channel-1/uatom"), + ); +} + +#[test] +#[should_panic(expected = "Missing fallback address")] +fn test_distribute_fallback_errors_without_fallback_address() { + let mut suite = IbcForwarderBuilder::default().build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), forwarder_addr.clone()); + + // register ica + suite.tick_contract(forwarder_addr.clone()); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![coin(100_000, DENOM_ATOM_ON_NTRN.to_string())], + coins(1_000_000, DENOM_NTRN), + ); +} + +#[test] +#[should_panic(expected = "Cannot distribute target denom via fallback distribution")] +fn test_distribute_fallback_validates_denom() { + let mut builder = IbcForwarderBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), forwarder_addr.clone()); + + // register ica + suite.tick_contract(forwarder_addr.clone()); + + let forwarder_ica = suite.query_ica_address(forwarder_addr.clone()); + + // fund the ica with sufficient amount of DENOM_ATOM_ON_NTRN + suite.fund_contract(&coins(100_000, DENOM_ATOM_ON_NTRN), forwarder_ica.clone()); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_ATOM_ON_NTRN)); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![coin(100_000, DENOM_ATOM_ON_NTRN.to_string())], + coins(1_000_000, DENOM_NTRN), + ); +} + +#[test] +#[should_panic(expected = "must cover ibc fees to distribute fallback denoms")] +fn test_distribute_fallback_validates_ibc_fee_coverage() { + let mut builder = IbcForwarderBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), forwarder_addr.clone()); + + // register ica + suite.tick_contract(forwarder_addr.clone()); + + let forwarder_ica = suite.query_ica_address(forwarder_addr.clone()); + + // fund the ica with sufficient amount of DENOM_ATOM_ON_NTRN + suite.fund_contract(&coins(100_000, DENOM_ATOM_ON_NTRN), forwarder_ica.clone()); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_ATOM_ON_NTRN)); + + // try to distribute fallback denom + suite.distribute_fallback(vec![coin(100_000, DENOM_ATOM_ON_NTRN.to_string())], vec![]); +} + +#[test] +#[should_panic(expected = "no ica found")] +fn test_distribute_fallback_validates_ica_exists() { + let mut builder = IbcForwarderBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![coin(100_000, DENOM_FALLBACK_ON_HUB.to_string())], + coins(1_000_000, DENOM_NTRN), + ); +} + +#[test] +#[should_panic(expected = "insufficient fees")] +fn test_distribute_fallback_validates_insufficient_ibc_fee_coverage() { + let mut builder = IbcForwarderBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), forwarder_addr.clone()); + + // register ica + suite.tick_contract(forwarder_addr.clone()); + + let forwarder_ica = suite.query_ica_address(forwarder_addr.clone()); + + // fund the ica with sufficient amount of DENOM_ATOM_ON_NTRN + suite.fund_contract(&coins(100_000, DENOM_ATOM_ON_NTRN), forwarder_ica.clone()); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_ATOM_ON_NTRN)); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![coin(100_000, DENOM_ATOM_ON_NTRN.to_string())], + coins(5_000, DENOM_NTRN), + ); +} + +#[test] +#[should_panic(expected = "Attempt to distribute duplicate denoms via fallback distribution")] +fn test_distribute_fallback_validates_duplicate_input_denoms() { + let mut builder = IbcForwarderBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), forwarder_addr.clone()); + + // register ica + suite.tick_contract(forwarder_addr.clone()); + + let forwarder_ica = suite.query_ica_address(forwarder_addr.clone()); + + // fund the ica with sufficient amount of DENOM_ATOM_ON_NTRN + suite.fund_contract( + &coins(100_000, DENOM_FALLBACK_ON_HUB), + forwarder_ica.clone(), + ); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_FALLBACK_ON_HUB)); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![ + coin(100_000, DENOM_FALLBACK_ON_HUB.to_string()), + coin(100_000, DENOM_FALLBACK_ON_HUB.to_string()), + ], + coins(1_000_000, DENOM_NTRN), + ); + + // assert that the funds were in fact forwarded + suite.assert_balance(&forwarder_ica, coin(0, DENOM_FALLBACK_ON_HUB)); +} + +#[test] +fn test_distribute_fallback_happy() { + let mut builder = IbcForwarderBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(3_000_000, DENOM_NTRN), forwarder_addr.clone()); + + // register ica + suite.tick_contract(forwarder_addr.clone()); + + let forwarder_ica = suite.query_ica_address(forwarder_addr.clone()); + + // fund the ica with sufficient amount of DENOM_FALLBACK_ON_HUB and + suite.fund_contract( + &coins(100_000, DENOM_FALLBACK_ON_HUB), + forwarder_ica.clone(), + ); + suite.fund_contract( + &coins(100_000, DENOM_OSMO_ON_HUB_FROM_NTRN), + forwarder_ica.clone(), + ); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_FALLBACK_ON_HUB)); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_OSMO_ON_HUB_FROM_NTRN)); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![ + coin(100_000, DENOM_FALLBACK_ON_HUB.to_string()), + coin(100_000, DENOM_OSMO_ON_HUB_FROM_NTRN), + ], + vec![coin(2_000_000, DENOM_NTRN)], + ); + + // assert that the funds were in fact forwarded + suite.assert_balance(&forwarder_ica, coin(0, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&forwarder_ica, coin(0, DENOM_OSMO_ON_HUB_FROM_NTRN)); + suite.assert_balance( + suite.clock_addr.to_string(), + coin(100_000, DENOM_FALLBACK_ON_HUB), + ); + suite.assert_balance( + suite.clock_addr.to_string(), + coin(100_000, DENOM_OSMO_ON_HUB_FROM_NTRN), + ); +} + +#[test] +fn test_migrate_update_config() { + let mut suite = IbcForwarderBuilder::default().build(); + + let forwarder_addr = suite.ibc_forwarder.clone(); + let next_contract = suite.query_next_contract(); + let mut remote_chain_info = suite.query_remote_chain_info(); + remote_chain_info.denom = "some new denom".to_string(); + let clock_addr = suite.query_clock_address(); + + // migrate + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + forwarder_addr.clone(), + &valence_ibc_forwarder::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(next_contract.to_string()), + next_contract: Some(clock_addr.to_string()), + remote_chain_info: Box::new(Some(remote_chain_info)), + transfer_amount: Some(Uint128::new(69)), + fallback_address: Some(FallbackAddressUpdateConfig::ExplicitAddress( + clock_addr.to_string(), + )), + }, + 10, + ) + .unwrap(); + + assert_eq!(suite.query_clock_address(), next_contract); + assert_eq!(suite.query_remote_chain_info().denom, "some new denom"); + assert_eq!(suite.query_transfer_amount(), Uint128::new(69)); + assert_eq!(suite.query_next_contract(), clock_addr); + assert_eq!(suite.query_fallback_address().unwrap(), clock_addr); +} + +#[test] +fn test_migrate_update_config_remove_fallback() { + let mut builder = IbcForwarderBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.ibc_forwarder.clone(), + &valence_ibc_forwarder::msg::MigrateMsg::UpdateConfig { + clock_addr: None, + next_contract: None, + remote_chain_info: Box::new(None), + transfer_amount: None, + fallback_address: Some(FallbackAddressUpdateConfig::Disable {}), + }, + 10, + ) + .unwrap(); + + assert!(suite.query_fallback_address().is_none()); +} diff --git a/unit-tests/src/test_interchain_router/mod.rs b/unit-tests/src/test_interchain_router/mod.rs new file mode 100644 index 00000000..7b881830 --- /dev/null +++ b/unit-tests/src/test_interchain_router/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod tests; diff --git a/unit-tests/src/test_interchain_router/suite.rs b/unit-tests/src/test_interchain_router/suite.rs new file mode 100644 index 00000000..d8be180c --- /dev/null +++ b/unit-tests/src/test_interchain_router/suite.rs @@ -0,0 +1,138 @@ +use std::collections::BTreeSet; + +use cosmwasm_std::Addr; +use covenant_utils::DestinationConfig; + +use crate::setup::{ + base_suite::BaseSuiteMut, instantiates::interchain_router::InterchainRouterInstantiate, + suite_builder::SuiteBuilder, CustomApp, CLOCK_SALT, INTERCHAIN_ROUTER_SALT, +}; + +pub struct InterchainRouterBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: InterchainRouterInstantiate, +} + +impl Default for InterchainRouterBuilder { + fn default() -> Self { + let mut builder = SuiteBuilder::new(); + + let clock_addr = builder.get_contract_addr(builder.clock_code_id, CLOCK_SALT); + let interchain_router_addr = + builder.get_contract_addr(builder.interchain_router_code_id, INTERCHAIN_ROUTER_SALT); + + let clock_instantiate_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: None, + whitelist: vec![interchain_router_addr.to_string()], + }; + builder.contract_init2( + builder.clock_code_id, + CLOCK_SALT, + &clock_instantiate_msg, + &[], + ); + + let party_receiver = builder.get_random_addr(); + + let interchain_router_instantiate = + InterchainRouterInstantiate::default(clock_addr, party_receiver.to_string()); + + Self { + builder, + instantiate_msg: interchain_router_instantiate, + } + } +} + +#[allow(dead_code)] +impl InterchainRouterBuilder { + pub fn with_clock_address(mut self, clock_address: String) -> Self { + self.instantiate_msg.with_clock_address(clock_address); + self + } + + pub fn with_destination_config(mut self, destination_config: DestinationConfig) -> Self { + self.instantiate_msg + .with_destination_config(destination_config); + self + } + + pub fn with_denoms(mut self, denoms: BTreeSet) -> Self { + self.instantiate_msg.with_denoms(denoms); + self + } + + pub fn build(mut self) -> Suite { + let interchain_router_address = self.builder.contract_init2( + self.builder.interchain_router_code_id, + INTERCHAIN_ROUTER_SALT, + &self.instantiate_msg.msg, + &[], + ); + + let clock_addr = self + .builder + .app + .wrap() + .query_wasm_smart( + interchain_router_address.clone(), + &valence_interchain_router::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let receiver_config = self + .builder + .app + .wrap() + .query_wasm_smart( + interchain_router_address.clone(), + &valence_interchain_router::msg::QueryMsg::ReceiverConfig {}, + ) + .unwrap(); + + let denoms = self + .builder + .app + .wrap() + .query_wasm_smart( + interchain_router_address.clone(), + &valence_interchain_router::msg::QueryMsg::TargetDenoms {}, + ) + .unwrap(); + + Suite { + faucet: self.builder.faucet.clone(), + admin: self.builder.admin.clone(), + clock_addr, + denoms, + receiver_config, + app: self.builder.build(), + } + } +} + +#[allow(dead_code)] +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + + pub clock_addr: Addr, + pub receiver_config: covenant_utils::DestinationConfig, + pub denoms: BTreeSet, +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} diff --git a/unit-tests/src/test_interchain_router/tests.rs b/unit-tests/src/test_interchain_router/tests.rs new file mode 100644 index 00000000..cf13a490 --- /dev/null +++ b/unit-tests/src/test_interchain_router/tests.rs @@ -0,0 +1,21 @@ +use super::suite::InterchainRouterBuilder; + +#[test] +#[should_panic] +fn test_instantiate_validates_clock_address() { + InterchainRouterBuilder::default() + .with_clock_address("invalid_clock".to_string()) + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_destination_receiver_addr() { + let mut builder = InterchainRouterBuilder::default(); + builder + .instantiate_msg + .msg + .destination_config + .destination_receiver_addr = "invalid_receiver".to_string(); + builder.build(); +} diff --git a/unit-tests/src/test_native_router/mod.rs b/unit-tests/src/test_native_router/mod.rs new file mode 100644 index 00000000..7b881830 --- /dev/null +++ b/unit-tests/src/test_native_router/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod tests; diff --git a/unit-tests/src/test_native_router/suite.rs b/unit-tests/src/test_native_router/suite.rs new file mode 100644 index 00000000..663350af --- /dev/null +++ b/unit-tests/src/test_native_router/suite.rs @@ -0,0 +1,192 @@ +use std::collections::BTreeSet; + +use cosmwasm_std::Addr; +use cw_multi_test::{AppResponse, Executor}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::native_router::NativeRouterInstantiate, + suite_builder::SuiteBuilder, + CustomApp, CLOCK_SALT, NATIVE_ROUTER_SALT, +}; + +pub struct NativeRouterBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: NativeRouterInstantiate, +} + +impl Default for NativeRouterBuilder { + fn default() -> Self { + let mut builder = SuiteBuilder::new(); + + let clock_addr = builder.get_contract_addr(builder.clock_code_id, CLOCK_SALT); + + let native_router_addr = + builder.get_contract_addr(builder.native_router_code_id, NATIVE_ROUTER_SALT); + + let clock_instantiate_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: None, + whitelist: vec![native_router_addr.to_string()], + }; + builder.contract_init2( + builder.clock_code_id, + CLOCK_SALT, + &clock_instantiate_msg, + &[], + ); + + let party_receiver = builder.get_random_addr(); + + let native_router_instantiate = + NativeRouterInstantiate::default(clock_addr, party_receiver); + + Self { + builder, + instantiate_msg: native_router_instantiate, + } + } +} + +#[allow(dead_code)] +impl NativeRouterBuilder { + pub fn with_clock_address(mut self, addr: &str) -> Self { + self.instantiate_msg.with_clock_address(addr.to_string()); + self + } + + pub fn with_receiver_address(mut self, addr: &str) -> Self { + self.instantiate_msg.with_receiver_address(addr.to_string()); + self + } + + pub fn with_denoms(mut self, denoms: Vec) -> Self { + let denom_set = BTreeSet::from_iter(denoms); + self.instantiate_msg.with_denoms(denom_set); + self + } + + pub fn build(mut self) -> Suite { + let native_router_address = self.builder.contract_init2( + self.builder.native_router_code_id, + NATIVE_ROUTER_SALT, + &self.instantiate_msg.msg, + &[], + ); + + let clock_addr = self + .builder + .app + .wrap() + .query_wasm_smart( + native_router_address.clone(), + &valence_native_router::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let receiver_addr = self + .builder + .app + .wrap() + .query_wasm_smart( + native_router_address.clone(), + &valence_native_router::msg::QueryMsg::ReceiverConfig {}, + ) + .unwrap(); + + let denoms = self + .builder + .app + .wrap() + .query_wasm_smart( + native_router_address.clone(), + &valence_native_router::msg::QueryMsg::TargetDenoms {}, + ) + .unwrap(); + + Suite { + router_addr: native_router_address, + faucet: self.builder.faucet.clone(), + admin: self.builder.admin.clone(), + clock_addr, + receiver_addr, + denoms, + app: self.builder.build(), + } + } +} + +#[allow(dead_code)] +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + + pub router_addr: Addr, + pub clock_addr: Addr, + pub receiver_addr: Addr, + pub denoms: BTreeSet, +} + +impl Suite { + pub fn query_receiver_config(&mut self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.router_addr.clone(), + &valence_native_router::msg::QueryMsg::ReceiverConfig {}, + ) + .unwrap() + } + + pub fn query_clock_address(&mut self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.router_addr.clone(), + &valence_native_router::msg::QueryMsg::ClockAddress {}, + ) + .unwrap() + } + + pub fn query_target_denoms(&mut self) -> BTreeSet { + self.app + .wrap() + .query_wasm_smart( + self.router_addr.clone(), + &valence_native_router::msg::QueryMsg::TargetDenoms {}, + ) + .unwrap() + } + + pub fn distribute_fallback(&mut self, denoms: Vec) -> AppResponse { + self.app + .execute_contract( + self.receiver_addr.clone(), + self.router_addr.clone(), + &valence_native_router::msg::ExecuteMsg::DistributeFallback { denoms }, + &[], + ) + .unwrap() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} diff --git a/unit-tests/src/test_native_router/tests.rs b/unit-tests/src/test_native_router/tests.rs new file mode 100644 index 00000000..2a72746e --- /dev/null +++ b/unit-tests/src/test_native_router/tests.rs @@ -0,0 +1,166 @@ +use cosmwasm_std::{coin, coins, Addr, Event}; +use cw_multi_test::Executor; + +use crate::{ + setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + ADMIN, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, + }, + test_native_router::suite::NativeRouterBuilder, +}; + +#[test] +#[should_panic] +fn test_instantiate_validates_clock_addr() { + NativeRouterBuilder::default() + .with_clock_address("not a clock") + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_receiver_addr() { + NativeRouterBuilder::default() + .with_receiver_address("not a receiver") + .build(); +} + +#[test] +#[should_panic(expected = "Caller is not the clock, only clock can tick contracts")] +fn test_execute_tick_validates_clock_addr() { + let mut suite = NativeRouterBuilder::default().build(); + + let router = suite.router_addr; + let not_the_clock = suite.faucet; + + suite + .app + .execute_contract( + not_the_clock, + router, + &valence_native_router::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap(); +} + +#[test] +fn test_execute_route_balances_with_no_balances() { + let mut suite = NativeRouterBuilder::default().build(); + let router = suite.router_addr.clone(); + suite.tick_contract(router).assert_event( + &Event::new("wasm") + .add_attribute("method", "try_route_balances") + .add_attribute("balances", "[]"), + ); +} + +#[test] +fn test_execute_route_balances_with_one_balance() { + let mut suite = NativeRouterBuilder::default().build(); + let router = suite.router_addr.clone(); + + suite.fund_contract(&coins(5000, DENOM_ATOM_ON_NTRN), router.clone()); + + suite.tick_contract(router.clone()).assert_event( + &Event::new("wasm") + .add_attribute("method", "try_route_balances") + .add_attribute(DENOM_ATOM_ON_NTRN.to_string(), "5000"), + ); + + suite.assert_balance(&router, coin(0, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&suite.receiver_addr, coin(5000, DENOM_ATOM_ON_NTRN)); +} + +#[test] +fn test_execute_route_balances_with_multiple_balances() { + let mut suite = NativeRouterBuilder::default() + .with_denoms(vec![ + DENOM_ATOM_ON_NTRN.to_string(), + DENOM_LS_ATOM_ON_NTRN.to_string(), + ]) + .build(); + + let router = suite.router_addr.clone(); + + suite.fund_contract(&coins(5000, DENOM_ATOM_ON_NTRN), router.clone()); + suite.fund_contract(&coins(1000, DENOM_LS_ATOM_ON_NTRN), router.clone()); + + suite.tick_contract(router.clone()).assert_event( + &Event::new("wasm") + .add_attribute("method", "try_route_balances") + .add_attribute(DENOM_ATOM_ON_NTRN.to_string(), "5000") + .add_attribute(DENOM_LS_ATOM_ON_NTRN.to_string(), "1000"), + ); + + suite.assert_balance(&router, coin(0, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&router, coin(0, DENOM_LS_ATOM_ON_NTRN)); + + suite.assert_balance(&suite.receiver_addr, coin(5000, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&suite.receiver_addr, coin(1000, DENOM_LS_ATOM_ON_NTRN)); +} + +#[test] +#[should_panic(expected = "unauthorized denom distribution")] +fn test_execute_distribute_fallback_validates_explicit_denoms() { + let mut suite = NativeRouterBuilder::default().build(); + + let router = suite.router_addr.clone(); + + suite.fund_contract(&coins(5000, DENOM_ATOM_ON_NTRN), router.clone()); + suite.fund_contract(&coins(1000, DENOM_LS_ATOM_ON_NTRN), router.clone()); + + suite.distribute_fallback(vec![ + DENOM_ATOM_ON_NTRN.to_string(), + DENOM_LS_ATOM_ON_NTRN.to_string(), + ]); +} + +#[test] +fn test_execute_distribute_fallback_happy() { + let mut suite = NativeRouterBuilder::default().build(); + + let router = suite.router_addr.clone(); + + suite.fund_contract(&coins(5000, DENOM_ATOM_ON_NTRN), router.clone()); + suite.fund_contract(&coins(1000, DENOM_LS_ATOM_ON_NTRN), router.clone()); + + suite + .distribute_fallback(vec![DENOM_LS_ATOM_ON_NTRN.to_string()]) + .assert_event(&Event::new("wasm").add_attribute("method", "try_distribute_fallback")); + + suite.assert_balance(&router, coin(5000, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&router, coin(0, DENOM_LS_ATOM_ON_NTRN)); + + suite.assert_balance(&suite.receiver_addr, coin(0, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&suite.receiver_addr, coin(1000, DENOM_LS_ATOM_ON_NTRN)); +} + +#[test] +fn test_migrate_update_config() { + let mut suite = NativeRouterBuilder::default().build(); + + let router_addr = suite.router_addr.clone(); + let clock_addr = suite.query_clock_address(); + let mut target_denoms = suite.query_target_denoms(); + let receiver_addr = suite.receiver_addr.clone(); + target_denoms.insert("new_denom".to_string()); + + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + router_addr, + &valence_native_router::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(receiver_addr.to_string()), + receiver_address: Some(clock_addr.to_string()), + target_denoms: Some(target_denoms.clone().into_iter().collect()), + }, + 9, + ) + .unwrap(); + + assert_eq!(suite.query_clock_address(), receiver_addr); + assert_eq!(suite.query_target_denoms(), target_denoms); + assert_eq!(suite.query_receiver_config(), clock_addr); +} diff --git a/unit-tests/src/test_native_splitter/mod.rs b/unit-tests/src/test_native_splitter/mod.rs new file mode 100644 index 00000000..7b881830 --- /dev/null +++ b/unit-tests/src/test_native_splitter/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod tests; diff --git a/unit-tests/src/test_native_splitter/suite.rs b/unit-tests/src/test_native_splitter/suite.rs new file mode 100644 index 00000000..65166192 --- /dev/null +++ b/unit-tests/src/test_native_splitter/suite.rs @@ -0,0 +1,225 @@ +use std::collections::BTreeMap; + +use cosmwasm_std::Addr; +use covenant_utils::split::SplitConfig; +use cw_multi_test::{AppResponse, Executor}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::native_splitter::NativeSplitterInstantiate, + suite_builder::SuiteBuilder, + CustomApp, CLOCK_SALT, NATIVE_SPLITTER_SALT, +}; + +pub struct NativeSplitterBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: NativeSplitterInstantiate, +} + +impl Default for NativeSplitterBuilder { + fn default() -> Self { + let mut builder = SuiteBuilder::new(); + + let clock_addr = builder.get_contract_addr(builder.clock_code_id, CLOCK_SALT); + let native_splitter_addr = + builder.get_contract_addr(builder.native_splitter_code_id, NATIVE_SPLITTER_SALT); + + let clock_instantiate_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: None, + whitelist: vec![native_splitter_addr.to_string()], + }; + builder.contract_init2( + builder.clock_code_id, + CLOCK_SALT, + &clock_instantiate_msg, + &[], + ); + + let party_a_controller_addr = builder.get_random_addr(); + let party_b_controller_addr = builder.get_random_addr(); + + let native_splitter_instantiate = NativeSplitterInstantiate::default( + clock_addr.to_string(), + party_a_controller_addr.to_string(), + party_b_controller_addr.to_string(), + ); + + Self { + builder, + instantiate_msg: native_splitter_instantiate, + } + } +} + +#[allow(dead_code)] +impl NativeSplitterBuilder { + pub fn with_clock_address(mut self, addr: String) -> Self { + self.instantiate_msg.with_clock_address(addr); + self + } + + pub fn with_splits(mut self, splits: BTreeMap) -> Self { + self.instantiate_msg.with_splits(splits); + self + } + + pub fn with_fallback_split(mut self, fallback_split: Option) -> Self { + self.instantiate_msg.with_fallback_split(fallback_split); + self + } + + pub fn build(mut self) -> Suite { + let native_splitter_address = self.builder.contract_init2( + self.builder.native_splitter_code_id, + NATIVE_SPLITTER_SALT, + &self.instantiate_msg.msg, + &[], + ); + + let clock_addr = self + .builder + .app + .wrap() + .query_wasm_smart( + native_splitter_address.clone(), + &valence_native_splitter::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let splits: Vec<(String, SplitConfig)> = self + .builder + .app + .wrap() + .query_wasm_smart( + native_splitter_address.clone(), + &valence_native_splitter::msg::QueryMsg::Splits {}, + ) + .unwrap(); + let config_1 = splits[0].clone().1.receivers; + let receivers: Vec = config_1.keys().cloned().collect(); + + let split_map = BTreeMap::from_iter(splits); + + let fallback_split = self + .builder + .app + .wrap() + .query_wasm_smart( + native_splitter_address.clone(), + &valence_native_splitter::msg::QueryMsg::FallbackSplit {}, + ) + .unwrap(); + + Suite { + faucet: self.builder.faucet.clone(), + admin: self.builder.admin.clone(), + clock_addr, + splitter: native_splitter_address, + splits: split_map, + fallback_split, + app: self.builder.build(), + receiver_1: Addr::unchecked(receivers[0].to_string()), + receiver_2: Addr::unchecked(receivers[1].to_string()), + } + } +} + +#[allow(dead_code)] +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + + pub splitter: Addr, + pub clock_addr: Addr, + pub splits: BTreeMap, + pub fallback_split: Option, + pub receiver_1: Addr, + pub receiver_2: Addr, +} + +impl Suite { + pub fn query_clock_address(&mut self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_native_splitter::msg::QueryMsg::ClockAddress {}, + ) + .unwrap() + } + + pub fn query_denom_split(&mut self, denom: String) -> SplitConfig { + self.app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_native_splitter::msg::QueryMsg::DenomSplit { denom }, + ) + .unwrap() + } + + pub fn query_all_splits(&mut self) -> BTreeMap { + let splits: Vec<(String, SplitConfig)> = self + .app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_native_splitter::msg::QueryMsg::Splits {}, + ) + .unwrap(); + BTreeMap::from_iter(splits) + } + + pub fn query_fallback_split(&mut self) -> Option { + self.app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_native_splitter::msg::QueryMsg::FallbackSplit {}, + ) + .unwrap() + } + + pub fn query_deposit_address(&mut self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_native_splitter::msg::QueryMsg::DepositAddress {}, + ) + .unwrap() + } + + pub fn distribute_fallback(&mut self, denoms: Vec) -> AppResponse { + self.app + .execute_contract( + self.faucet.clone(), + self.splitter.clone(), + &valence_native_splitter::msg::ExecuteMsg::DistributeFallback { denoms }, + &[], + ) + .unwrap() + } +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} diff --git a/unit-tests/src/test_native_splitter/tests.rs b/unit-tests/src/test_native_splitter/tests.rs new file mode 100644 index 00000000..0a0e42c2 --- /dev/null +++ b/unit-tests/src/test_native_splitter/tests.rs @@ -0,0 +1,215 @@ +use std::collections::BTreeMap; + +use cosmwasm_std::{coin, coins, Addr, Decimal}; +use covenant_utils::split::SplitConfig; +use cw_multi_test::Executor; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + ADMIN, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, DENOM_NTRN, +}; + +use super::suite::NativeSplitterBuilder; + +#[test] +#[should_panic(expected = "shares must add up to 1.0")] +fn test_instantiate_validates_explicit_split_shares() { + let mut builder = NativeSplitterBuilder::default(); + let (denom, mut split_config) = builder.instantiate_msg.msg.splits.pop_first().unwrap(); + let invalid_split_config: BTreeMap = split_config + .receivers + .iter_mut() + .map(|(k, _)| (k.to_string(), Decimal::percent(49))) + .collect(); + builder.instantiate_msg.msg.splits.insert( + denom, + SplitConfig { + receivers: invalid_split_config, + }, + ); + builder.build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_explicit_split_receiver_addresses() { + let mut builder = NativeSplitterBuilder::default(); + let (denom, mut split_config) = builder.instantiate_msg.msg.splits.pop_first().unwrap(); + let invalid_split_config: BTreeMap = split_config + .receivers + .iter_mut() + .map(|(k, v)| (format!("invalid_{:?}", k), *v)) + .collect(); + builder.instantiate_msg.msg.splits.insert( + denom, + SplitConfig { + receivers: invalid_split_config, + }, + ); + builder.build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_clock_address() { + NativeSplitterBuilder::default() + .with_clock_address("invalid_clock".to_string()) + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_fallback_split_receiver_addresses() { + let mut invalid_split_config = BTreeMap::new(); + invalid_split_config.insert("invalid_address".to_string(), Decimal::one()); + NativeSplitterBuilder::default() + .with_fallback_split(Some(SplitConfig { + receivers: invalid_split_config, + })) + .build(); +} + +#[test] +#[should_panic(expected = "shares must add up to 1.0")] +fn test_instantiate_validates_fallback_split_shares() { + let builder = NativeSplitterBuilder::default(); + let mut invalid_split_config = BTreeMap::new(); + invalid_split_config.insert( + builder.instantiate_msg.msg.clock_address.to_string(), + Decimal::percent(50), + ); + builder + .with_fallback_split(Some(SplitConfig { + receivers: invalid_split_config, + })) + .build(); +} + +#[test] +#[should_panic(expected = "Caller is not the clock, only clock can tick contracts")] +fn test_execute_tick_validates_clock() { + let mut suite = NativeSplitterBuilder::default().build(); + + suite + .app + .execute_contract( + suite.faucet, + suite.splitter, + &valence_native_splitter::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap(); +} + +#[test] +fn test_execute_distribute_single_denom() { + let mut suite = NativeSplitterBuilder::default().build(); + + suite.fund_contract(&coins(100000, DENOM_ATOM_ON_NTRN), suite.splitter.clone()); + + suite.tick_contract(suite.splitter.clone()); + suite.assert_balance(&suite.splitter, coin(0, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&suite.receiver_1, coin(50000, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&suite.receiver_2, coin(50000, DENOM_ATOM_ON_NTRN)); +} + +#[test] +fn test_execute_distribute_multiple_denoms() { + let mut suite = NativeSplitterBuilder::default().build(); + + suite.fund_contract(&coins(100000, DENOM_ATOM_ON_NTRN), suite.splitter.clone()); + suite.fund_contract( + &coins(100000, DENOM_LS_ATOM_ON_NTRN), + suite.splitter.clone(), + ); + + suite.tick_contract(suite.splitter.clone()); + suite.assert_balance(&suite.splitter, coin(0, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&suite.splitter, coin(0, DENOM_LS_ATOM_ON_NTRN)); + + suite.assert_balance(&suite.receiver_1, coin(50000, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&suite.receiver_1, coin(50000, DENOM_LS_ATOM_ON_NTRN)); + + suite.assert_balance(&suite.receiver_2, coin(50000, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&suite.receiver_2, coin(50000, DENOM_LS_ATOM_ON_NTRN)); +} + +#[test] +#[should_panic(expected = "unauthorized denom distribution")] +fn test_execute_distribute_fallback_validates_explicit_denoms() { + let mut suite = NativeSplitterBuilder::default().build(); + + suite.fund_contract(&coins(100000, DENOM_ATOM_ON_NTRN), suite.splitter.clone()); + suite.fund_contract( + &coins(100000, DENOM_LS_ATOM_ON_NTRN), + suite.splitter.clone(), + ); + + suite.distribute_fallback(vec![DENOM_ATOM_ON_NTRN.to_string()]); +} + +#[test] +#[should_panic(expected = "no fallback split defined")] +fn test_execute_distribute_fallback_validates_fallback_split_presence() { + let mut suite = NativeSplitterBuilder::default() + .with_fallback_split(None) + .build(); + + suite.fund_contract(&coins(100000, DENOM_ATOM_ON_NTRN), suite.splitter.clone()); + suite.fund_contract( + &coins(100000, DENOM_LS_ATOM_ON_NTRN), + suite.splitter.clone(), + ); + + suite.distribute_fallback(vec![DENOM_ATOM_ON_NTRN.to_string()]); +} + +#[test] +fn test_execute_distribute_fallback_happy() { + let mut suite = NativeSplitterBuilder::default().build(); + + suite.fund_contract(&coins(100000, DENOM_NTRN), suite.splitter.clone()); + + suite.distribute_fallback(vec![DENOM_NTRN.to_string()]); + suite.assert_balance(&suite.splitter, coin(0, DENOM_NTRN)); + suite.assert_balance(&suite.receiver_1, coin(50000, DENOM_NTRN)); + suite.assert_balance(&suite.receiver_2, coin(50000, DENOM_NTRN)); +} + +#[test] +fn test_migrate_update_config() { + let mut suite = NativeSplitterBuilder::default() + .with_fallback_split(None) + .build(); + + let mut splits = suite.query_all_splits(); + let fallback_split = suite.query_fallback_split(); + assert!(fallback_split.is_none()); + + splits.remove(DENOM_ATOM_ON_NTRN); + assert_eq!(splits.len(), 1); + + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.splitter.clone(), + &valence_native_splitter::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(suite.faucet.to_string()), + fallback_split: Some(splits.get(DENOM_LS_ATOM_ON_NTRN).unwrap().clone()), + splits: Some(splits.clone()), + }, + 7, + ) + .unwrap(); + + let clock_address = suite.query_clock_address(); + let new_splits = suite.query_all_splits(); + let new_fallback_split = suite.query_fallback_split(); + let ls_atom_split = suite.query_denom_split(DENOM_LS_ATOM_ON_NTRN.to_string()); + + assert!(new_fallback_split.is_some()); + assert_eq!(splits, new_splits); + assert_eq!(clock_address, suite.faucet); + assert_eq!(splits.get(DENOM_LS_ATOM_ON_NTRN).unwrap(), &ls_atom_split); +} diff --git a/unit-tests/src/test_osmo_lp_outpost/mod.rs b/unit-tests/src/test_osmo_lp_outpost/mod.rs new file mode 100644 index 00000000..7b881830 --- /dev/null +++ b/unit-tests/src/test_osmo_lp_outpost/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod tests; diff --git a/unit-tests/src/test_osmo_lp_outpost/suite.rs b/unit-tests/src/test_osmo_lp_outpost/suite.rs new file mode 100644 index 00000000..caffac09 --- /dev/null +++ b/unit-tests/src/test_osmo_lp_outpost/suite.rs @@ -0,0 +1,97 @@ +use cosmwasm_std::{Addr, Coin}; +use cw_multi_test::{AppResponse, Executor}; + +use crate::setup::{ + base_suite::BaseSuiteMut, instantiates::osmo_lp_outpost::OsmoLpOutpostInstantiate, + suite_builder::SuiteBuilder, CustomApp, +}; + +pub struct OsmoLpOutpostBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: OsmoLpOutpostInstantiate, +} + +impl Default for OsmoLpOutpostBuilder { + fn default() -> Self { + Self { + builder: SuiteBuilder::new(), + instantiate_msg: OsmoLpOutpostInstantiate::default(), + } + } +} + +impl OsmoLpOutpostBuilder { + pub fn build(mut self) -> Suite { + let outpost_addr = self.builder.contract_init( + self.builder.osmo_lp_outpost_code_id, + "outpost".to_string(), + &self.instantiate_msg.msg, + &[], + ); + + Suite { + faucet: self.builder.faucet.clone(), + admin: self.builder.admin.clone(), + outpost: outpost_addr, + app: self.builder.build(), + } + } +} + +#[allow(dead_code)] +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + pub outpost: Addr, +} + +impl Suite { + pub fn provide_liquidity( + &mut self, + funds: Vec, + sender: Addr, + config: valence_outpost_osmo_liquid_pooler::msg::OutpostProvideLiquidityConfig, + ) -> AppResponse { + self.app + .execute_contract( + sender, + self.outpost.clone(), + &valence_outpost_osmo_liquid_pooler::msg::ExecuteMsg::ProvideLiquidity { config }, + &funds, + ) + .unwrap() + } + + pub fn withdraw_liquidity( + &mut self, + funds: Vec, + sender: Addr, + config: valence_outpost_osmo_liquid_pooler::msg::OutpostWithdrawLiquidityConfig, + ) -> AppResponse { + self.app + .execute_contract( + sender, + self.outpost.clone(), + &valence_outpost_osmo_liquid_pooler::msg::ExecuteMsg::WithdrawLiquidity { config }, + &funds, + ) + .unwrap() + } +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + // outpost is not clocked + Addr::unchecked("") + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} diff --git a/unit-tests/src/test_osmo_lp_outpost/tests.rs b/unit-tests/src/test_osmo_lp_outpost/tests.rs new file mode 100644 index 00000000..f04bbf21 --- /dev/null +++ b/unit-tests/src/test_osmo_lp_outpost/tests.rs @@ -0,0 +1,91 @@ +use std::str::FromStr; + +use cosmwasm_std::{coin, coins, Decimal, Uint128, Uint64}; +use valence_outpost_osmo_liquid_pooler::msg::{ + OutpostProvideLiquidityConfig, OutpostWithdrawLiquidityConfig, +}; + +use crate::{ + setup::{base_suite::BaseSuiteMut, DENOM_ATOM, DENOM_FALLBACK, DENOM_LS_ATOM_ON_NTRN}, + test_osmo_lp_outpost::suite::OsmoLpOutpostBuilder, +}; + +// TODO: these tests are incomplete and should be expanded +#[test] +fn test_withdraw_liquidity() { + let mut suite = OsmoLpOutpostBuilder::default().build(); + + suite.fund_contract(&coins(1, DENOM_ATOM), suite.outpost.clone()); + suite.fund_contract(&coins(1, DENOM_LS_ATOM_ON_NTRN), suite.outpost.clone()); + + suite.withdraw_liquidity( + coins(1, DENOM_FALLBACK), + suite.faucet.clone(), + OutpostWithdrawLiquidityConfig { + pool_id: Uint64::new(1), + }, + ); +} + +#[test] +fn test_provide_liquidity_double_sided() { + let mut suite = OsmoLpOutpostBuilder::default().build(); + + suite.fund_contract(&coins(1, DENOM_ATOM), suite.outpost.clone()); + suite.fund_contract(&coins(1, DENOM_LS_ATOM_ON_NTRN), suite.outpost.clone()); + + suite.provide_liquidity( + vec![coin(1, DENOM_ATOM), coin(1, DENOM_LS_ATOM_ON_NTRN)], + suite.faucet.clone(), + OutpostProvideLiquidityConfig { + pool_id: Uint64::new(1), + expected_spot_price: Decimal::from_str("1.0").unwrap(), + acceptable_price_spread: Decimal::from_str("0.01").unwrap(), + slippage_tolerance: Decimal::from_str("0.01").unwrap(), + asset_1_single_side_lp_limit: Uint128::new(100000), + asset_2_single_side_lp_limit: Uint128::new(100000), + }, + ); +} + +#[test] +fn test_provide_liquidity_single_sided_asset_a() { + let mut suite = OsmoLpOutpostBuilder::default().build(); + + suite.fund_contract(&coins(1, DENOM_ATOM), suite.outpost.clone()); + suite.fund_contract(&coins(1, DENOM_LS_ATOM_ON_NTRN), suite.outpost.clone()); + + suite.provide_liquidity( + coins(1, DENOM_ATOM), + suite.faucet.clone(), + OutpostProvideLiquidityConfig { + pool_id: Uint64::new(1), + expected_spot_price: Decimal::from_str("1.0").unwrap(), + acceptable_price_spread: Decimal::from_str("0.01").unwrap(), + slippage_tolerance: Decimal::from_str("0.01").unwrap(), + asset_1_single_side_lp_limit: Uint128::new(100000), + asset_2_single_side_lp_limit: Uint128::new(100000), + }, + ); +} + +#[test] +fn test_provide_liquidity_single_sided_asset_b() { + let mut suite = OsmoLpOutpostBuilder::default().build(); + + suite.fund_contract(&coins(1, DENOM_ATOM), suite.outpost.clone()); + suite.fund_contract(&coins(1, DENOM_LS_ATOM_ON_NTRN), suite.outpost.clone()); + + suite.provide_liquidity( + coins(1, DENOM_LS_ATOM_ON_NTRN), + suite.faucet.clone(), + OutpostProvideLiquidityConfig { + pool_id: Uint64::new(1), + expected_spot_price: Decimal::from_str("1.0").unwrap(), + acceptable_price_spread: Decimal::from_str("0.01").unwrap(), + slippage_tolerance: Decimal::from_str("0.01").unwrap(), + asset_1_single_side_lp_limit: Uint128::new(100000), + asset_2_single_side_lp_limit: Uint128::new(100000), + }, + ); +} diff --git a/unit-tests/src/test_remote_chain_splitter/mod.rs b/unit-tests/src/test_remote_chain_splitter/mod.rs new file mode 100644 index 00000000..7b881830 --- /dev/null +++ b/unit-tests/src/test_remote_chain_splitter/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod tests; diff --git a/unit-tests/src/test_remote_chain_splitter/suite.rs b/unit-tests/src/test_remote_chain_splitter/suite.rs new file mode 100644 index 00000000..b4d50266 --- /dev/null +++ b/unit-tests/src/test_remote_chain_splitter/suite.rs @@ -0,0 +1,324 @@ +use std::collections::BTreeMap; + +use cosmwasm_std::{Addr, Coin, Uint128, Uint64}; +use covenant_utils::{neutron::RemoteChainInfo, split::SplitConfig}; +use cw_multi_test::{AppResponse, Executor}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::remote_chain_splitter::RemoteChainSplitterInstantiate, + suite_builder::SuiteBuilder, + CustomApp, CLOCK_SALT, DENOM_ATOM_ON_NTRN, NTRN_HUB_CHANNEL, REMOTE_CHAIN_SPLITTER_SALT, +}; + +pub struct RemoteChainSplitterBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: RemoteChainSplitterInstantiate, +} + +impl Default for RemoteChainSplitterBuilder { + fn default() -> Self { + let mut builder = SuiteBuilder::new(); + + let clock_addr = builder.get_contract_addr(builder.clock_code_id, CLOCK_SALT); + let remote_chain_splitter_addr = + builder.get_contract_addr(builder.remote_splitter_code_id, REMOTE_CHAIN_SPLITTER_SALT); + + let forwarder_a_addr = + builder.get_contract_addr(builder.ibc_forwarder_code_id, "forwarder_a"); + let forwarder_b_addr = + builder.get_contract_addr(builder.ibc_forwarder_code_id, "forwarder_b"); + let clock_instantiate_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: None, + whitelist: vec![ + remote_chain_splitter_addr.to_string(), + forwarder_a_addr.to_string(), + forwarder_b_addr.to_string(), + ], + }; + builder.contract_init2( + builder.clock_code_id, + CLOCK_SALT, + &clock_instantiate_msg, + &[], + ); + + let default_forwarder_instantiate_msg = valence_ibc_forwarder::msg::InstantiateMsg { + clock_address: clock_addr.to_string(), + next_contract: clock_addr.to_string(), + remote_chain_connection_id: "connection-0".to_string(), + remote_chain_channel_id: NTRN_HUB_CHANNEL.0.to_string(), + denom: DENOM_ATOM_ON_NTRN.to_string(), + amount: Uint128::new(100), + ibc_transfer_timeout: Uint64::new(100), + ica_timeout: Uint64::new(100), + fallback_address: None, + }; + + builder.contract_init2( + builder.ibc_forwarder_code_id, + "forwarder_a", + &default_forwarder_instantiate_msg, + &[], + ); + builder.contract_init2( + builder.ibc_forwarder_code_id, + "forwarder_b", + &default_forwarder_instantiate_msg, + &[], + ); + + let remote_chain_splitter_instantiate = RemoteChainSplitterInstantiate::default( + clock_addr.to_string(), + forwarder_a_addr.to_string(), + forwarder_b_addr.to_string(), + ); + + Self { + builder, + instantiate_msg: remote_chain_splitter_instantiate, + } + } +} + +#[allow(dead_code)] +impl RemoteChainSplitterBuilder { + pub fn with_clock_address(mut self, addr: String) -> Self { + self.instantiate_msg.with_clock_address(addr); + self + } + + pub fn with_remote_chain_connection_id(mut self, id: String) -> Self { + self.instantiate_msg.with_remote_chain_connection_id(id); + self + } + + pub fn with_remote_chain_channel_id(mut self, id: String) -> Self { + self.instantiate_msg.with_remote_chain_channel_id(id); + self + } + + pub fn with_denom(mut self, denom: String) -> Self { + self.instantiate_msg.with_denom(denom); + self + } + + pub fn with_amount(mut self, amount: Uint128) -> Self { + self.instantiate_msg.with_amount(amount); + self + } + + pub fn with_splits(mut self, splits: BTreeMap) -> Self { + self.instantiate_msg.with_splits(splits); + self + } + + pub fn with_ica_timeout(mut self, ica_timeout: Uint64) -> Self { + self.instantiate_msg.with_ica_timeout(ica_timeout); + self + } + + pub fn with_ibc_transfer_timeout(mut self, ibc_transfer_timeout: Uint64) -> Self { + self.instantiate_msg + .with_ibc_transfer_timeout(ibc_transfer_timeout); + self + } + + pub fn build(mut self) -> Suite { + let remote_chain_splitter_address = self.builder.contract_init2( + self.builder.remote_splitter_code_id, + REMOTE_CHAIN_SPLITTER_SALT, + &self.instantiate_msg.msg, + &[], + ); + + let clock_addr = self + .builder + .app + .wrap() + .query_wasm_smart( + remote_chain_splitter_address.clone(), + &valence_remote_chain_splitter::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let split_config: Vec<(String, SplitConfig)> = self + .builder + .app + .wrap() + .query_wasm_smart( + remote_chain_splitter_address.clone(), + &valence_remote_chain_splitter::msg::QueryMsg::SplitConfig {}, + ) + .unwrap(); + let config_1 = split_config[0].clone().1.receivers; + let receivers: Vec = config_1.keys().cloned().collect(); + + let splits = BTreeMap::from_iter(split_config); + + let transfer_amount = self + .builder + .app + .wrap() + .query_wasm_smart( + remote_chain_splitter_address.clone(), + &valence_remote_chain_splitter::msg::QueryMsg::TransferAmount {}, + ) + .unwrap(); + + let remote_chain_info = self + .builder + .app + .wrap() + .query_wasm_smart( + remote_chain_splitter_address.clone(), + &valence_remote_chain_splitter::msg::QueryMsg::RemoteChainInfo {}, + ) + .unwrap(); + + Suite { + splitter: remote_chain_splitter_address, + faucet: self.builder.faucet.clone(), + admin: self.builder.admin.clone(), + clock_addr, + splits, + transfer_amount, + remote_chain_info, + app: self.builder.build(), + receiver_1: Addr::unchecked(receivers[0].to_string()), + receiver_2: Addr::unchecked(receivers[1].to_string()), + } + } +} + +#[allow(dead_code)] +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + pub clock_addr: Addr, + pub splitter: Addr, + + pub splits: BTreeMap, + pub transfer_amount: Uint128, + pub remote_chain_info: RemoteChainInfo, + + pub receiver_1: Addr, + pub receiver_2: Addr, +} + +impl Suite { + pub fn query_clock_address(&self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_remote_chain_splitter::msg::QueryMsg::ClockAddress {}, + ) + .unwrap() + } + + pub fn query_contract_state(&self) -> valence_remote_chain_splitter::msg::ContractState { + self.app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_remote_chain_splitter::msg::QueryMsg::ContractState {}, + ) + .unwrap() + } + + pub fn query_remote_chain_info(&self) -> RemoteChainInfo { + self.app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_remote_chain_splitter::msg::QueryMsg::RemoteChainInfo {}, + ) + .unwrap() + } + + pub fn query_split_config(&self) -> BTreeMap { + let split_config: Vec<(String, SplitConfig)> = self + .app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_remote_chain_splitter::msg::QueryMsg::SplitConfig {}, + ) + .unwrap(); + BTreeMap::from_iter(split_config) + } + + pub fn query_transfer_amount(&self) -> Uint128 { + self.app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_remote_chain_splitter::msg::QueryMsg::TransferAmount {}, + ) + .unwrap() + } + + pub fn query_deposit_address(&self, addr: Addr) -> Option { + self.app + .wrap() + .query_wasm_smart( + addr, + &valence_remote_chain_splitter::msg::QueryMsg::DepositAddress {}, + ) + .unwrap() + } + + pub fn query_fallback_address(&self) -> Option { + self.app + .wrap() + .query_wasm_smart( + self.splitter.clone(), + &valence_remote_chain_splitter::msg::QueryMsg::FallbackAddress {}, + ) + .unwrap() + } + + pub fn distribute_fallback(&mut self, coins: Vec, funds: Vec) -> AppResponse { + self.app + .execute_contract( + self.faucet.clone(), + self.splitter.clone(), + &valence_remote_chain_splitter::msg::ExecuteMsg::DistributeFallback { coins }, + &funds, + ) + .unwrap() + } + + pub fn query_ica_address(&mut self, addr: Addr) -> Addr { + self.app + .wrap() + .query_wasm_smart( + addr, + &valence_remote_chain_splitter::msg::QueryMsg::IcaAddress {}, + ) + .unwrap() + } +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} diff --git a/unit-tests/src/test_remote_chain_splitter/tests.rs b/unit-tests/src/test_remote_chain_splitter/tests.rs new file mode 100644 index 00000000..bacb3f7e --- /dev/null +++ b/unit-tests/src/test_remote_chain_splitter/tests.rs @@ -0,0 +1,535 @@ +use std::{collections::BTreeMap, str::FromStr}; + +use cosmwasm_std::{coin, coins, Addr, Decimal, Uint128}; +use covenant_utils::split::SplitConfig; +use cw_multi_test::Executor; +use valence_remote_chain_splitter::msg::FallbackAddressUpdateConfig; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + ADMIN, DENOM_ATOM_ON_NTRN, DENOM_FALLBACK_ON_HUB, DENOM_LS_ATOM_ON_NTRN, DENOM_NTRN, + DENOM_OSMO_ON_HUB_FROM_NTRN, +}; + +use super::suite::RemoteChainSplitterBuilder; + +#[test] +#[should_panic] +fn test_instantiate_validates_clock_address() { + RemoteChainSplitterBuilder::default() + .with_clock_address("oo0oOo0".to_string()) + .build(); +} + +#[test] +#[should_panic(expected = "shares must add up to 1.0")] +fn test_instantiate_validates_explicit_split_shares() { + let mut builder = RemoteChainSplitterBuilder::default(); + let (denom, mut split_config) = builder.instantiate_msg.msg.splits.pop_first().unwrap(); + let invalid_split_config: BTreeMap = split_config + .receivers + .iter_mut() + .map(|(k, _)| (k.to_string(), Decimal::percent(49))) + .collect(); + builder.instantiate_msg.msg.splits.insert( + denom, + SplitConfig { + receivers: invalid_split_config, + }, + ); + builder.build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_explicit_split_receiver_addresses() { + let mut split_config = BTreeMap::new(); + split_config.insert("invalid_address".to_string(), Decimal::one()); + + let mut invalid_splits = BTreeMap::new(); + invalid_splits.insert( + DENOM_ATOM_ON_NTRN.to_string(), + SplitConfig { + receivers: split_config, + }, + ); + + RemoteChainSplitterBuilder::default() + .with_splits(invalid_splits) + .build(); +} + +#[test] +#[should_panic(expected = "Caller is not the clock, only clock can tick contracts")] +fn test_execute_tick_validates_clock() { + let mut suite = RemoteChainSplitterBuilder::default().build(); + + suite + .app + .execute_contract( + suite.faucet, + suite.splitter, + &valence_remote_chain_splitter::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap(); +} + +#[test] +fn test_execute_tick_registers_ica() { + let mut suite = RemoteChainSplitterBuilder::default().build(); + + let splitter = suite.splitter.clone(); + suite.fund_contract(&coins(1000000, DENOM_NTRN), splitter.clone()); + + assert!(suite.query_deposit_address(splitter.clone()).is_none()); + + suite.tick_contract(splitter.clone()); + + assert!(suite.query_deposit_address(splitter.clone()).is_some()); +} + +#[test] +#[should_panic(expected = "forwarder ica not created not found")] +fn test_execute_tick_split_funds_errors_if_receiver_deposit_address_unavailable() { + let mut suite = RemoteChainSplitterBuilder::default().build(); + + let splitter = suite.splitter.clone(); + suite.fund_contract(&coins(1000000, DENOM_NTRN), splitter.clone()); + + assert!(suite.query_deposit_address(splitter.clone()).is_none()); + + suite.tick_contract(splitter.clone()); + suite.tick_contract(splitter); +} + +#[test] +fn test_execute_tick_splits_funds_happy() { + let mut suite = RemoteChainSplitterBuilder::default().build(); + + let splitter = suite.splitter.clone(); + let receiver_1 = suite.receiver_1.clone(); + let receiver_2 = suite.receiver_2.clone(); + + suite.fund_contract(&coins(10000000, DENOM_NTRN), splitter.clone()); + suite.fund_contract(&coins(1000000, DENOM_NTRN), receiver_1.clone()); + suite.fund_contract(&coins(1000000, DENOM_NTRN), receiver_2.clone()); + + assert!(suite.query_deposit_address(splitter.clone()).is_none()); + assert!(suite.query_deposit_address(receiver_1.clone()).is_none()); + assert!(suite.query_deposit_address(receiver_2.clone()).is_none()); + + suite.tick_contract(splitter.clone()); + suite.tick_contract(receiver_1.clone()); + suite.tick_contract(receiver_2.clone()); + + let r1_ica = Addr::unchecked(suite.query_deposit_address(receiver_1.clone()).unwrap()); + let r2_ica = Addr::unchecked(suite.query_deposit_address(receiver_2.clone()).unwrap()); + let splitter_ica = Addr::unchecked(suite.query_deposit_address(splitter.clone()).unwrap()); + + let zero_bal = coin(0, DENOM_ATOM_ON_NTRN); + + suite.assert_balance(&r1_ica, zero_bal.clone()); + suite.assert_balance(&r2_ica, zero_bal.clone()); + suite.assert_balance(&splitter_ica, zero_bal.clone()); + + let amount = coins(10000, DENOM_ATOM_ON_NTRN); + let amount_halved = coin(5000, DENOM_ATOM_ON_NTRN); + + suite.fund_contract(&amount, splitter_ica.clone()); + suite.assert_balance(&splitter_ica, amount[0].clone()); + + suite.tick_contract(splitter); + + suite.assert_balance(&r1_ica, amount_halved.clone()); + suite.assert_balance(&r2_ica, amount_halved.clone()); + suite.assert_balance(&splitter_ica, zero_bal.clone()); +} + +#[test] +fn test_execute_tick_splits_with_no_leftover() { + let mut builder = RemoteChainSplitterBuilder::default().with_amount(Uint128::new(100)); + let mut split_config = builder + .instantiate_msg + .msg + .splits + .get(DENOM_ATOM_ON_NTRN) + .unwrap() + .clone(); + let mut first_entry = split_config.receivers.pop_first().unwrap(); + let mut second_entry = split_config.receivers.pop_first().unwrap(); + + first_entry.1 = Decimal::from_str("0.107").unwrap(); + second_entry.1 = Decimal::from_str("0.893").unwrap(); + + split_config.receivers.insert(first_entry.0, first_entry.1); + split_config + .receivers + .insert(second_entry.0, second_entry.1); + + builder + .instantiate_msg + .msg + .splits + .insert(DENOM_ATOM_ON_NTRN.to_string(), split_config); + + let mut suite = builder.build(); + + let splitter = suite.splitter.clone(); + let receiver_1 = suite.receiver_1.clone(); + let receiver_2 = suite.receiver_2.clone(); + + suite.fund_contract(&coins(10000000, DENOM_NTRN), splitter.clone()); + suite.fund_contract(&coins(1000000, DENOM_NTRN), receiver_1.clone()); + suite.fund_contract(&coins(1000000, DENOM_NTRN), receiver_2.clone()); + + assert!(suite.query_deposit_address(splitter.clone()).is_none()); + assert!(suite.query_deposit_address(receiver_1.clone()).is_none()); + assert!(suite.query_deposit_address(receiver_2.clone()).is_none()); + + suite.tick_contract(splitter.clone()); + suite.tick_contract(receiver_1.clone()); + suite.tick_contract(receiver_2.clone()); + + let r1_ica = Addr::unchecked(suite.query_deposit_address(receiver_1.clone()).unwrap()); + let r2_ica = Addr::unchecked(suite.query_deposit_address(receiver_2.clone()).unwrap()); + let splitter_ica = Addr::unchecked(suite.query_deposit_address(splitter.clone()).unwrap()); + + let zero_bal = coin(0, DENOM_ATOM_ON_NTRN); + + suite.assert_balance(&r1_ica, zero_bal.clone()); + suite.assert_balance(&r2_ica, zero_bal.clone()); + suite.assert_balance(&splitter_ica, zero_bal.clone()); + + let amount = coins(100, DENOM_ATOM_ON_NTRN); + let expected_first_coin = coin(11, DENOM_ATOM_ON_NTRN); + let expected_second_coin = coin(89, DENOM_ATOM_ON_NTRN); + + suite.fund_contract(&amount, splitter_ica.clone()); + suite.assert_balance(&splitter_ica, amount[0].clone()); + + suite.tick_contract(splitter); + + suite.assert_balance(&r1_ica, expected_first_coin.clone()); + suite.assert_balance(&r2_ica, expected_second_coin.clone()); + suite.assert_balance(&splitter_ica, zero_bal.clone()); +} + +#[test] +fn test_migrate_update_config() { + let mut suite = RemoteChainSplitterBuilder::default().build(); + + let mut remote_chain_info = suite.query_remote_chain_info(); + let mut split_config = suite.query_split_config(); + + let mut split = split_config.get(DENOM_ATOM_ON_NTRN).unwrap().clone(); + + split.receivers.insert( + suite.receiver_1.to_string(), + Decimal::from_str("0.1").unwrap(), + ); + split.receivers.insert( + suite.receiver_2.to_string(), + Decimal::from_str("0.9").unwrap(), + ); + + split_config.insert(DENOM_ATOM_ON_NTRN.to_string(), split.clone()); + + remote_chain_info.denom = DENOM_LS_ATOM_ON_NTRN.to_string(); + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.splitter.clone(), + &valence_remote_chain_splitter::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(suite.faucet.to_string()), + remote_chain_info: Some(remote_chain_info.clone()), + splits: Some(split_config.clone()), + fallback_address: Some(FallbackAddressUpdateConfig::ExplicitAddress( + suite.faucet.to_string(), + )), + }, + 6, + ) + .unwrap(); + + let new_remote_chain_info = suite.query_remote_chain_info(); + let new_split_config = suite.query_split_config(); + let clock_addr = suite.query_clock_address(); + let fallback_addr = suite.query_fallback_address().unwrap(); + + assert_eq!(suite.faucet, clock_addr); + assert_eq!(remote_chain_info, new_remote_chain_info); + assert_eq!(split_config, new_split_config); + assert_eq!(suite.faucet, fallback_addr); +} + +#[test] +fn test_migrate_update_config_disable_fallback() { + let mut builder = RemoteChainSplitterBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.splitter.clone(), + &valence_remote_chain_splitter::msg::MigrateMsg::UpdateConfig { + clock_addr: None, + remote_chain_info: None, + splits: None, + fallback_address: Some(FallbackAddressUpdateConfig::Disable {}), + }, + 6, + ) + .unwrap(); + + let fallback_addr = suite.query_fallback_address(); + assert!(fallback_addr.is_none()); +} + +#[test] +#[should_panic(expected = "shares must add up to 1.0")] +fn test_migrate_update_config_validates_splits() { + let mut suite = RemoteChainSplitterBuilder::default().build(); + + let mut split_config = suite.query_split_config(); + + let mut split = split_config.get(DENOM_ATOM_ON_NTRN).unwrap().clone(); + + split.receivers.insert( + suite.receiver_1.to_string(), + Decimal::from_str("0.41").unwrap(), + ); + split.receivers.insert( + suite.receiver_2.to_string(), + Decimal::from_str("0.9").unwrap(), + ); + + split_config.insert(DENOM_ATOM_ON_NTRN.to_string(), split.clone()); + + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.splitter.clone(), + &valence_remote_chain_splitter::msg::MigrateMsg::UpdateConfig { + clock_addr: None, + remote_chain_info: None, + splits: Some(split_config.clone()), + fallback_address: None, + }, + 6, + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "Missing fallback address")] +fn test_distribute_fallback_errors_without_fallback_address() { + let mut suite = RemoteChainSplitterBuilder::default().build(); + + let splitter_addr = suite.splitter.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), splitter_addr.clone()); + + // register ica + suite.tick_contract(splitter_addr.clone()); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![coin(100_000, DENOM_ATOM_ON_NTRN.to_string())], + coins(1_000_000, DENOM_NTRN), + ); +} + +#[test] +#[should_panic(expected = "Cannot distribute target denom via fallback distribution")] +fn test_distribute_fallback_validates_denom() { + let mut builder = RemoteChainSplitterBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + let splitter_addr = suite.splitter.clone(); + + // fund splitter to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), splitter_addr.clone()); + + // register ica + suite.tick_contract(splitter_addr.clone()); + + let splitter_ica = suite.query_ica_address(splitter_addr.clone()); + + // fund the ica with sufficient amount of DENOM_ATOM_ON_NTRN + suite.fund_contract(&coins(100_000, DENOM_ATOM_ON_NTRN), splitter_ica.clone()); + suite.assert_balance(&splitter_ica, coin(100_000, DENOM_ATOM_ON_NTRN)); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![coin(100_000, DENOM_ATOM_ON_NTRN.to_string())], + coins(1_000_000, DENOM_NTRN), + ); +} + +#[test] +#[should_panic(expected = "must cover ibc fees to distribute fallback denoms")] +fn test_distribute_fallback_validates_ibc_fee_coverage() { + let mut builder = RemoteChainSplitterBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + let splitter_addr = suite.splitter.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), splitter_addr.clone()); + + // register ica + suite.tick_contract(splitter_addr.clone()); + + let forwarder_ica = suite.query_ica_address(splitter_addr.clone()); + + // fund the ica with sufficient amount of DENOM_ATOM_ON_NTRN + suite.fund_contract(&coins(100_000, DENOM_ATOM_ON_NTRN), forwarder_ica.clone()); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_ATOM_ON_NTRN)); + + // try to distribute fallback denom + suite.distribute_fallback(vec![coin(100_000, DENOM_ATOM_ON_NTRN.to_string())], vec![]); +} + +#[test] +#[should_panic(expected = "insufficient fees")] +fn test_distribute_fallback_validates_insufficient_ibc_fee_coverage() { + let mut builder = RemoteChainSplitterBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + let splitter_addr = suite.splitter.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), splitter_addr.clone()); + + // register ica + suite.tick_contract(splitter_addr.clone()); + + let forwarder_ica = suite.query_ica_address(splitter_addr.clone()); + + // fund the ica with sufficient amount of DENOM_ATOM_ON_NTRN + suite.fund_contract(&coins(100_000, DENOM_ATOM_ON_NTRN), forwarder_ica.clone()); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_ATOM_ON_NTRN)); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![coin(100_000, DENOM_ATOM_ON_NTRN.to_string())], + coins(5_000, DENOM_NTRN), + ); +} + +#[test] +#[should_panic(expected = "Attempt to distribute duplicate denoms via fallback distribution")] +fn test_distribute_fallback_validates_duplicate_input_denoms() { + let mut builder = RemoteChainSplitterBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + let splitter_addr = suite.splitter.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(2_000_000, DENOM_NTRN), splitter_addr.clone()); + + // register ica + suite.tick_contract(splitter_addr.clone()); + + let forwarder_ica = suite.query_ica_address(splitter_addr.clone()); + + // fund the ica with sufficient amount of DENOM_ATOM_ON_NTRN + suite.fund_contract( + &coins(100_000, DENOM_FALLBACK_ON_HUB), + forwarder_ica.clone(), + ); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_FALLBACK_ON_HUB)); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![ + coin(100_000, DENOM_FALLBACK_ON_HUB.to_string()), + coin(100_000, DENOM_FALLBACK_ON_HUB.to_string()), + ], + coins(1_000_000, DENOM_NTRN), + ); + + // assert that the funds were in fact forwarded + suite.assert_balance(&forwarder_ica, coin(0, DENOM_FALLBACK_ON_HUB)); +} + +#[test] +#[should_panic(expected = "no ica found")] +fn test_distribute_fallback_validates_ica_exists() { + let mut builder = RemoteChainSplitterBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![coin(100_000, DENOM_FALLBACK_ON_HUB.to_string())], + coins(1_000_000, DENOM_NTRN), + ); +} + +#[test] +fn test_distribute_fallback_happy() { + let mut builder = RemoteChainSplitterBuilder::default(); + builder.instantiate_msg.msg.fallback_address = + Some(builder.instantiate_msg.msg.clock_address.to_string()); + let mut suite = builder.build(); + + let splitter_addr = suite.splitter.clone(); + + // fund forwarder to register the ica + suite.fund_contract(&coins(3_000_000, DENOM_NTRN), splitter_addr.clone()); + + // register ica + suite.tick_contract(splitter_addr.clone()); + + let forwarder_ica = suite.query_ica_address(splitter_addr.clone()); + + // fund the ica with sufficient amount of DENOM_FALLBACK_ON_HUB and + suite.fund_contract( + &coins(100_000, DENOM_FALLBACK_ON_HUB), + forwarder_ica.clone(), + ); + suite.fund_contract( + &coins(100_000, DENOM_OSMO_ON_HUB_FROM_NTRN), + forwarder_ica.clone(), + ); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_FALLBACK_ON_HUB)); + suite.assert_balance(&forwarder_ica, coin(100_000, DENOM_OSMO_ON_HUB_FROM_NTRN)); + + // try to distribute fallback denom + suite.distribute_fallback( + vec![ + coin(100_000, DENOM_FALLBACK_ON_HUB.to_string()), + coin(100_000, DENOM_OSMO_ON_HUB_FROM_NTRN), + ], + vec![coin(2_000_000, DENOM_NTRN)], + ); + + // assert that the funds were in fact forwarded + suite.assert_balance(&forwarder_ica, coin(0, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(&forwarder_ica, coin(0, DENOM_OSMO_ON_HUB_FROM_NTRN)); + suite.assert_balance( + suite.clock_addr.to_string(), + coin(100_000, DENOM_FALLBACK_ON_HUB), + ); + suite.assert_balance( + suite.clock_addr.to_string(), + coin(100_000, DENOM_OSMO_ON_HUB_FROM_NTRN), + ); +} diff --git a/unit-tests/src/test_single_party_covenant/mod.rs b/unit-tests/src/test_single_party_covenant/mod.rs new file mode 100644 index 00000000..755bfe37 --- /dev/null +++ b/unit-tests/src/test_single_party_covenant/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod test; diff --git a/unit-tests/src/test_single_party_covenant/suite.rs b/unit-tests/src/test_single_party_covenant/suite.rs new file mode 100644 index 00000000..713d0bd0 --- /dev/null +++ b/unit-tests/src/test_single_party_covenant/suite.rs @@ -0,0 +1,475 @@ +use std::{collections::BTreeMap, str::FromStr}; + +use cosmwasm_std::{coin, Addr, Coin, Decimal, StdResult}; +use cw_multi_test::Executor; +use valence_covenant_single_party_pol::msg::CovenantContractCodeIds; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::single_party_covenant::SinglePartyCovenantInstantiate, + suite_builder::SuiteBuilder, + CustomApp, DENOM_ATOM, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_STRIDE, + HUB_STRIDE_CHANNEL, NTRN_HUB_CHANNEL, NTRN_STRIDE_CHANNEL, SINGLE_PARTY_COVENANT_SALT, +}; + +#[allow(dead_code)] +pub(super) struct Suite { + pub fuacet: Addr, + pub admin: Addr, + + pub app: CustomApp, + + pub covenant_addr: Addr, + pub clock_addr: Addr, + pub holder_addr: Addr, + pub splitter_addr: Addr, + pub lser_addr: Addr, + pub lper_addr: Addr, + pub ls_forwarder_addr: Addr, + pub lp_forwarder_addr: Addr, + pub router_addr: Addr, + + // The receiver address + pub party_receiver: Addr, + pub party_local_receiver: Addr, + pub ls_receiver: Addr, + + // Astro + pub pool_addr: Addr, + pub lp_token_addr: Addr, +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.fuacet.clone() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} + +impl Suite { + fn build( + mut builder: SuiteBuilder, + covenant_addr: Addr, + party_receiver: Addr, + party_local_receiver: Addr, + ls_receiver: Addr, + pool_addr: Addr, + lp_token_addr: Addr, + ) -> Self { + let clock_addr = builder + .app + .wrap() + .query_wasm_smart( + covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let holder_addr = builder + .app + .wrap() + .query_wasm_smart( + covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::HolderAddress {}, + ) + .unwrap(); + + let splitter_addr = builder + .app + .wrap() + .query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::SplitterAddress {}, + ) + .unwrap(); + builder.fund_with_ntrn(&splitter_addr, 2_000_000_u128); + + let router_addr = builder + .app + .wrap() + .query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::InterchainRouterAddress {}, + ) + .unwrap(); + builder.fund_with_ntrn(&router_addr, 2_000_000_u128); + + let lser_addr = builder + .app + .wrap() + .query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::LiquidStakerAddress {}, + ) + .unwrap(); + builder.fund_with_ntrn(&lser_addr, 2_000_000_u128); + + let ls_forwarder_addr = builder + .app + .wrap() + .query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::IbcForwarderAddress { + ty: "ls".to_string(), + }, + ) + .unwrap(); + builder.fund_with_ntrn(&ls_forwarder_addr, 2_000_000_u128); + + let lper_addr = builder + .app + .wrap() + .query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::LiquidPoolerAddress {}, + ) + .unwrap(); + let lp_forwarder_addr = builder + .app + .wrap() + .query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::IbcForwarderAddress { + ty: "lp".to_string(), + }, + ) + .unwrap(); + builder.fund_with_ntrn(&lp_forwarder_addr, 2_000_000_u128); + + Self { + fuacet: builder.faucet.clone(), + admin: builder.admin.clone(), + + covenant_addr, + clock_addr, + holder_addr, + splitter_addr, + lser_addr, + lper_addr, + ls_forwarder_addr, + lp_forwarder_addr, + router_addr, + + party_receiver, + party_local_receiver, + ls_receiver, + + pool_addr, + lp_token_addr, + + // Make sure its the last one, because build() consume the builder + app: builder.build(), + } + } +} + +impl Suite { + pub fn new_with_stable_pool() -> Self { + let mut builder = SuiteBuilder::new(); + + let covenant_addr = builder.get_contract_addr( + builder.single_party_covenant_code_id, + SINGLE_PARTY_COVENANT_SALT, + ); + + // init astro pools + let (pool_addr, lp_token_addr) = builder.init_astro_pool( + astroport::factory::PairType::Stable {}, + coin(10_000_000_000_000, DENOM_ATOM_ON_NTRN), + coin(10_000_000_000_000, DENOM_LS_ATOM_ON_NTRN), + ); + + let party_receiver = builder.get_random_addr(); + let party_receiver_on_ntrn = builder.get_random_addr(); + let ls_receiver = builder.get_random_addr(); + let ls_receiver_on_ntrn = builder.get_random_addr(); + + let mut pfm = BTreeMap::new(); + pfm.insert( + DENOM_LS_ATOM_ON_NTRN.to_string(), + covenant_utils::PacketForwardMiddlewareConfig { + local_to_hop_chain_channel_id: NTRN_STRIDE_CHANNEL.0.to_string(), + hop_to_destination_chain_channel_id: HUB_STRIDE_CHANNEL.1.to_string(), + hop_chain_receiver_address: ls_receiver.to_string(), + }, + ); + + let covenant_party = SinglePartyCovenantInstantiate::get_covenant_party( + &party_receiver, + &party_receiver_on_ntrn, + DENOM_ATOM, + DENOM_ATOM_ON_NTRN, + NTRN_HUB_CHANNEL.0, + NTRN_HUB_CHANNEL.1, + 1_000_000_000_000_u128, + pfm, + ); + + let pooler_config = SinglePartyCovenantInstantiate::get_astro_pooler_config( + DENOM_ATOM_ON_NTRN, + DENOM_LS_ATOM_ON_NTRN, + &pool_addr, + astroport::factory::PairType::Stable {}, + covenant_utils::SingleSideLpLimits { + asset_a_limit: 500_000_000_u128.into(), + asset_b_limit: 500_000_000_u128.into(), + }, + ); + let ls_forwarder_config = SinglePartyCovenantInstantiate::get_forwarder_config_interchain( + &ls_receiver, + &ls_receiver_on_ntrn, + DENOM_ATOM, + DENOM_LS_ATOM_ON_STRIDE, + HUB_STRIDE_CHANNEL.1, + HUB_STRIDE_CHANNEL.0, + 500_000_000_000_u128, + ); + let lp_forwarder_config = SinglePartyCovenantInstantiate::get_forwarder_config_interchain( + &Addr::unchecked("not_used"), + &Addr::unchecked("not_used"), + DENOM_ATOM, + DENOM_ATOM_ON_NTRN, + NTRN_HUB_CHANNEL.0, + NTRN_HUB_CHANNEL.1, + 500_000_000_000_u128, + ); + let remote_splitter = SinglePartyCovenantInstantiate::get_remote_splitter_config( + NTRN_HUB_CHANNEL.0, + DENOM_ATOM, + 1_000_000_000_000_u128, + Decimal::bps(5000), + Decimal::bps(5000), + ); + let pool_price_config = SinglePartyCovenantInstantiate::get_pool_price_config( + Decimal::from_str("1").unwrap(), + Decimal::bps(5000), + ); + let init_msg = SinglePartyCovenantInstantiate::default( + &builder, + ls_forwarder_config, + lp_forwarder_config, + remote_splitter, + covenant_party, + pooler_config, + pool_price_config, + ); + + builder.contract_init2( + builder.single_party_covenant_code_id, + SINGLE_PARTY_COVENANT_SALT, + &init_msg.msg, + &[], + ); + + Self::build( + builder, + covenant_addr, + party_receiver, + party_receiver_on_ntrn, + ls_receiver, + pool_addr, + lp_token_addr, + ) + } + + pub fn new_with_xyk_pool() -> Self { + let mut builder = SuiteBuilder::new(); + + let covenant_addr = builder.get_contract_addr( + builder.single_party_covenant_code_id, + SINGLE_PARTY_COVENANT_SALT, + ); + + // init astro pools + let (pool_addr, lp_token_addr) = builder.init_astro_pool( + astroport::factory::PairType::Xyk {}, + coin(10_000_000_000_000, DENOM_ATOM_ON_NTRN), + coin(10_000_000_000_000, DENOM_LS_ATOM_ON_NTRN), + ); + + let party_receiver = builder.get_random_addr(); + let party_receiver_on_ntrn = builder.get_random_addr(); + let ls_receiver = builder.get_random_addr(); + let ls_receiver_on_ntrn = builder.get_random_addr(); + + let mut pfm = BTreeMap::new(); + pfm.insert( + DENOM_LS_ATOM_ON_NTRN.to_string(), + covenant_utils::PacketForwardMiddlewareConfig { + local_to_hop_chain_channel_id: NTRN_STRIDE_CHANNEL.0.to_string(), + hop_to_destination_chain_channel_id: HUB_STRIDE_CHANNEL.1.to_string(), + hop_chain_receiver_address: ls_receiver.to_string(), + }, + ); + + let covenant_party = SinglePartyCovenantInstantiate::get_covenant_party( + &party_receiver, + &party_receiver_on_ntrn, + DENOM_ATOM, + DENOM_ATOM_ON_NTRN, + NTRN_HUB_CHANNEL.0, + NTRN_HUB_CHANNEL.1, + 1_000_000_000_000_u128, + pfm, + ); + + let pooler_config = SinglePartyCovenantInstantiate::get_astro_pooler_config( + DENOM_ATOM_ON_NTRN, + DENOM_LS_ATOM_ON_NTRN, + &pool_addr, + astroport::factory::PairType::Xyk {}, + covenant_utils::SingleSideLpLimits { + asset_a_limit: 10_000_000_u128.into(), + asset_b_limit: 10_000_000_u128.into(), + }, + ); + let ls_forwarder_config = SinglePartyCovenantInstantiate::get_forwarder_config_interchain( + &ls_receiver, + &ls_receiver_on_ntrn, + DENOM_ATOM, + DENOM_LS_ATOM_ON_STRIDE, + HUB_STRIDE_CHANNEL.1, + HUB_STRIDE_CHANNEL.0, + 500_000_000_000_u128, + ); + let lp_forwarder_config = SinglePartyCovenantInstantiate::get_forwarder_config_interchain( + &Addr::unchecked("not_used"), + &Addr::unchecked("not_used"), + DENOM_ATOM, + DENOM_ATOM_ON_NTRN, + NTRN_HUB_CHANNEL.0, + NTRN_HUB_CHANNEL.1, + 500_000_000_000_u128, + ); + let remote_splitter = SinglePartyCovenantInstantiate::get_remote_splitter_config( + NTRN_HUB_CHANNEL.0, + DENOM_ATOM, + 1_000_000_000_000_u128, + Decimal::bps(5000), + Decimal::bps(5000), + ); + let pool_price_config = SinglePartyCovenantInstantiate::get_pool_price_config( + Decimal::from_str("1").unwrap(), + Decimal::bps(5000), + ); + let init_msg = SinglePartyCovenantInstantiate::default( + &builder, + ls_forwarder_config, + lp_forwarder_config, + remote_splitter, + covenant_party, + pooler_config, + pool_price_config, + ); + + builder.contract_init2( + builder.single_party_covenant_code_id, + SINGLE_PARTY_COVENANT_SALT, + &init_msg.msg, + &[], + ); + + Self::build( + builder, + covenant_addr, + party_receiver, + party_receiver_on_ntrn, + ls_receiver, + pool_addr, + lp_token_addr, + ) + } +} + +// helpers +impl Suite { + pub fn get_and_fund_depositors(&mut self, a: Coin) -> Addr { + while self.query_deposit_addr().is_err() { + self.tick_clock_debug(); + } + + let depositor = self.query_deposit_addr().unwrap(); + + self.app + .send_tokens(self.fuacet.clone(), depositor.clone(), &[a]) + .unwrap(); + + depositor + } + + pub fn get_ica(&mut self, addr: Addr) -> Addr { + while self + .app + .wrap() + .query_wasm_smart::( + &addr, + &valence_remote_chain_splitter::msg::QueryMsg::IcaAddress {}, + ) + .is_err() + { + self.tick("Wait for ICA"); + } + + self.app + .wrap() + .query_wasm_smart::( + addr, + &valence_remote_chain_splitter::msg::QueryMsg::IcaAddress {}, + ) + .unwrap() + } + + pub fn astro_swap(&mut self, coin: Coin) { + self.app + .execute_contract( + self.fuacet.clone(), + self.pool_addr.clone(), + &astroport::pair::ExecuteMsg::Swap { + offer_asset: astroport::asset::Asset { + info: astroport::asset::AssetInfo::NativeToken { + denom: coin.denom.clone(), + }, + amount: coin.amount, + }, + ask_asset_info: None, + belief_price: None, + max_spread: Some(Decimal::bps(5000)), + to: None, + }, + &[coin], + ) + .unwrap(); + } +} +// queries +impl Suite { + pub fn query_deposit_addr(&self) -> StdResult { + self.app.wrap().query_wasm_smart( + self.covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::PartyDepositAddress {}, + ) + } + + pub fn query_contract_codes(&self) -> CovenantContractCodeIds { + self.app + .wrap() + .query_wasm_smart::( + self.covenant_addr.clone(), + &valence_covenant_two_party_pol::msg::QueryMsg::ContractCodes {}, + ) + .unwrap() + } +} diff --git a/unit-tests/src/test_single_party_covenant/test.rs b/unit-tests/src/test_single_party_covenant/test.rs new file mode 100644 index 00000000..b07a2599 --- /dev/null +++ b/unit-tests/src/test_single_party_covenant/test.rs @@ -0,0 +1,1204 @@ +use cosmwasm_std::{coin, to_json_binary, Addr, Event, Uint128, Uint64}; +use covenant_utils::neutron::RemoteChainInfo; +use cw_multi_test::{AppResponse, Executor}; + +use crate::setup::{ + base_suite::BaseSuiteMut, ADMIN, DENOM_ATOM, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, + DENOM_LS_ATOM_ON_STRIDE, +}; + +use super::suite::Suite; + +#[test] +fn test_covenant() { + let mut suite = Suite::new_with_stable_pool(); + let resp: AppResponse = suite + .app + .execute_contract( + suite.admin.clone(), + suite.lser_addr.clone(), + &valence_stride_liquid_staker::msg::ExecuteMsg::Transfer { + amount: 500_000_000_000_u128.into(), + }, + &[], + ) + .unwrap(); + + resp.assert_event( + &Event::new("wasm") + .add_attribute("method", "try_permisionless_transfer") + .add_attribute("ica_status", "not_created"), + ); + + suite.get_and_fund_depositors(coin(1_000_000_000_000_u128, DENOM_ATOM)); + + // Verify forwarders got their split from the splitter + let lp_forwarder_ica = suite.get_ica(suite.lp_forwarder_addr.clone()); + let ls_forwarder_ica = suite.get_ica(suite.ls_forwarder_addr.clone()); + + while suite + .app + .wrap() + .query_all_balances(lp_forwarder_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lp_forwarder ICA to get its split"); + } + + while suite + .app + .wrap() + .query_all_balances(ls_forwarder_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for ls_forwarder ICA to get its split"); + } + + let lp_forwarder_ica_balance = suite + .app + .wrap() + .query_balance(lp_forwarder_ica.clone(), DENOM_ATOM) + .unwrap(); + let ls_forwarder_ica_balance = suite + .app + .wrap() + .query_balance(ls_forwarder_ica.clone(), DENOM_ATOM) + .unwrap(); + + assert_eq!(lp_forwarder_ica_balance.amount.u128(), 500_000_000_000_u128); + assert_eq!(ls_forwarder_ica_balance.amount.u128(), 500_000_000_000_u128); + + // Wait for forwarders to forward the funds to the correct addrs + let lser_ica = suite.get_ica(suite.lser_addr.clone()); + + // lser_ica should get his half on stride (lsAtom on stride) + while suite + .app + .wrap() + .query_all_balances(lser_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lser ICA to get his lsAtom"); + } + + // lper should get his atom (atom on neutron) + while suite + .app + .wrap() + .query_all_balances(suite.lper_addr.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lser ICA to get his lsAtom"); + } + + // Make sure the correct denoms are received on the correct addrs + let lser_ica_balance = suite + .app + .wrap() + .query_balance(lser_ica, DENOM_LS_ATOM_ON_STRIDE) + .unwrap(); + let lper_balance = suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_ATOM_ON_NTRN) + .unwrap(); + + assert_eq!(lser_ica_balance.amount.u128(), 500_000_000_000_u128); + assert_eq!(lper_balance.amount.u128(), 500_000_000_000_u128); + + // TODO: Currently we need to manually send the LS tokens from stride to the lper + // TODO: When autopilot will be able to auto send over IBC, we can wait on the lper to receive both denoms + suite + .app + .execute_contract( + suite.admin.clone(), + suite.lser_addr.clone(), + &valence_stride_liquid_staker::msg::ExecuteMsg::Transfer { + amount: 500_000_000_000_u128.into(), + }, + &[], + ) + .unwrap(); + + // We only check that lper got the ls tokens, as we already have the native atom check + let lper_balance = suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_LS_ATOM_ON_NTRN) + .unwrap(); + assert_eq!(lper_balance.amount.u128(), 500_000_000_000_u128); + + // Wait until lper provide liquidity + while suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_LS_ATOM_ON_NTRN) + .unwrap() + .amount + .u128() + > 100_000_000_000_u128 + { + suite.tick("Wait for lper to provide liquidity"); + } + + suite.app.update_block(|b| { + b.height += 5; + b.time = b.time.plus_seconds(15) + }); + + // Verify lper has the lp tokens after providing liquidity + let lper_lp_token_balance = suite + .app + .wrap() + .query_wasm_smart::( + suite.lp_token_addr.clone(), + &cw20::Cw20QueryMsg::Balance { + address: suite.lper_addr.to_string(), + }, + ) + .unwrap(); + + assert!(lper_lp_token_balance.balance > Uint128::zero()); + + // Try to claim, but we still in the lockup period so this should fail. + suite + .app + .execute_contract( + suite.party_local_receiver.clone(), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap_err(); + + // pass the lockup period, and try to withdraw the liquidity + suite.app.update_block(|b| { + b.height += 100000; + b.time = b.time.plus_seconds(100000 * 3) + }); + + suite + .app + .execute_contract( + suite.party_local_receiver.clone(), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap(); + + let _router_addr = suite + .app + .wrap() + .query_wasm_smart::( + suite.covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::InterchainRouterAddress {}, + ) + .unwrap(); + + while suite + .app + .wrap() + .query_all_balances(suite.party_receiver.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for party_receiver to get funds"); + } + + let receiver_balance = suite + .app + .wrap() + .query_balance(suite.party_receiver.clone(), DENOM_ATOM) + .unwrap(); + + // We used pfm, so the receiver should have close to 1_000_000_000_000 uatom + assert!(receiver_balance.amount.u128() > 900_000_000_000_u128); +} + +#[test] +fn test_covenant_with_xyk_pool() { + let mut suite = Suite::new_with_xyk_pool(); + + suite.get_and_fund_depositors(coin(1_000_000_000_000_u128, DENOM_ATOM)); + + // Verify forwarders got their split from the splitter + let lp_forwarder_ica = suite.get_ica(suite.lp_forwarder_addr.clone()); + let ls_forwarder_ica = suite.get_ica(suite.ls_forwarder_addr.clone()); + + while suite + .app + .wrap() + .query_all_balances(lp_forwarder_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lp_forwarder ICA to get its split"); + } + + while suite + .app + .wrap() + .query_all_balances(ls_forwarder_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lp_forwarder ICA to get its split"); + } + + let lp_forwarder_ica_balance = suite + .app + .wrap() + .query_balance(lp_forwarder_ica.clone(), DENOM_ATOM) + .unwrap(); + let ls_forwarder_ica_balance = suite + .app + .wrap() + .query_balance(ls_forwarder_ica.clone(), DENOM_ATOM) + .unwrap(); + + assert_eq!(lp_forwarder_ica_balance.amount.u128(), 500_000_000_000_u128); + assert_eq!(ls_forwarder_ica_balance.amount.u128(), 500_000_000_000_u128); + + // Wait for forwarders to forward the funds to the correct addrs + let lser_ica = suite.get_ica(suite.lser_addr.clone()); + + // lser_ica should get his half on stride (lsAtom on stride) + while suite + .app + .wrap() + .query_all_balances(lser_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lser ICA to get his lsAtom"); + } + + // lper should get his atom (atom on neutron) + while suite + .app + .wrap() + .query_all_balances(suite.lper_addr.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lser ICA to get his lsAtom"); + } + + // Make sure the correct denoms are received on the correct addrs + let lser_ica_balance = suite + .app + .wrap() + .query_balance(lser_ica, DENOM_LS_ATOM_ON_STRIDE) + .unwrap(); + let lper_balance = suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_ATOM_ON_NTRN) + .unwrap(); + + assert_eq!(lser_ica_balance.amount.u128(), 500_000_000_000_u128); + assert_eq!(lper_balance.amount.u128(), 500_000_000_000_u128); + + // TODO: Currently we need to manually send the LS tokens from stride to the lper + // TODO: When autopilot will be able to auto send over IBC, we can wait on the lper to receive both denoms + suite + .app + .execute_contract( + suite.admin.clone(), + suite.lser_addr.clone(), + &valence_stride_liquid_staker::msg::ExecuteMsg::Transfer { + amount: 500_000_000_000_u128.into(), + }, + &[], + ) + .unwrap(); + + // We only check that lper got the ls tokens, as we already have the native atom check + let lper_balance = suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_LS_ATOM_ON_NTRN) + .unwrap(); + assert_eq!(lper_balance.amount.u128(), 500_000_000_000_u128); + + // Wait until lper provide liquidity + while suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_LS_ATOM_ON_NTRN) + .unwrap() + .amount + .u128() + > 100_000_000_000_u128 + { + suite.tick("Wait for lper to provide liquidity"); + } + + suite.app.update_block(|b| { + b.height += 5; + b.time = b.time.plus_seconds(15) + }); + + // Verify lper has the lp tokens after providing liquidity + let lper_lp_token_balance = suite + .app + .wrap() + .query_wasm_smart::( + suite.lp_token_addr.clone(), + &cw20::Cw20QueryMsg::Balance { + address: suite.lper_addr.to_string(), + }, + ) + .unwrap(); + + assert!(lper_lp_token_balance.balance > Uint128::zero()); + + // Try to claim, but we still in the lockup period so this should fail. + suite + .app + .execute_contract( + suite.party_local_receiver.clone(), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap_err(); + + // pass the lockup period, and try to withdraw the liquidity + suite.app.update_block(|b| { + b.height += 100000; + b.time = b.time.plus_seconds(100000 * 3) + }); + + suite + .app + .execute_contract( + suite.party_local_receiver.clone(), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap(); + + let _router_addr = suite + .app + .wrap() + .query_wasm_smart::( + suite.covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::InterchainRouterAddress {}, + ) + .unwrap(); + + while suite + .app + .wrap() + .query_all_balances(suite.party_receiver.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for party_receiver to get funds"); + } + + let receiver_balance = suite + .app + .wrap() + .query_balance(suite.party_receiver.clone(), DENOM_ATOM) + .unwrap(); + + // We used pfm, so the receiver should have close to 1_000_000_000_000 uatom + assert!(receiver_balance.amount.u128() > 900_000_000_000_u128); +} + +#[test] +fn test_covenant_with_uneven_pool() { + let mut suite = Suite::new_with_xyk_pool(); + + suite.astro_swap(coin(512_345_678_987, DENOM_ATOM_ON_NTRN)); + + suite.get_and_fund_depositors(coin(1_000_000_000_000_u128, DENOM_ATOM)); + + // Verify forwarders got their split from the splitter + let lp_forwarder_ica = suite.get_ica(suite.lp_forwarder_addr.clone()); + let ls_forwarder_ica = suite.get_ica(suite.ls_forwarder_addr.clone()); + + while suite + .app + .wrap() + .query_all_balances(lp_forwarder_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lp_forwarder ICA to get its split"); + } + + while suite + .app + .wrap() + .query_all_balances(ls_forwarder_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lp_forwarder ICA to get its split"); + } + + let lp_forwarder_ica_balance = suite + .app + .wrap() + .query_balance(lp_forwarder_ica.clone(), DENOM_ATOM) + .unwrap(); + let ls_forwarder_ica_balance = suite + .app + .wrap() + .query_balance(ls_forwarder_ica.clone(), DENOM_ATOM) + .unwrap(); + + assert_eq!(lp_forwarder_ica_balance.amount.u128(), 500_000_000_000_u128); + assert_eq!(ls_forwarder_ica_balance.amount.u128(), 500_000_000_000_u128); + + // Wait for forwarders to forward the funds to the correct addrs + let lser_ica = suite.get_ica(suite.lser_addr.clone()); + + // lser_ica should get his half on stride (lsAtom on stride) + while suite + .app + .wrap() + .query_all_balances(lser_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lser ICA to get his lsAtom"); + } + + // lper should get his atom (atom on neutron) + while suite + .app + .wrap() + .query_all_balances(suite.lper_addr.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lser ICA to get his lsAtom"); + } + + // Make sure the correct denoms are received on the correct addrs + let lser_ica_balance = suite + .app + .wrap() + .query_balance(lser_ica, DENOM_LS_ATOM_ON_STRIDE) + .unwrap(); + let lper_balance = suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_ATOM_ON_NTRN) + .unwrap(); + + assert_eq!(lser_ica_balance.amount.u128(), 500_000_000_000_u128); + assert_eq!(lper_balance.amount.u128(), 500_000_000_000_u128); + + // TODO: Currently we need to manually send the LS tokens from stride to the lper + // TODO: When autopilot will be able to auto send over IBC, we can wait on the lper to receive both denoms + suite + .app + .execute_contract( + suite.admin.clone(), + suite.lser_addr.clone(), + &valence_stride_liquid_staker::msg::ExecuteMsg::Transfer { + amount: 500_000_000_000_u128.into(), + }, + &[], + ) + .unwrap(); + + // We only check that lper got the ls tokens, as we already have the native atom check + let lper_balance = suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_LS_ATOM_ON_NTRN) + .unwrap(); + assert_eq!(lper_balance.amount.u128(), 500_000_000_000_u128); + + // Wait until lper provide liquidity + while suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_LS_ATOM_ON_NTRN) + .unwrap() + .amount + .u128() + > 100_000_000_000_u128 + { + suite.tick("Wait for lper to provide liquidity"); + } + + suite.app.update_block(|b| { + b.height += 5; + b.time = b.time.plus_seconds(15) + }); + + // Verify lper has the lp tokens after providing liquidity + let lper_lp_token_balance = suite + .app + .wrap() + .query_wasm_smart::( + suite.lp_token_addr.clone(), + &cw20::Cw20QueryMsg::Balance { + address: suite.lper_addr.to_string(), + }, + ) + .unwrap(); + assert!(lper_lp_token_balance.balance > Uint128::zero()); + + // Try to claim, but we still in the lockup period so this should fail. + suite + .app + .execute_contract( + suite.party_local_receiver.clone(), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap_err(); + + // pass the lockup period, and try to withdraw the liquidity + suite.app.update_block(|b| { + b.height += 100000; + b.time = b.time.plus_seconds(100000 * 3) + }); + + suite + .app + .execute_contract( + suite.party_local_receiver.clone(), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap(); + + let _router_addr = suite + .app + .wrap() + .query_wasm_smart::( + suite.covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::InterchainRouterAddress {}, + ) + .unwrap(); + + while suite + .app + .wrap() + .query_all_balances(suite.party_receiver.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for party_receiver to get funds"); + } + + let receiver_balance = suite + .app + .wrap() + .query_balance(suite.party_receiver.clone(), DENOM_ATOM) + .unwrap(); + + // We used pfm, so the receiver should have close to 1_000_000_000_000 uatom + assert!(receiver_balance.amount.u128() > 900_000_000_000_u128); +} + +#[test] +fn test_covenant_with_uneven_pool_stable() { + let mut suite = Suite::new_with_stable_pool(); + + suite.astro_swap(coin(512_345_678_987, DENOM_ATOM_ON_NTRN)); + suite.astro_swap(coin(712_345_678_987, DENOM_LS_ATOM_ON_NTRN)); + + suite.get_and_fund_depositors(coin(1_000_000_000_000_u128, DENOM_ATOM)); + + // Verify forwarders got their split from the splitter + let lp_forwarder_ica = suite.get_ica(suite.lp_forwarder_addr.clone()); + let ls_forwarder_ica = suite.get_ica(suite.ls_forwarder_addr.clone()); + + while suite + .app + .wrap() + .query_all_balances(lp_forwarder_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lp_forwarder ICA to get its split"); + } + + while suite + .app + .wrap() + .query_all_balances(ls_forwarder_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lp_forwarder ICA to get its split"); + } + + let lp_forwarder_ica_balance = suite + .app + .wrap() + .query_balance(lp_forwarder_ica.clone(), DENOM_ATOM) + .unwrap(); + let ls_forwarder_ica_balance = suite + .app + .wrap() + .query_balance(ls_forwarder_ica.clone(), DENOM_ATOM) + .unwrap(); + + assert_eq!(lp_forwarder_ica_balance.amount.u128(), 500_000_000_000_u128); + assert_eq!(ls_forwarder_ica_balance.amount.u128(), 500_000_000_000_u128); + + // Wait for forwarders to forward the funds to the correct addrs + let lser_ica = suite.get_ica(suite.lser_addr.clone()); + + // lser_ica should get his half on stride (lsAtom on stride) + while suite + .app + .wrap() + .query_all_balances(lser_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lser ICA to get his lsAtom"); + } + + // lper should get his atom (atom on neutron) + while suite + .app + .wrap() + .query_all_balances(suite.lper_addr.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lser ICA to get his lsAtom"); + } + + // Make sure the correct denoms are received on the correct addrs + let lser_ica_balance = suite + .app + .wrap() + .query_balance(lser_ica, DENOM_LS_ATOM_ON_STRIDE) + .unwrap(); + let lper_balance = suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_ATOM_ON_NTRN) + .unwrap(); + + assert_eq!(lser_ica_balance.amount.u128(), 500_000_000_000_u128); + assert_eq!(lper_balance.amount.u128(), 500_000_000_000_u128); + + // TODO: Currently we need to manually send the LS tokens from stride to the lper + // TODO: When autopilot will be able to auto send over IBC, we can wait on the lper to receive both denoms + suite + .app + .execute_contract( + suite.admin.clone(), + suite.lser_addr.clone(), + &valence_stride_liquid_staker::msg::ExecuteMsg::Transfer { + amount: 500_000_000_000_u128.into(), + }, + &[], + ) + .unwrap(); + + // We only check that lper got the ls tokens, as we already have the native atom check + let lper_balance = suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_LS_ATOM_ON_NTRN) + .unwrap(); + assert_eq!(lper_balance.amount.u128(), 500_000_000_000_u128); + + // Wait until lper provide liquidity + while suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_LS_ATOM_ON_NTRN) + .unwrap() + .amount + .u128() + > 100_000_000_000_u128 + { + suite.tick("Wait for lper to provide liquidity"); + } + + // We provided liquidty but the pool is out of range for our single sided liquidity, so we should have leftovers + let lper_balance = suite + .app + .wrap() + .query_all_balances(suite.lper_addr.clone()) + .unwrap(); + assert!(lper_balance.len() == 1); + assert!(lper_balance[0].amount.u128() > 10_000_000_u128); + + suite.app.update_block(|b| { + b.height += 5; + b.time = b.time.plus_seconds(15) + }); + + // Verify lper has the lp tokens after providing liquidity + let lper_lp_token_balance = suite + .app + .wrap() + .query_wasm_smart::( + suite.lp_token_addr.clone(), + &cw20::Cw20QueryMsg::Balance { + address: suite.lper_addr.to_string(), + }, + ) + .unwrap(); + assert!(lper_lp_token_balance.balance > Uint128::zero()); + + // Try to claim, but we still in the lockup period so this should fail. + suite + .app + .execute_contract( + suite.party_local_receiver.clone(), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap_err(); + + // pass the lockup period, and try to withdraw the liquidity + suite.app.update_block(|b| { + b.height += 100000; + b.time = b.time.plus_seconds(100000 * 3) + }); + + suite + .app + .execute_contract( + suite.party_local_receiver.clone(), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap(); + + let _router_addr = suite + .app + .wrap() + .query_wasm_smart::( + suite.covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::InterchainRouterAddress {}, + ) + .unwrap(); + + while suite + .app + .wrap() + .query_all_balances(suite.party_receiver.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for party_receiver to get funds"); + } + + let receiver_balance = suite + .app + .wrap() + .query_balance(suite.party_receiver.clone(), DENOM_ATOM) + .unwrap(); + + // We used pfm, so the receiver should have close to 1_000_000_000_000 uatom + assert!(receiver_balance.amount.u128() > 900_000_000_000_u128); +} + +#[test] +fn test_covenant_with_single_sided() { + let mut suite = Suite::new_with_stable_pool(); + + suite.astro_swap(coin(345_678_987, DENOM_ATOM_ON_NTRN)); + + suite.get_and_fund_depositors(coin(1_000_000_000_000_u128, DENOM_ATOM)); + + // Verify forwarders got their split from the splitter + let lp_forwarder_ica = suite.get_ica(suite.lp_forwarder_addr.clone()); + let ls_forwarder_ica = suite.get_ica(suite.ls_forwarder_addr.clone()); + + while suite + .app + .wrap() + .query_all_balances(lp_forwarder_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lp_forwarder ICA to get its split"); + } + + while suite + .app + .wrap() + .query_all_balances(ls_forwarder_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lp_forwarder ICA to get its split"); + } + + let lp_forwarder_ica_balance = suite + .app + .wrap() + .query_balance(lp_forwarder_ica.clone(), DENOM_ATOM) + .unwrap(); + let ls_forwarder_ica_balance = suite + .app + .wrap() + .query_balance(ls_forwarder_ica.clone(), DENOM_ATOM) + .unwrap(); + + assert_eq!(lp_forwarder_ica_balance.amount.u128(), 500_000_000_000_u128); + assert_eq!(ls_forwarder_ica_balance.amount.u128(), 500_000_000_000_u128); + + // Wait for forwarders to forward the funds to the correct addrs + let lser_ica = suite.get_ica(suite.lser_addr.clone()); + + // lser_ica should get his half on stride (lsAtom on stride) + while suite + .app + .wrap() + .query_all_balances(lser_ica.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lser ICA to get his lsAtom"); + } + + // lper should get his atom (atom on neutron) + while suite + .app + .wrap() + .query_all_balances(suite.lper_addr.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for lser ICA to get his lsAtom"); + } + + // Make sure the correct denoms are received on the correct addrs + let lser_ica_balance = suite + .app + .wrap() + .query_balance(lser_ica, DENOM_LS_ATOM_ON_STRIDE) + .unwrap(); + let lper_balance = suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_ATOM_ON_NTRN) + .unwrap(); + + assert_eq!(lser_ica_balance.amount.u128(), 500_000_000_000_u128); + assert_eq!(lper_balance.amount.u128(), 500_000_000_000_u128); + + // TODO: Currently we need to manually send the LS tokens from stride to the lper + // TODO: When autopilot will be able to auto send over IBC, we can wait on the lper to receive both denoms + suite + .app + .execute_contract( + suite.admin.clone(), + suite.lser_addr.clone(), + &valence_stride_liquid_staker::msg::ExecuteMsg::Transfer { + amount: 500_000_000_000_u128.into(), + }, + &[], + ) + .unwrap(); + + // We only check that lper got the ls tokens, as we already have the native atom check + let lper_balance = suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_LS_ATOM_ON_NTRN) + .unwrap(); + assert_eq!(lper_balance.amount.u128(), 500_000_000_000_u128); + + // Wait until lper provide liquidity + while suite + .app + .wrap() + .query_balance(suite.lper_addr.clone(), DENOM_LS_ATOM_ON_NTRN) + .unwrap() + .amount + .u128() + > 100_000_000_000_u128 + { + suite.tick("Wait for lper to provide liquidity"); + } + + // do couple more ticks to provide single sided liquidity + suite.tick("Wait for lper to provide single sided liquidity"); + suite.tick("Wait for lper to provide single sided liquidity"); + suite.tick("Wait for lper to provide single sided liquidity"); + suite.tick("Wait for lper to provide single sided liquidity"); + suite.tick("Wait for lper to provide single sided liquidity"); + suite.tick("Wait for lper to provide single sided liquidity"); + suite.tick("Wait for lper to provide single sided liquidity"); + suite.tick("Wait for lper to provide single sided liquidity"); + suite.tick("Wait for lper to provide single sided liquidity"); + suite.tick("Wait for lper to provide single sided liquidity"); + + // We provided liquidty but the pool is out of range for our single sided liquidity, so we should have leftovers + let lper_balance = suite + .app + .wrap() + .query_all_balances(suite.lper_addr.clone()) + .unwrap(); + assert!(lper_balance.is_empty()); + + suite.app.update_block(|b| { + b.height += 5; + b.time = b.time.plus_seconds(15) + }); + + // Verify lper has the lp tokens after providing liquidity + let lper_lp_token_balance = suite + .app + .wrap() + .query_wasm_smart::( + suite.lp_token_addr.clone(), + &cw20::Cw20QueryMsg::Balance { + address: suite.lper_addr.to_string(), + }, + ) + .unwrap(); + assert!(lper_lp_token_balance.balance > Uint128::zero()); + + // Try to claim, but we still in the lockup period so this should fail. + suite + .app + .execute_contract( + suite.party_local_receiver.clone(), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap_err(); + + // pass the lockup period, and try to withdraw the liquidity + suite.app.update_block(|b| { + b.height += 100000; + b.time = b.time.plus_seconds(100000 * 3) + }); + + suite + .app + .execute_contract( + suite.party_local_receiver.clone(), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap(); + + let _router_addr = suite + .app + .wrap() + .query_wasm_smart::( + suite.covenant_addr.clone(), + &valence_covenant_single_party_pol::msg::QueryMsg::InterchainRouterAddress {}, + ) + .unwrap(); + + // let router_balances = suite + // .app + // .wrap() + // .query_all_balances(router_addr.clone()) + // .unwrap(); + // println!("router balances: {router_balances:?}"); + + while suite + .app + .wrap() + .query_all_balances(suite.party_receiver.clone()) + .unwrap() + .is_empty() + { + suite.tick("Wait for party_receiver to get funds"); + } + + let receiver_balance = suite + .app + .wrap() + .query_balance(suite.party_receiver.clone(), DENOM_ATOM) + .unwrap(); + + // We used pfm, so the receiver should have close to 1_000_000_000_000 uatom + assert!(receiver_balance.amount.u128() > 900_000_000_000_u128); +} + +#[test] +fn test_migrate_update_config_with_codes() { + let mut suite = Suite::new_with_stable_pool(); + let covenant_addr = suite.covenant_addr.clone(); + let mut contract_codes = suite.query_contract_codes(); + contract_codes.clock_code = 69; + + let holder_migrate_msg = valence_single_party_pol_holder::msg::MigrateMsg::UpdateConfig { + withdrawer: Some(covenant_addr.to_string()), + withdraw_to: None, + emergency_committee: None, + pooler_address: None, + lockup_period: None, + }; + + let ibc_forwarder_migrate_msg = valence_ibc_forwarder::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(covenant_addr.to_string()), + next_contract: None, + remote_chain_info: Box::new(None), + transfer_amount: None, + fallback_address: None, + }; + + let liquid_pooler_migrate_msg = + valence_astroport_liquid_pooler::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(covenant_addr.to_string()), + holder_address: None, + lp_config: None, + }; + + let new_remote_chain_info = RemoteChainInfo { + connection_id: "connection-id".to_string(), + channel_id: "channel-id".to_string(), + denom: "denom".to_string(), + ibc_transfer_timeout: Uint64::one(), + ica_timeout: Uint64::one(), + }; + let liquid_staker_migrate_msg = valence_stride_liquid_staker::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(covenant_addr.to_string()), + next_contract: Some(covenant_addr.to_string()), + remote_chain_info: Some(new_remote_chain_info), + }; + + let remote_chain_splitter_migrate_msg = + valence_remote_chain_splitter::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(covenant_addr.to_string()), + remote_chain_info: None, + splits: None, + fallback_address: None, + }; + + let resp = suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + covenant_addr, + &valence_covenant_single_party_pol::msg::MigrateMsg::MigrateContracts { + codes: Some(contract_codes.clone()), + clock: None, + holder: Some(holder_migrate_msg.clone()), + ls_forwarder: Some(ibc_forwarder_migrate_msg.clone()), + lp_forwarder: Some(ibc_forwarder_migrate_msg.clone()), + splitter: Some(remote_chain_splitter_migrate_msg.clone()), + liquid_pooler: Some( + valence_covenant_single_party_pol::msg::LiquidPoolerMigrateMsg::Astroport( + liquid_pooler_migrate_msg.clone(), + ), + ), + liquid_staker: Some(liquid_staker_migrate_msg.clone()), + router: None, + }, + 2, + ) + .unwrap(); + + resp.assert_event( + &Event::new("wasm") + .add_attribute( + "contract_codes_migrate", + to_json_binary(&contract_codes).unwrap().to_base64(), + ) + .add_attribute( + "ls_forwarder_migrate", + to_json_binary(&ibc_forwarder_migrate_msg) + .unwrap() + .to_base64(), + ) + .add_attribute( + "lp_forwarder_migrate", + to_json_binary(&ibc_forwarder_migrate_msg) + .unwrap() + .to_base64(), + ) + .add_attribute( + "liquid_pooler_migrate", + to_json_binary(&liquid_pooler_migrate_msg) + .unwrap() + .to_base64(), + ) + .add_attribute( + "liquid_staker_migrate", + to_json_binary(&liquid_staker_migrate_msg) + .unwrap() + .to_base64(), + ) + .add_attribute( + "splitter_migrate", + to_json_binary(&remote_chain_splitter_migrate_msg) + .unwrap() + .to_base64(), + ) + .add_attribute( + "holder_migrate", + to_json_binary(&holder_migrate_msg).unwrap().to_base64(), + ), + ); + + let new_codes = suite.query_contract_codes(); + assert_eq!(new_codes.clock_code, 69); +} + +#[test] +fn test_migrate_update_config_no_codes() { + let mut suite = Suite::new_with_stable_pool(); + let covenant_addr = suite.covenant_addr.clone(); + + let clock_migrate_msg = valence_clock::msg::MigrateMsg::UpdateTickMaxGas { + new_value: Uint64::new(50000), + }; + let router_migrate_msg = valence_interchain_router::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(covenant_addr.to_string()), + destination_config: None, + target_denoms: None, + }; + let resp = suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + covenant_addr, + &valence_covenant_single_party_pol::msg::MigrateMsg::MigrateContracts { + codes: None, + clock: Some(clock_migrate_msg.clone()), + holder: None, + ls_forwarder: None, + lp_forwarder: None, + splitter: None, + liquid_pooler: None, + liquid_staker: None, + router: Some(router_migrate_msg.clone()), + }, + 2, + ) + .unwrap(); + + resp.assert_event( + &Event::new("wasm") + .add_attribute( + "clock_migrate", + to_json_binary(&clock_migrate_msg).unwrap().to_base64(), + ) + .add_attribute( + "router_migrate", + to_json_binary(&router_migrate_msg).unwrap().to_base64(), + ), + ); +} diff --git a/unit-tests/src/test_single_party_holder/mod.rs b/unit-tests/src/test_single_party_holder/mod.rs new file mode 100644 index 00000000..7b881830 --- /dev/null +++ b/unit-tests/src/test_single_party_holder/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod tests; diff --git a/unit-tests/src/test_single_party_holder/suite.rs b/unit-tests/src/test_single_party_holder/suite.rs new file mode 100644 index 00000000..5b37a0bc --- /dev/null +++ b/unit-tests/src/test_single_party_holder/suite.rs @@ -0,0 +1,344 @@ +use astroport::factory::PairType; +use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; +use covenant_utils::{PoolPriceConfig, SingleSideLpLimits}; +use cw_multi_test::{AppResponse, Executor}; +use cw_utils::Expiration; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::single_party_holder::SinglePartyHolderInstantiate, + suite_builder::SuiteBuilder, + CustomApp, ASTRO_LIQUID_POOLER_SALT, CLOCK_SALT, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, + SINGLE_PARTY_HOLDER_SALT, +}; + +pub struct SinglePartyHolderBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: SinglePartyHolderInstantiate, +} + +impl Default for SinglePartyHolderBuilder { + fn default() -> Self { + let mut builder = SuiteBuilder::new(); + + // init astro pools + let (pool_addr, _lp_token_addr) = builder.init_astro_pool( + astroport::factory::PairType::Stable {}, + coin(10_000_000_000_000, DENOM_ATOM_ON_NTRN), + coin(10_000_000_000_000, DENOM_LS_ATOM_ON_NTRN), + ); + + let holder_addr = builder.get_contract_addr( + builder.single_party_holder_code_id, + SINGLE_PARTY_HOLDER_SALT, + ); + let liquid_pooler_addr = + builder.get_contract_addr(builder.astro_pooler_code_id, ASTRO_LIQUID_POOLER_SALT); + let clock_addr = builder.get_contract_addr(builder.clock_code_id, CLOCK_SALT); + + let clock_instantiate_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: None, + whitelist: vec![liquid_pooler_addr.to_string()], + }; + builder.contract_init2( + builder.clock_code_id, + CLOCK_SALT, + &clock_instantiate_msg, + &[], + ); + + let liquid_pooler_instantiate_msg = valence_astroport_liquid_pooler::msg::InstantiateMsg { + pool_address: pool_addr.to_string(), + clock_address: clock_addr.to_string(), + slippage_tolerance: None, + assets: valence_astroport_liquid_pooler::msg::AssetData { + asset_a_denom: DENOM_ATOM_ON_NTRN.to_string(), + asset_b_denom: DENOM_LS_ATOM_ON_NTRN.to_string(), + }, + single_side_lp_limits: SingleSideLpLimits { + asset_a_limit: Uint128::new(100000), + asset_b_limit: Uint128::new(100000), + }, + pool_price_config: PoolPriceConfig { + expected_spot_price: Decimal::one(), + acceptable_price_spread: Decimal::from_ratio(Uint128::one(), Uint128::new(2)), + }, + pair_type: PairType::Stable {}, + holder_address: holder_addr.to_string(), + }; + + builder.contract_init2( + builder.astro_pooler_code_id, + ASTRO_LIQUID_POOLER_SALT, + &liquid_pooler_instantiate_msg, + &[], + ); + + let holder_instantiate_msg = + SinglePartyHolderInstantiate::default(liquid_pooler_addr.to_string()); + + Self { + builder, + instantiate_msg: holder_instantiate_msg, + } + } +} + +#[allow(dead_code)] +impl SinglePartyHolderBuilder { + pub fn with_withdrawer(mut self, addr: String) -> Self { + self.instantiate_msg.with_withdrawer(addr); + self + } + + pub fn with_withdraw_to(mut self, addr: String) -> Self { + self.instantiate_msg.with_withdraw_to(addr); + self + } + + pub fn with_emergency_committee_addr(mut self, addr: Option) -> Self { + self.instantiate_msg.with_emergency_committee_addr(addr); + self + } + + pub fn with_pooler_address(mut self, addr: &str) -> Self { + self.instantiate_msg.with_pooler_address(addr); + self + } + + pub fn with_lockup_period(mut self, period: Expiration) -> Self { + self.instantiate_msg.with_lockup_period(period); + self + } + + pub fn build(mut self) -> Suite { + let holder_addr = self.builder.contract_init2( + self.builder.single_party_holder_code_id, + SINGLE_PARTY_HOLDER_SALT, + &self.instantiate_msg.msg, + &[], + ); + + let liquid_pooler_address: Addr = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_single_party_pol_holder::msg::QueryMsg::PoolerAddress {}, + ) + .unwrap(); + + let clock_address = self + .builder + .app + .wrap() + .query_wasm_smart( + liquid_pooler_address.to_string(), + &valence_astroport_liquid_pooler::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let withdrawer = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_single_party_pol_holder::msg::QueryMsg::Withdrawer {}, + ) + .unwrap(); + + let withdraw_to = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_single_party_pol_holder::msg::QueryMsg::WithdrawTo {}, + ) + .unwrap(); + + Suite { + faucet: self.builder.faucet.clone(), + admin: self.builder.admin.clone(), + holder_addr, + clock: clock_address, + withdraw_to, + withdrawer, + liquid_pooler_address, + app: self.builder.build(), + } + } +} + +#[allow(dead_code)] +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + pub clock: Addr, + + pub holder_addr: Addr, + pub withdraw_to: Option, + pub withdrawer: Option, + pub liquid_pooler_address: Addr, +} + +impl Suite { + pub fn execute_claim(&mut self, sender: Addr) -> AppResponse { + let holder = self.holder_addr.clone(); + + self.app + .execute_contract( + sender, + holder, + &valence_single_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap() + } + + pub fn execute_distribute(&mut self, sender: Addr, funds: Vec) -> AppResponse { + let holder = self.holder_addr.clone(); + + self.app + .execute_contract( + sender, + holder, + &valence_single_party_pol_holder::msg::ExecuteMsg::Distribute {}, + &funds, + ) + .unwrap() + } + + pub fn execute_withdraw_failed(&mut self, sender: Addr) -> AppResponse { + let holder = self.holder_addr.clone(); + + self.app + .execute_contract( + sender, + holder, + &valence_single_party_pol_holder::msg::ExecuteMsg::WithdrawFailed {}, + &[], + ) + .unwrap() + } + + pub fn execute_emergency_withdraw(&mut self, sender: Addr) -> AppResponse { + let holder = self.holder_addr.clone(); + + self.app + .execute_contract( + sender, + holder, + &valence_single_party_pol_holder::msg::ExecuteMsg::EmergencyWithdraw {}, + &[], + ) + .unwrap() + } + + pub fn expire_lockup(&mut self) { + let expiration = self.query_lockup_period(); + self.app.update_block(|b| match expiration { + Expiration::AtHeight(h) => b.height = h + 1, + Expiration::AtTime(t) => b.time = t, + Expiration::Never {} => (), + }) + } + + pub fn fund_contract_coins(&mut self, funds: Vec, addr: Addr) { + self.fund_contract(&funds, addr) + } + + pub fn enter_pool(&mut self) -> AppResponse { + let pooler = self.liquid_pooler_address.clone(); + let funds = vec![ + coin(1_000_000, DENOM_ATOM_ON_NTRN), + coin(1_000_000, DENOM_LS_ATOM_ON_NTRN), + ]; + let clock = self.clock.clone(); + self.fund_contract(&funds, pooler.clone()); + + self.app + .execute_contract( + clock, + pooler, + &valence_astroport_liquid_pooler::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap() + } + + pub fn query_withdrawer(&mut self) -> Option { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_single_party_pol_holder::msg::QueryMsg::Withdrawer {}, + ) + .unwrap() + } + + pub fn query_withdraw_to(&mut self) -> Option { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_single_party_pol_holder::msg::QueryMsg::WithdrawTo {}, + ) + .unwrap() + } + + pub fn query_pooler_address(&mut self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_single_party_pol_holder::msg::QueryMsg::PoolerAddress {}, + ) + .unwrap() + } + + pub fn query_emergency_committee(&mut self) -> Option { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_single_party_pol_holder::msg::QueryMsg::EmergencyCommitteeAddr {}, + ) + .unwrap() + } + + pub fn query_lockup_period(&mut self) -> Expiration { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_single_party_pol_holder::msg::QueryMsg::LockupConfig {}, + ) + .unwrap() + } +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + // single party holder is not clocked + Addr::unchecked("") + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} diff --git a/unit-tests/src/test_single_party_holder/tests.rs b/unit-tests/src/test_single_party_holder/tests.rs new file mode 100644 index 00000000..2474603f --- /dev/null +++ b/unit-tests/src/test_single_party_holder/tests.rs @@ -0,0 +1,259 @@ +use cosmwasm_std::{coin, Addr, Event, Storage}; +use cw_multi_test::Executor; +use cw_utils::Expiration; + +use crate::setup::{base_suite::BaseSuite, ADMIN, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN}; + +use super::suite::SinglePartyHolderBuilder; + +#[test] +#[should_panic] +fn test_instantiate_validates_withdrawer() { + SinglePartyHolderBuilder::default() + .with_withdrawer("0Oo0Oo".to_string()) + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_invalid_liquid_pooler_addr() { + SinglePartyHolderBuilder::default() + .with_pooler_address("0Oo0Oo") + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_invalid_withdraw_to_addr() { + SinglePartyHolderBuilder::default() + .with_withdraw_to("0Oo0Oo".to_string()) + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_invalid_emergency_committee_addr() { + SinglePartyHolderBuilder::default() + .with_emergency_committee_addr(Some("0Oo0Oo".to_string())) + .build(); +} + +#[test] +#[should_panic(expected = "The lockup period must be in the future")] +fn test_instantiate_validates_lockup_period() { + SinglePartyHolderBuilder::default() + .with_lockup_period(Expiration::AtHeight(1)) + .build(); +} + +#[test] +#[should_panic(expected = "A withdraw process already started")] +fn test_execute_claim_validates_pending_withdrawals() { + let mut suite = SinglePartyHolderBuilder::default().build(); + + suite.enter_pool(); + + let sender = suite.liquid_pooler_address.clone(); + suite.expire_lockup(); + + // manually setting the storage key to true + let withdraw_state_key = "\0\u{4}wasm\0Ocontract_data/cosmos1lxsjav25s55mnxkfzkmvhdkqpsnmlm9whwk8ctqawgj438kda96s54a6mlwithdraw_state".as_bytes(); + suite + .app + .storage_mut() + .set(withdraw_state_key, "true".as_bytes()); + + suite.execute_claim(sender); +} + +#[test] +#[should_panic(expected = "The position is still locked, unlock at: expiration: never")] +fn test_execute_claim_validates_lockup_period() { + let mut suite = SinglePartyHolderBuilder::default() + .with_lockup_period(cw_utils::Expiration::Never {}) + .build(); + + let sender = suite.liquid_pooler_address.clone(); + suite.execute_claim(sender); +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_execute_claim_validates_withdrawer_addr() { + let mut suite = SinglePartyHolderBuilder::default().build(); + + let sender = suite.faucet.clone(); + suite.expire_lockup(); + suite.execute_claim(sender); +} + +#[test] +fn test_execute_claim_happy() { + let mut suite = SinglePartyHolderBuilder::default().build(); + + suite.enter_pool(); + + let sender = suite.liquid_pooler_address.clone(); + let bals = suite.query_all_balances(&suite.liquid_pooler_address); + assert_eq!(bals.len(), 0); + suite.expire_lockup(); + + suite.execute_claim(sender); + + let bals = suite.query_all_balances(&suite.liquid_pooler_address); + assert_eq!(bals.len(), 2); +} + +#[test] +#[should_panic(expected = "A withdraw process already started")] +fn test_execute_emergency_withdraw_validates_pending_withdrawals() { + let mut suite = SinglePartyHolderBuilder::default().build(); + + suite.enter_pool(); + + let sender = suite.liquid_pooler_address.clone(); + suite.expire_lockup(); + + // manually setting the storage key to true + let withdraw_state_key = "\0\u{4}wasm\0Ocontract_data/cosmos1lxsjav25s55mnxkfzkmvhdkqpsnmlm9whwk8ctqawgj438kda96s54a6mlwithdraw_state".as_bytes(); + suite + .app + .storage_mut() + .set(withdraw_state_key, "true".as_bytes()); + + suite.execute_emergency_withdraw(sender); +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_execute_emergency_withdraw_validates_emergency_committee() { + let mut suite = SinglePartyHolderBuilder::default().build(); + let sender = suite.clock.clone(); + suite.execute_emergency_withdraw(sender); +} + +#[test] +fn test_execute_emergency_withdraw_happy() { + let mut suite = SinglePartyHolderBuilder::default().build(); + + suite.enter_pool(); + + let sender = suite.liquid_pooler_address.clone(); + let bals = suite.query_all_balances(&suite.liquid_pooler_address); + assert_eq!(bals.len(), 0); + + suite.expire_lockup(); + suite.execute_emergency_withdraw(sender); + + let bals = suite.query_all_balances(&suite.liquid_pooler_address); + assert_eq!(bals.len(), 2); +} + +#[test] +fn test_execute_distribute_validates_liquidity_pooler() { + let mut suite = SinglePartyHolderBuilder::default().build(); + + let sender = suite.liquid_pooler_address.clone(); + let funds = vec![ + coin(1_000_000, DENOM_ATOM_ON_NTRN), + coin(1_000_000, DENOM_LS_ATOM_ON_NTRN), + ]; + suite.fund_contract_coins(funds.clone(), sender.clone()); + suite.expire_lockup(); + let resp = suite.execute_distribute(sender, funds); + println!("resp: {:?}", resp); +} + +#[test] +#[should_panic(expected = "We expect 2 denoms to be received from the liquidity pooler")] +fn test_execute_distribute_ensures_two_denoms_sent() { + let mut suite = SinglePartyHolderBuilder::default().build(); + + let sender = suite.liquid_pooler_address.clone(); + let funds = vec![coin(1_000_000, DENOM_ATOM_ON_NTRN)]; + suite.fund_contract_coins(funds.clone(), sender.clone()); + suite.expire_lockup(); + + suite.execute_distribute(sender, funds); +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_execute_withdraw_failed_authorizes_liquidity_pooler() { + let mut suite = SinglePartyHolderBuilder::default().build(); + + suite.execute_withdraw_failed(suite.clock.clone()); +} + +#[test] +fn test_execute_withdraw_failed_removes_withdraw_state() { + let _suite = SinglePartyHolderBuilder::default().build(); + // todo +} + +#[test] +#[should_panic(expected = "The lockup period must be in the future")] +fn test_migrate_update_config_validates_lockup_config() { + let mut suite = SinglePartyHolderBuilder::default().build(); + let current_block = suite.get_app().block_info().height; + let past_expiration = Expiration::AtHeight(current_block - 1); + + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::MigrateMsg::UpdateConfig { + withdrawer: None, + withdraw_to: None, + emergency_committee: None, + pooler_address: None, + lockup_period: Some(past_expiration), + }, + 5, + ) + .unwrap(); +} + +#[test] +fn test_migrate_update_config() { + let mut suite = SinglePartyHolderBuilder::default().build(); + + let clock = suite.clock.to_string(); + + let resp = suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.holder_addr.clone(), + &valence_single_party_pol_holder::msg::MigrateMsg::UpdateConfig { + withdrawer: Some(clock.to_string()), + withdraw_to: Some(clock.to_string()), + emergency_committee: Some(clock.to_string()), + pooler_address: Some(clock.to_string()), + lockup_period: Some(Expiration::AtHeight(192837465)), + }, + 5, + ) + .unwrap(); + + resp.assert_event( + &Event::new("wasm") + .add_attribute("withdrawer", clock.to_string()) + .add_attribute("withdraw_to", clock.to_string()) + .add_attribute("emergency_committee", clock.to_string()) + .add_attribute("pool_address", clock.to_string()), + ); + + let withdrawer = suite.query_withdrawer().unwrap().to_string(); + let withdraw_to = suite.query_withdraw_to().unwrap().to_string(); + let emergency_committee = suite.query_emergency_committee().unwrap().to_string(); + let pooler_address = suite.query_pooler_address().to_string(); + let lockup_period = suite.query_lockup_period(); + + assert_eq!(clock, withdrawer); + assert_eq!(clock, withdraw_to); + assert_eq!(clock, emergency_committee); + assert_eq!(clock, pooler_address); + assert_eq!(Expiration::AtHeight(192837465), lockup_period); +} diff --git a/unit-tests/src/test_swap_covenant/mod.rs b/unit-tests/src/test_swap_covenant/mod.rs new file mode 100644 index 00000000..755bfe37 --- /dev/null +++ b/unit-tests/src/test_swap_covenant/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod test; diff --git a/unit-tests/src/test_swap_covenant/suite.rs b/unit-tests/src/test_swap_covenant/suite.rs new file mode 100644 index 00000000..bd821d56 --- /dev/null +++ b/unit-tests/src/test_swap_covenant/suite.rs @@ -0,0 +1,559 @@ +use std::vec; + +use cosmwasm_std::{coins, Addr, Coin, Decimal, StdResult}; +use cw_multi_test::Executor; +use valence_covenant_swap::msg::CovenantContractCodes; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::swap_covenant::SwapCovenantInstantiate, + suite_builder::SuiteBuilder, + CustomApp, DENOM_ATOM, DENOM_ATOM_ON_NTRN, DENOM_NTRN, DENOM_OSMO, DENOM_OSMO_ON_NTRN, + NTRN_HUB_CHANNEL, NTRN_OSMO_CHANNEL, SWAP_COVENANT_SALT, +}; + +pub(super) struct Suite { + pub fuacet: Addr, + pub admin: Addr, + + pub app: CustomApp, + + pub covenant_addr: Addr, + pub clock_addr: Addr, + pub holder_addr: Addr, + pub splitter_addr: Addr, + pub router_a_addr: Addr, + pub router_b_addr: Addr, + + // The address that should receive the final amount of the swap + pub party_a_receiver: Addr, + pub party_b_receiver: Addr, +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.fuacet.clone() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} + +impl Suite { + fn build( + mut builder: SuiteBuilder, + covenant_addr: Addr, + party_a_receiver: Addr, + party_b_receiver: Addr, + ) -> Self { + let clock_addr = builder + .app + .wrap() + .query_wasm_smart( + covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let holder_addr = builder + .app + .wrap() + .query_wasm_smart( + covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::HolderAddress {}, + ) + .unwrap(); + + let router_a_addr = builder + .app + .wrap() + .query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::InterchainRouterAddress { + party: "party_a".to_string(), + }, + ) + .unwrap(); + + let router_b_addr = builder + .app + .wrap() + .query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::InterchainRouterAddress { + party: "party_b".to_string(), + }, + ) + .unwrap(); + + let splitter_addr = builder + .app + .wrap() + .query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::SplitterAddress {}, + ) + .unwrap(); + + if let Ok(ibc_forwarder) = builder.app.wrap().query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::IbcForwarderAddress { + party: "party_a".to_string(), + }, + ) { + builder + .app + .send_tokens( + builder.faucet.clone(), + ibc_forwarder, + &coins(2_000_000, DENOM_NTRN), + ) + .unwrap(); + }; + + if let Ok(ibc_forwarder) = builder.app.wrap().query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::IbcForwarderAddress { + party: "party_b".to_string(), + }, + ) { + builder + .app + .send_tokens( + builder.faucet.clone(), + ibc_forwarder, + &coins(2_000_000, DENOM_NTRN), + ) + .unwrap(); + }; + + // fund routers + if let Ok(router) = builder.app.wrap().query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::InterchainRouterAddress { + party: "party_a".to_string(), + }, + ) { + builder + .app + .send_tokens(builder.faucet.clone(), router, &coins(400_000, DENOM_NTRN)) + .unwrap(); + }; + + if let Ok(router) = builder.app.wrap().query_wasm_smart::( + covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::InterchainRouterAddress { + party: "party_b".to_string(), + }, + ) { + builder + .app + .send_tokens(builder.faucet.clone(), router, &coins(400_000, DENOM_NTRN)) + .unwrap(); + }; + + Self { + fuacet: builder.faucet.clone(), + admin: builder.admin.clone(), + + covenant_addr, + clock_addr, + holder_addr, + splitter_addr, + router_a_addr, + router_b_addr, + + party_a_receiver, + party_b_receiver, + + // Make sure its the last one, because build() consume the builder + app: builder.build(), + } + } +} + +impl Suite { + pub fn new() -> Self { + let mut builder = SuiteBuilder::new(); + + let covenant_addr = + builder.get_contract_addr(builder.swap_covenant_code_id, SWAP_COVENANT_SALT); + + let party_a_receiver = builder.get_random_addr(); + let party_a_on_ntrn = builder.get_random_addr(); + let party_b_receiver = builder.get_random_addr(); + + let recievers = vec![ + (&party_a_receiver, Decimal::bps(5000)), + (&party_b_receiver, Decimal::bps(5000)), + ]; + let splits = SwapCovenantInstantiate::get_split_custom(vec![ + (DENOM_ATOM_ON_NTRN, &recievers), + (DENOM_NTRN, &recievers), + ]); + let party_a_config = SwapCovenantInstantiate::get_party_config_interchain( + &party_a_receiver, + &party_a_on_ntrn, + DENOM_ATOM, + DENOM_ATOM_ON_NTRN, + NTRN_HUB_CHANNEL.0, + NTRN_HUB_CHANNEL.1, + 10_000_000_u128, + ); + let party_b_config = SwapCovenantInstantiate::get_party_config_native( + &party_b_receiver, + DENOM_NTRN, + 10_000_000_u128, + ); + let init_msg = + SwapCovenantInstantiate::default(&builder, party_a_config, party_b_config, splits).msg; + + builder.contract_init2( + builder.swap_covenant_code_id, + SWAP_COVENANT_SALT, + &init_msg, + &[], + ); + + Self::build(builder, covenant_addr, party_a_receiver, party_b_receiver) + } + + pub fn new_with_split_denoms(denom_1: &str, denom_2: &str) -> Self { + let mut builder = SuiteBuilder::new(); + + let covenant_addr = + builder.get_contract_addr(builder.swap_covenant_code_id, SWAP_COVENANT_SALT); + + let party_a_receiver = builder.get_random_addr(); + let party_a_on_ntrn = builder.get_random_addr(); + let party_b_receiver = builder.get_random_addr(); + + let recievers = vec![ + (&party_a_receiver, Decimal::bps(5000)), + (&party_b_receiver, Decimal::bps(5000)), + ]; + let splits = SwapCovenantInstantiate::get_split_custom(vec![ + (denom_1, &recievers), + (denom_2, &recievers), + ]); + let party_a_config = SwapCovenantInstantiate::get_party_config_interchain( + &party_a_receiver, + &party_a_on_ntrn, + DENOM_ATOM, + DENOM_ATOM_ON_NTRN, + NTRN_HUB_CHANNEL.0, + NTRN_HUB_CHANNEL.1, + 10_000_000_u128, + ); + let party_b_config = SwapCovenantInstantiate::get_party_config_native( + &party_b_receiver, + DENOM_NTRN, + 10_000_000_u128, + ); + let init_msg = + SwapCovenantInstantiate::default(&builder, party_a_config, party_b_config, splits).msg; + + builder.contract_init2( + builder.swap_covenant_code_id, + SWAP_COVENANT_SALT, + &init_msg, + &[], + ); + + Self::build(builder, covenant_addr, party_a_receiver, party_b_receiver) + } + + /// Init covenant with 2 native parties and 50% split of both denoms + pub fn new_with_2_native_configs() -> Self { + let mut builder = SuiteBuilder::new(); + + let covenant_addr = + builder.get_contract_addr(builder.swap_covenant_code_id, SWAP_COVENANT_SALT); + + let party_a_receiver = builder.get_random_addr(); + let party_b_receiver = builder.get_random_addr(); + + let recievers = vec![ + (&party_a_receiver, Decimal::bps(5000)), + (&party_b_receiver, Decimal::bps(5000)), + ]; + let splits = SwapCovenantInstantiate::get_split_custom(vec![ + (DENOM_ATOM, &recievers), + (DENOM_NTRN, &recievers), + ]); + let party_a_config = SwapCovenantInstantiate::get_party_config_native( + &party_a_receiver, + DENOM_ATOM, + 10_000_000_u128, + ); + let party_b_config = SwapCovenantInstantiate::get_party_config_native( + &party_b_receiver, + DENOM_NTRN, + 10_000_000_u128, + ); + let init_msg = + SwapCovenantInstantiate::default(&builder, party_a_config, party_b_config, splits).msg; + + builder.contract_init2( + builder.swap_covenant_code_id, + SWAP_COVENANT_SALT, + &init_msg, + &[], + ); + + Self::build(builder, covenant_addr, party_a_receiver, party_b_receiver) + } + + pub fn new_with_2_interchain_configs() -> Self { + let mut builder = SuiteBuilder::new(); + + let covenant_addr = + builder.get_contract_addr(builder.swap_covenant_code_id, SWAP_COVENANT_SALT); + + let party_a_receiver = builder.get_random_addr(); + let party_a_on_ntrn = builder.get_random_addr(); + let party_b_receiver = builder.get_random_addr(); + let party_b_on_ntrn = builder.get_random_addr(); + + let recievers = vec![ + (&party_a_receiver, Decimal::bps(5000)), + (&party_b_receiver, Decimal::bps(5000)), + ]; + let splits = SwapCovenantInstantiate::get_split_custom(vec![ + (DENOM_ATOM_ON_NTRN, &recievers), + (DENOM_OSMO_ON_NTRN, &recievers), + ]); + let party_a_config = SwapCovenantInstantiate::get_party_config_interchain( + &party_a_receiver, + &party_a_on_ntrn, + DENOM_ATOM, + DENOM_ATOM_ON_NTRN, + NTRN_HUB_CHANNEL.0, + NTRN_HUB_CHANNEL.1, + 10_000_000_u128, + ); + let party_b_config = SwapCovenantInstantiate::get_party_config_interchain( + &party_b_receiver, + &party_b_on_ntrn, + DENOM_OSMO, + DENOM_OSMO_ON_NTRN, + NTRN_OSMO_CHANNEL.0, + NTRN_OSMO_CHANNEL.1, + 10_000_000_u128, + ); + let init_msg = + SwapCovenantInstantiate::default(&builder, party_a_config, party_b_config, splits).msg; + + builder.contract_init2( + builder.swap_covenant_code_id, + SWAP_COVENANT_SALT, + &init_msg, + &[], + ); + + Self::build(builder, covenant_addr, party_a_receiver, party_b_receiver) + } + + pub fn new_with_100_percent_split() -> Self { + let mut builder = SuiteBuilder::new(); + + let covenant_addr = + builder.get_contract_addr(builder.swap_covenant_code_id, SWAP_COVENANT_SALT); + + let party_a_receiver = builder.get_random_addr(); + let party_a_on_ntrn = builder.get_random_addr(); + let party_b_receiver = builder.get_random_addr(); + let party_b_on_ntrn = builder.get_random_addr(); + + let recievers_1 = vec![ + (&party_a_receiver, Decimal::one()), + (&party_b_receiver, Decimal::zero()), + ]; + let recievers_2 = vec![ + (&party_b_receiver, Decimal::one()), + (&party_a_receiver, Decimal::zero()), + ]; + let splits = SwapCovenantInstantiate::get_split_custom(vec![ + (DENOM_ATOM_ON_NTRN, &recievers_2), + (DENOM_OSMO_ON_NTRN, &recievers_1), + ]); + let party_a_config = SwapCovenantInstantiate::get_party_config_interchain( + &party_a_receiver, + &party_a_on_ntrn, + DENOM_ATOM, + DENOM_ATOM_ON_NTRN, + NTRN_HUB_CHANNEL.0, + NTRN_HUB_CHANNEL.1, + 10_000_000_u128, + ); + let party_b_config = SwapCovenantInstantiate::get_party_config_interchain( + &party_b_receiver, + &party_b_on_ntrn, + DENOM_OSMO, + DENOM_OSMO_ON_NTRN, + NTRN_OSMO_CHANNEL.0, + NTRN_OSMO_CHANNEL.1, + 10_000_000_u128, + ); + let init_msg = + SwapCovenantInstantiate::default(&builder, party_a_config, party_b_config, splits).msg; + + builder.contract_init2( + builder.swap_covenant_code_id, + SWAP_COVENANT_SALT, + &init_msg, + &[], + ); + + Self::build(builder, covenant_addr, party_a_receiver, party_b_receiver) + } + + pub fn new_with_fallback() -> Self { + let mut builder = SuiteBuilder::new(); + + let covenant_addr = + builder.get_contract_addr(builder.swap_covenant_code_id, SWAP_COVENANT_SALT); + + let party_a_receiver = builder.get_random_addr(); + let party_b_receiver = builder.get_random_addr(); + + let recievers = vec![ + (&party_a_receiver, Decimal::bps(5000)), + (&party_b_receiver, Decimal::bps(5000)), + ]; + let splits = SwapCovenantInstantiate::get_split_custom(vec![ + (DENOM_ATOM, &recievers), + (DENOM_NTRN, &recievers), + ]); + let party_a_config = SwapCovenantInstantiate::get_party_config_native( + &party_a_receiver, + DENOM_ATOM, + 10_000_000_u128, + ); + let party_b_config = SwapCovenantInstantiate::get_party_config_native( + &party_b_receiver, + DENOM_NTRN, + 10_000_000_u128, + ); + let mut init_msg = + SwapCovenantInstantiate::default(&builder, party_a_config, party_b_config, splits); + init_msg.with_fallback_split(&recievers); + + builder.contract_init2( + builder.swap_covenant_code_id, + SWAP_COVENANT_SALT, + &init_msg.msg, + &[], + ); + + Self::build(builder, covenant_addr, party_a_receiver, party_b_receiver) + } + + pub fn new_with_interchain_fallback() -> Self { + let mut builder = SuiteBuilder::new(); + + let covenant_addr = + builder.get_contract_addr(builder.swap_covenant_code_id, SWAP_COVENANT_SALT); + + let party_a_receiver = builder.get_random_addr(); + let party_a_on_ntrn = builder.get_random_addr(); + let party_b_receiver = builder.get_random_addr(); + let party_b_on_ntrn = builder.get_random_addr(); + + let recievers = vec![ + (&party_a_receiver, Decimal::bps(5000)), + (&party_b_receiver, Decimal::bps(5000)), + ]; + let splits = SwapCovenantInstantiate::get_split_custom(vec![ + (DENOM_ATOM_ON_NTRN, &recievers), + (DENOM_OSMO_ON_NTRN, &recievers), + ]); + let party_a_config = SwapCovenantInstantiate::get_party_config_interchain( + &party_a_receiver, + &party_a_on_ntrn, + DENOM_ATOM, + DENOM_ATOM_ON_NTRN, + NTRN_HUB_CHANNEL.0, + NTRN_HUB_CHANNEL.1, + 10_000_000_u128, + ); + let party_b_config = SwapCovenantInstantiate::get_party_config_interchain( + &party_b_receiver, + &party_b_on_ntrn, + DENOM_OSMO, + DENOM_OSMO_ON_NTRN, + NTRN_OSMO_CHANNEL.0, + NTRN_OSMO_CHANNEL.1, + 10_000_000_u128, + ); + let mut init_msg = + SwapCovenantInstantiate::default(&builder, party_a_config, party_b_config, splits); + init_msg.with_fallback_split(&recievers); + + builder.contract_init2( + builder.swap_covenant_code_id, + SWAP_COVENANT_SALT, + &init_msg.msg, + &[], + ); + + Self::build(builder, covenant_addr, party_a_receiver, party_b_receiver) + } +} + +// helpers +impl Suite { + pub fn get_and_fund_depositors(&mut self, a: Coin, b: Coin) -> (Addr, Addr) { + while self.query_deposit_addr("party_a").is_err() { + self.tick("Wait depositor_a is ready"); + } + + while self.query_deposit_addr("party_b").is_err() { + self.tick("Wait depositor_b is ready"); + } + + let depositor_a = self.query_deposit_addr("party_a").unwrap(); + let depositor_b = self.query_deposit_addr("party_b").unwrap(); + + self.app + .send_tokens(self.fuacet.clone(), depositor_a.clone(), &[a]) + .unwrap(); + self.app + .send_tokens(self.fuacet.clone(), depositor_b.clone(), &[b]) + .unwrap(); + + (depositor_a, depositor_b) + } +} +// queries +impl Suite { + pub fn query_deposit_addr(&self, party: &str) -> StdResult { + self.app.wrap().query_wasm_smart( + self.covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::PartyDepositAddress { + party: party.to_string(), + }, + ) + } + + pub fn query_contract_codes(&self) -> CovenantContractCodes { + self.app + .wrap() + .query_wasm_smart::( + self.covenant_addr.clone(), + &valence_covenant_swap::msg::QueryMsg::ContractCodes {}, + ) + .unwrap() + } +} diff --git a/unit-tests/src/test_swap_covenant/test.rs b/unit-tests/src/test_swap_covenant/test.rs new file mode 100644 index 00000000..66bfae44 --- /dev/null +++ b/unit-tests/src/test_swap_covenant/test.rs @@ -0,0 +1,674 @@ +use cosmwasm_std::{coin, coins, to_json_binary, Addr, Event, Uint128, Uint64}; +use cw_multi_test::Executor; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + ADMIN, DENOM_ATOM, DENOM_ATOM_ON_NTRN, DENOM_FALLBACK, DENOM_FALLBACK_ON_HUB, + DENOM_FALLBACK_ON_OSMO, DENOM_HUB_ON_OSMO_FROM_NTRN, DENOM_NTRN, DENOM_NTRN_ON_HUB, DENOM_OSMO, + DENOM_OSMO_ON_HUB_FROM_NTRN, DENOM_OSMO_ON_NTRN, +}; + +use super::suite::Suite; + +#[test] +#[should_panic(expected = "uatom contribution missing an explicit split configuration")] +fn test_instantiate_validates_split_config_denom_1() { + Suite::new_with_split_denoms("invalid", DENOM_NTRN); +} + +#[test] +#[should_panic(expected = "untrn contribution missing an explicit split configuration")] +fn test_instantiate_validates_split_config_denom_2() { + Suite::new_with_split_denoms(DENOM_ATOM_ON_NTRN, "invalid"); +} + +#[test] +fn test_covenant() { + let mut suite = Suite::new(); + + // Wait until depositors are ready and fund them + suite.get_and_fund_depositors( + coin(10_000_000_u128, DENOM_ATOM), + coin(10_000_000_u128, DENOM_NTRN), + ); + + // tick until holder receive both denoms + while suite.query_all_balances(&suite.holder_addr).len() < 2 { + suite.tick("Waiting for holder to receive both denoms"); + } + + // Assert balances are correct and of the correct denoms + let holder_ntrn_balance = suite.query_balance(&suite.holder_addr, DENOM_NTRN); + let holder_atom_balance = suite.query_balance(&suite.holder_addr, DENOM_ATOM_ON_NTRN); + assert_eq!(holder_ntrn_balance.amount.u128(), 10_000_000); + assert_eq!(holder_atom_balance.amount.u128(), 10_000_000); + + // Tick until receiver_a gets his split + while suite.query_all_balances(&suite.party_a_receiver).len() < 2 { + suite.tick("Wait for receiver_a to get his split"); + } + + // Tick until receiver_b gets his split + while suite.query_all_balances(&suite.party_b_receiver).len() < 2 { + suite.tick("Wait for receiver_b to get his split"); + } + + // Verify receiver_a have 2 denoms in his balances + let receiver_a_balances = suite.query_all_balances(&suite.party_a_receiver); + assert_eq!(receiver_a_balances.len(), 2); + + // Verify receiver_b have 2 denoms in his balances + let receiver_b_balances = suite.query_all_balances(&suite.party_b_receiver); + assert_eq!(receiver_b_balances.len(), 2); + + // Make sure party_a receiver have the correct denoms + let receiver_a_balance_atom = suite.query_balance(&suite.party_a_receiver, DENOM_ATOM); + let receiver_a_balance_ntrn = suite.query_balance(&suite.party_a_receiver, DENOM_NTRN_ON_HUB); + assert!(receiver_a_balance_ntrn.amount > Uint128::zero()); + assert!(receiver_a_balance_atom.amount > Uint128::zero()); + + // make sure party_b receiver have the correct denoms + let receiver_b_balance_atom = suite.query_balance(&suite.party_b_receiver, DENOM_NTRN); + let receiver_b_balance_ntrn = suite.query_balance(&suite.party_b_receiver, DENOM_ATOM_ON_NTRN); + assert!(receiver_b_balance_ntrn.amount > Uint128::zero()); + assert!(receiver_b_balance_atom.amount > Uint128::zero()); +} + +#[test] +fn test_covenant_2_native_parties() { + let mut suite = Suite::new_with_2_native_configs(); + + // Wait until depositors are ready and fund them + suite.get_and_fund_depositors( + coin(10_000_000_u128, DENOM_ATOM), + coin(10_000_000_u128, DENOM_NTRN), + ); + + // tick until holder receive both denoms + while suite.query_all_balances(&suite.holder_addr).len() < 2 { + suite.tick("Waiting for holder to receive both denoms"); + } + + // Assert balances are correct and of the correct denoms + let holder_ntrn_balance = suite.query_balance(&suite.holder_addr, DENOM_NTRN); + let holder_atom_balance = suite.query_balance(&suite.holder_addr, DENOM_ATOM); + assert_eq!(holder_ntrn_balance.amount.u128(), 10_000_000); + assert_eq!(holder_atom_balance.amount.u128(), 10_000_000); + + // Tick until receiver_a gets his split + while suite.query_all_balances(&suite.party_a_receiver).len() < 2 { + suite.tick("Wait for receiver_a to get his split"); + } + + // Tick until receiver_b gets his split + while suite.query_all_balances(&suite.party_b_receiver).len() < 2 { + suite.tick("Wait for receiver_b to get his split"); + } + + // Verify balances of receivers are correct + let receiver_a_balance_atom = suite.query_balance(&suite.party_a_receiver, DENOM_ATOM); + let receiver_a_balance_ntrn = suite.query_balance(&suite.party_a_receiver, DENOM_NTRN); + assert!(receiver_a_balance_atom.amount > Uint128::zero()); + assert!(receiver_a_balance_ntrn.amount > Uint128::zero()); + + let receiver_b_balance_atom = suite.query_balance(&suite.party_b_receiver, DENOM_ATOM); + let receiver_b_balance_ntrn = suite.query_balance(&suite.party_b_receiver, DENOM_NTRN); + assert!(receiver_b_balance_atom.amount > Uint128::zero()); + assert!(receiver_b_balance_ntrn.amount > Uint128::zero()); +} + +#[test] +fn test_covenant_2_interchain_parties() { + let mut suite = Suite::new_with_2_interchain_configs(); + + // Wait until depositors are ready and fund them + suite.get_and_fund_depositors( + coin(10_000_000_u128, DENOM_ATOM), + coin(10_000_000_u128, DENOM_OSMO), + ); + + // tick until holder receive both denoms + while suite.query_all_balances(&suite.holder_addr).len() < 2 { + suite.tick("Waiting for holder to receive both denoms"); + } + + // Assert balances are correct and of the correct denoms + let holder_ntrn_balance = suite.query_balance(&suite.holder_addr, DENOM_ATOM_ON_NTRN); + let holder_atom_balance = suite.query_balance(&suite.holder_addr, DENOM_OSMO_ON_NTRN); + assert_eq!(holder_ntrn_balance.amount.u128(), 10_000_000); + assert_eq!(holder_atom_balance.amount.u128(), 10_000_000); + + // Tick until receiver_a gets his split + while suite.query_all_balances(&suite.party_a_receiver).len() < 2 { + suite.tick("Wait for receiver_a to get his split"); + } + + // Tick until receiver_b gets his split + while suite.query_all_balances(&suite.party_b_receiver).len() < 2 { + suite.tick("Wait for receiver_b to get his split"); + } + + // Verify balances of receivers are correct + let receiver_a_balance_atom = suite.query_balance(&suite.party_a_receiver, DENOM_ATOM); + let receiver_a_balance_ntrn = + suite.query_balance(&suite.party_a_receiver, DENOM_OSMO_ON_HUB_FROM_NTRN); + assert!(receiver_a_balance_atom.amount > Uint128::zero()); + assert!(receiver_a_balance_ntrn.amount > Uint128::zero()); + + let receiver_b_balance_atom = suite.query_balance(&suite.party_b_receiver, DENOM_OSMO); + let receiver_b_balance_ntrn = + suite.query_balance(&suite.party_b_receiver, DENOM_HUB_ON_OSMO_FROM_NTRN); + assert!(receiver_b_balance_atom.amount > Uint128::zero()); + assert!(receiver_b_balance_ntrn.amount > Uint128::zero()); +} + +#[test] +fn test_covenant_100_percent_split() { + let mut suite = Suite::new_with_100_percent_split(); + + // Wait until depositors are ready and fund them + suite.get_and_fund_depositors( + coin(10_000_000_u128, DENOM_ATOM), + coin(10_000_000_u128, DENOM_OSMO), + ); + + // tick until holder receive both denoms + while suite.query_all_balances(&suite.holder_addr).len() < 2 { + suite.tick("Waiting for holder to receive both denoms"); + } + + // Assert balances are correct and of the correct denoms + let holder_ntrn_balance = suite.query_balance(&suite.holder_addr, DENOM_ATOM_ON_NTRN); + let holder_atom_balance = suite.query_balance(&suite.holder_addr, DENOM_OSMO_ON_NTRN); + assert_eq!(holder_ntrn_balance.amount.u128(), 10_000_000); + assert_eq!(holder_atom_balance.amount.u128(), 10_000_000); + + // Tick until receiver_a gets his split + while suite.query_all_balances(&suite.party_a_receiver).is_empty() { + suite.tick("Wait for receiver_a to get his split"); + } + + // Tick until receiver_b gets his split + while suite.query_all_balances(&suite.party_b_receiver).is_empty() { + suite.tick("Wait for receiver_b to get his split"); + } + + // Verify balances of receivers are correct + let receiver_a_balance_atom = suite.query_balance(&suite.party_a_receiver, DENOM_ATOM); + let receiver_a_balance_osmo = + suite.query_balance(&suite.party_a_receiver, DENOM_OSMO_ON_HUB_FROM_NTRN); + assert!(receiver_a_balance_atom.amount == Uint128::zero()); + assert!(receiver_a_balance_osmo.amount > Uint128::zero()); + + let receiver_b_balance_atom = suite.query_balance(&suite.party_b_receiver, DENOM_OSMO); + let receiver_b_balance_osmo = + suite.query_balance(&suite.party_b_receiver, DENOM_HUB_ON_OSMO_FROM_NTRN); + assert!(receiver_b_balance_atom.amount == Uint128::zero()); + assert!(receiver_b_balance_osmo.amount > Uint128::zero()); +} + +#[test] +fn test_covenant_fallback_split() { + let mut suite = Suite::new_with_fallback(); + + // Wait until depositors are ready and fund them + suite.get_and_fund_depositors( + coin(10_000_000_u128, DENOM_ATOM), + coin(10_000_000_u128, DENOM_NTRN), + ); + + // Send some denom (ufallback, not part of the covenant) to the splitter + suite + .app + .send_tokens( + suite.fuacet.clone(), + suite.splitter_addr.clone(), + &coins(1_000_000, DENOM_FALLBACK), + ) + .unwrap(); + + // tick until splitter received all denoms + // should be 3 because we sent the fallback (ufallback) as well + while suite.query_all_balances(&suite.splitter_addr).len() < 3 { + suite.tick("Waiting for splitter to receive both denoms"); + } + + // Execute the fallback method on the splitter + suite + .app + .execute_contract( + suite.fuacet.clone(), + suite.splitter_addr.clone(), + &valence_native_splitter::msg::ExecuteMsg::DistributeFallback { + denoms: vec![DENOM_FALLBACK.to_string()], + }, + &[coin(1000000, DENOM_NTRN)], + ) + .unwrap(); + + // Execute the fallback method on the routers + suite + .app + .execute_contract( + suite.fuacet.clone(), + suite.router_a_addr.clone(), + &valence_native_router::msg::ExecuteMsg::DistributeFallback { + denoms: vec![DENOM_FALLBACK.to_string()], + }, + &[coin(1000000, DENOM_NTRN)], + ) + .unwrap(); + + suite + .app + .execute_contract( + suite.fuacet.clone(), + suite.router_b_addr.clone(), + &valence_native_router::msg::ExecuteMsg::DistributeFallback { + denoms: vec![DENOM_FALLBACK.to_string()], + }, + &[coin(1000000, DENOM_NTRN)], + ) + .unwrap(); + + // Tick until receiver_a gets his split with fallback + while suite.query_all_balances(&suite.party_a_receiver).len() < 3 { + println!( + "party_a balance: {:?}", + suite.query_all_balances(&suite.party_a_receiver) + ); + suite.tick("Wait for receiver_a to get his split"); + } + + // Tick until receiver_b gets his split with fallback + while suite.query_all_balances(&suite.party_b_receiver).len() < 3 { + suite.tick("Wait for receiver_b to get his split"); + } + + // Verify balances of receivers are correct + let receiver_a_balance_atom = suite.query_balance(&suite.party_a_receiver, DENOM_ATOM); + let receiver_a_balance_ntrn = suite.query_balance(&suite.party_a_receiver, DENOM_NTRN); + let receiver_a_balance_fallback = suite.query_balance(&suite.party_a_receiver, DENOM_FALLBACK); + assert!(receiver_a_balance_atom.amount > Uint128::zero()); + assert!(receiver_a_balance_ntrn.amount > Uint128::zero()); + assert!(receiver_a_balance_fallback.amount > Uint128::zero()); + + let receiver_b_balance_atom = suite.query_balance(&suite.party_b_receiver, DENOM_ATOM); + let receiver_b_balance_ntrn = suite.query_balance(&suite.party_b_receiver, DENOM_NTRN); + let receiver_b_balance_fallback = suite.query_balance(&suite.party_b_receiver, DENOM_FALLBACK); + assert!(receiver_b_balance_atom.amount > Uint128::zero()); + assert!(receiver_b_balance_ntrn.amount > Uint128::zero()); + assert!(receiver_b_balance_fallback.amount > Uint128::zero()); +} + +#[test] +fn test_covenant_interchain_fallback_split() { + let mut suite = Suite::new_with_interchain_fallback(); + + // Wait until depositors are ready and fund them + suite.get_and_fund_depositors( + coin(10_000_000_u128, DENOM_ATOM), + coin(10_000_000_u128, DENOM_OSMO), + ); + + // Send some denom (ufallback, not part of the covenant) to the splitter + suite + .app + .send_tokens( + suite.fuacet.clone(), + suite.splitter_addr.clone(), + &coins(1_000_000, DENOM_FALLBACK), + ) + .unwrap(); + + // tick until splitter received all denoms + // should be 3 because we sent the fallback (ufallback) as well + while suite.query_all_balances(&suite.splitter_addr).len() < 3 { + suite.tick("Waiting for splitter to receive all denoms"); + } + + // Execute the fallback method on the splitter + suite + .app + .execute_contract( + suite.fuacet.clone(), + suite.splitter_addr.clone(), + &valence_interchain_router::msg::ExecuteMsg::DistributeFallback { + denoms: vec![DENOM_FALLBACK.to_string()], + }, + &[coin(1000000, DENOM_NTRN)], + ) + .unwrap(); + + // Execute the fallback method on the routers + suite + .app + .execute_contract( + suite.fuacet.clone(), + suite.router_a_addr.clone(), + &valence_interchain_router::msg::ExecuteMsg::DistributeFallback { + denoms: vec![DENOM_FALLBACK.to_string()], + }, + &[coin(100000000, DENOM_NTRN)], + ) + .unwrap(); + + suite + .app + .execute_contract( + suite.fuacet.clone(), + suite.router_b_addr.clone(), + &valence_interchain_router::msg::ExecuteMsg::DistributeFallback { + denoms: vec![DENOM_FALLBACK.to_string()], + }, + &[coin(100000000, DENOM_NTRN)], + ) + .unwrap(); + + // Tick until receiver_a gets his split with fallback + while suite.query_all_balances(&suite.party_a_receiver).len() < 3 { + suite.tick("Wait for receiver_a to get his split"); + } + + // Tick until receiver_b gets his split with fallback + while suite.query_all_balances(&suite.party_b_receiver).len() < 3 { + suite.tick("Wait for receiver_b to get his split"); + } + + // Verify balances of receivers are correct + let receiver_a_balance_atom = suite.query_balance(&suite.party_a_receiver, DENOM_ATOM); + let receiver_a_balance_osmo = + suite.query_balance(&suite.party_a_receiver, DENOM_OSMO_ON_HUB_FROM_NTRN); + let receiver_a_balance_fallback = + suite.query_balance(&suite.party_a_receiver, DENOM_FALLBACK_ON_HUB); + assert!(receiver_a_balance_atom.amount > Uint128::zero()); + assert!(receiver_a_balance_osmo.amount > Uint128::zero()); + assert!(receiver_a_balance_fallback.amount > Uint128::zero()); + + let receiver_b_balance_atom = suite.query_balance(&suite.party_b_receiver, DENOM_OSMO); + let receiver_b_balance_osmo = + suite.query_balance(&suite.party_b_receiver, DENOM_HUB_ON_OSMO_FROM_NTRN); + let receiver_b_balance_fallback = + suite.query_balance(&suite.party_b_receiver, DENOM_FALLBACK_ON_OSMO); + assert!(receiver_b_balance_atom.amount > Uint128::zero()); + assert!(receiver_b_balance_osmo.amount > Uint128::zero()); + assert!(receiver_b_balance_fallback.amount > Uint128::zero()); +} + +#[test] +fn test_valence_native_refund() { + let mut suite = Suite::new_with_2_native_configs(); + let init_ntrn_router_balance = suite.query_balance(&suite.router_b_addr, DENOM_NTRN); + + // Wait until depositors are ready and fund them + suite.get_and_fund_depositors( + coin(10_000_000_u128, DENOM_ATOM), + coin(10_000_000_u128, DENOM_NTRN), + ); + + // tick until holder receive both denoms + while suite.query_all_balances(&suite.holder_addr).len() < 2 { + suite.tick("Waiting for holder to receive both denoms"); + } + + // Assert balances are correct and of the correct denoms + let holder_ntrn_balance = suite.query_balance(&suite.holder_addr, DENOM_NTRN); + let holder_atom_balance = suite.query_balance(&suite.holder_addr, DENOM_ATOM); + assert_eq!(holder_ntrn_balance.amount.u128(), 10_000_000); + assert_eq!(holder_atom_balance.amount.u128(), 10_000_000); + + // Expire the covenant + suite.app.update_block(|block| { + block.time = block.time.plus_hours(1_000_000); + block.height += 1_000_000; + }); + // tick to trigger the expiration + suite.tick_contract(suite.holder_addr.clone()); + + // Tick until receiver_a gets his split + while suite.query_all_balances(&suite.party_a_receiver).is_empty() { + suite.tick("Wait for receiver_a to get his split"); + } + + // Tick until receiver_b gets his split + while suite.query_all_balances(&suite.party_b_receiver).is_empty() { + suite.tick("Wait for receiver_b to get his split"); + } + + // Verify balances of receivers are correct + let receiver_a_balance_atom = suite.query_balance(&suite.party_a_receiver, DENOM_ATOM); + assert_eq!(receiver_a_balance_atom.amount.u128(), 10_000_000_u128); + + let receiver_b_balance_ntrn = suite.query_balance(&suite.party_b_receiver, DENOM_NTRN); + // router comes prefunded with some ntrn so we add that to the assertion + assert_eq!( + receiver_b_balance_ntrn.amount.u128(), + 10_000_000_u128 + init_ntrn_router_balance.amount.u128() + ); +} + +#[test] +fn test_migrate_update_with_codes() { + let mut suite = Suite::new_with_2_native_configs(); + let covenant_addr = suite.covenant_addr.to_string(); + + let mut contract_codes = suite.query_contract_codes(); + contract_codes.clock = 1; + + let native_router_migrate_msg = valence_native_router::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(covenant_addr.to_string()), + target_denoms: None, + receiver_address: None, + }; + + let holder_migrate_msg = valence_swap_holder::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(covenant_addr.to_string()), + next_contract: None, + lockup_config: None, + parites_config: Box::new(None), + covenant_terms: None, + refund_config: None, + }; + + let splitter_migrate_msg = valence_native_splitter::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(covenant_addr.to_string()), + fallback_split: None, + splits: None, + }; + + let resp = suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + Addr::unchecked(covenant_addr), + &valence_covenant_swap::msg::MigrateMsg::UpdateCovenant { + codes: Some(contract_codes.clone()), + clock: None, + holder: Some(holder_migrate_msg.clone()), + splitter: Some(splitter_migrate_msg.clone()), + party_a_router: Some(valence_covenant_swap::msg::RouterMigrateMsg::Native( + native_router_migrate_msg.clone(), + )), + party_b_router: Some(valence_covenant_swap::msg::RouterMigrateMsg::Native( + native_router_migrate_msg.clone(), + )), + party_a_forwarder: Box::new(None), + party_b_forwarder: Box::new(None), + }, + 1, + ) + .unwrap(); + + resp.assert_event( + &Event::new("wasm") + .add_attribute( + "contract_codes_migrate", + to_json_binary(&contract_codes).unwrap().to_base64(), + ) + .add_attribute( + "holder_migrate", + to_json_binary(&holder_migrate_msg).unwrap().to_base64(), + ) + .add_attribute( + "splitter_migrate", + to_json_binary(&splitter_migrate_msg).unwrap().to_base64(), + ) + .add_attribute( + "party_a_router_migrate", + to_json_binary(&native_router_migrate_msg) + .unwrap() + .to_base64(), + ) + .add_attribute( + "party_b_router_migrate", + to_json_binary(&native_router_migrate_msg) + .unwrap() + .to_base64(), + ), + ); + + let new_codes = suite.query_contract_codes(); + assert_eq!(contract_codes, new_codes); +} + +#[test] +fn test_migrate_update_without_codes() { + let mut suite = Suite::new_with_2_interchain_configs(); + let covenant_addr = suite.covenant_addr.to_string(); + + let interchain_router_migrate_msg = valence_interchain_router::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(covenant_addr.to_string()), + target_denoms: None, + destination_config: None, + }; + + let ibc_forwarder_migrate_msg = valence_ibc_forwarder::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(covenant_addr.to_string()), + next_contract: None, + remote_chain_info: Box::new(None), + transfer_amount: None, + fallback_address: None, + }; + + let clock_migrate_msg = valence_clock::msg::MigrateMsg::UpdateTickMaxGas { + new_value: Uint64::new(50000), + }; + + let resp = suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + Addr::unchecked(covenant_addr), + &valence_covenant_swap::msg::MigrateMsg::UpdateCovenant { + codes: None, + clock: Some(clock_migrate_msg.clone()), + holder: None, + splitter: None, + party_a_router: Some(valence_covenant_swap::msg::RouterMigrateMsg::Interchain( + interchain_router_migrate_msg.clone(), + )), + party_b_router: Some(valence_covenant_swap::msg::RouterMigrateMsg::Interchain( + interchain_router_migrate_msg.clone(), + )), + party_a_forwarder: Box::new(Some(ibc_forwarder_migrate_msg.clone())), + party_b_forwarder: Box::new(Some(ibc_forwarder_migrate_msg.clone())), + }, + 1, + ) + .unwrap(); + + resp.assert_event( + &Event::new("wasm") + .add_attribute( + "clock_migrate", + to_json_binary(&clock_migrate_msg).unwrap().to_base64(), + ) + .add_attribute( + "party_a_router_migrate", + to_json_binary(&interchain_router_migrate_msg) + .unwrap() + .to_base64(), + ) + .add_attribute( + "party_b_router_migrate", + to_json_binary(&interchain_router_migrate_msg) + .unwrap() + .to_base64(), + ) + .add_attribute( + "party_a_forwarder_migrate", + to_json_binary(&ibc_forwarder_migrate_msg) + .unwrap() + .to_base64(), + ) + .add_attribute( + "party_b_forwarder_migrate", + to_json_binary(&ibc_forwarder_migrate_msg) + .unwrap() + .to_base64(), + ), + ); +} + +// TODO: swap holder is using IBC transfer method isntead of the neutron msg, so this test is not working +// #[test] +// fn test_covenant_interchain_refund() { +// let mut suite = Suite::new_with_2_interchain_configs(); + +// // Wait until depositors are ready and fund them +// suite.get_and_fund_depositors( +// coin(10_000_000_u128.into(), DENOM_ATOM), +// coin(10_000_000_u128.into(), DENOM_OSMO), +// ); + +// // tick until holder receive both denoms +// while suite.query_all_balances(&suite.holder_addr).len() < 2 { +// suite.tick("Waiting for holder to receive both denoms"); +// } + +// // Assert balances are correct and of the correct denoms +// let holder_osmo_balance = suite.query_balance(&suite.holder_addr, DENOM_ATOM_ON_NTRN); +// let holder_atom_balance = suite.query_balance(&suite.holder_addr, DENOM_OSMO_ON_NTRN); +// assert_eq!(holder_osmo_balance.amount.u128(), 10_000_000); +// assert_eq!(holder_atom_balance.amount.u128(), 10_000_000); + +// // Expire the covenant +// suite.app.update_block(|block| { +// block.time = block.time.plus_hours(1_000_000); +// block.height = block.height + 1_000_000; +// }); + +// let res = suite.tick("Wait for receiver_a to get his refund"); +// let res = suite.tick("Wait for receiver_a to get his refund"); +// let res = suite.tick("Wait for receiver_a to get his refund"); +// let res = suite.tick("Wait for receiver_a to get his refund"); +// let res = suite.tick("Wait for receiver_a to get his refund"); +// let res = suite.tick("Wait for receiver_a to get his refund"); +// let res = suite.tick("Wait for receiver_a to get his refund"); +// let res = suite.tick("Wait for receiver_a to get his refund"); +// let res = suite.tick("Wait for receiver_a to get his refund"); +// let res = suite.tick("Wait for receiver_a to get his refund"); +// println!("res: {:?}", res); + +// return; +// // Tick until receiver_a gets his refund +// while suite.query_all_balances(&suite.party_a_receiver).len() < 1 { +// suite.tick("Wait for receiver_a to get his refund"); +// println!( +// "holder balance: {:?}", +// suite.query_all_balances(&suite.holder_addr) +// ); +// } + +// // Tick until receiver_b gets his refund +// while suite.query_all_balances(&suite.party_b_receiver).len() < 1 { +// suite.tick("Wait for receiver_b to get his refund"); +// } + +// // Verify balances of receivers are correct +// let receiver_a_balance_atom = suite.query_balance(&suite.party_a_receiver, DENOM_ATOM); +// assert_eq!(receiver_a_balance_atom.amount.u128(), 10_000_000_u128); + +// let receiver_b_balance_osmo = suite.query_balance(&suite.party_b_receiver, DENOM_OSMO); +// assert_eq!(receiver_b_balance_osmo.amount.u128(), 10_000_000_u128); +// } diff --git a/unit-tests/src/test_swap_holder/mod.rs b/unit-tests/src/test_swap_holder/mod.rs new file mode 100644 index 00000000..7b881830 --- /dev/null +++ b/unit-tests/src/test_swap_holder/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod tests; diff --git a/unit-tests/src/test_swap_holder/suite.rs b/unit-tests/src/test_swap_holder/suite.rs new file mode 100644 index 00000000..01713c2d --- /dev/null +++ b/unit-tests/src/test_swap_holder/suite.rs @@ -0,0 +1,350 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + str::FromStr, +}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::swap_holder::SwapHolderInstantiate, + suite_builder::SuiteBuilder, + CustomApp, CLOCK_SALT, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, NATIVE_SPLITTER_SALT, + SWAP_HOLDER_SALT, +}; +use cosmwasm_std::{Addr, Decimal}; +use covenant_utils::{split::SplitConfig, CovenantPartiesConfig, CovenantTerms}; +use cw_utils::Expiration; +use valence_swap_holder::msg::RefundConfig; + +pub struct SwapHolderBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: SwapHolderInstantiate, +} + +impl Default for SwapHolderBuilder { + fn default() -> Self { + let mut builder = SuiteBuilder::new(); + + let holder_addr = builder.get_contract_addr(builder.swap_holder_code_id, SWAP_HOLDER_SALT); + let clock_addr = builder.get_contract_addr(builder.clock_code_id, CLOCK_SALT); + let native_splitter_addr = + builder.get_contract_addr(builder.native_splitter_code_id, NATIVE_SPLITTER_SALT); + + let party_a_controller_addr = builder.get_random_addr(); + let party_b_controller_addr = builder.get_random_addr(); + + let party_a_router_addr = + builder.get_contract_addr(builder.native_router_code_id, "party_a"); + let party_b_router_addr = + builder.get_contract_addr(builder.native_router_code_id, "party_b"); + + let clock_instantiate_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: None, + whitelist: vec![ + holder_addr.to_string(), + native_splitter_addr.to_string(), + party_a_router_addr.to_string(), + party_b_router_addr.to_string(), + ], + }; + builder.contract_init2( + builder.clock_code_id, + CLOCK_SALT, + &clock_instantiate_msg, + &[], + ); + let denom_set = BTreeSet::from_iter(vec![ + DENOM_ATOM_ON_NTRN.to_string(), + DENOM_LS_ATOM_ON_NTRN.to_string(), + ]); + builder.contract_init2( + builder.native_router_code_id, + "party_a", + &valence_native_router::msg::InstantiateMsg { + clock_address: clock_addr.to_string(), + receiver_address: party_a_controller_addr.to_string(), + denoms: denom_set.clone(), + }, + &[], + ); + builder.contract_init2( + builder.native_router_code_id, + "party_b", + &valence_native_router::msg::InstantiateMsg { + clock_address: clock_addr.to_string(), + receiver_address: party_b_controller_addr.to_string(), + denoms: denom_set.clone(), + }, + &[], + ); + + let mut splits = BTreeMap::new(); + splits.insert( + party_a_router_addr.to_string(), + Decimal::from_str("0.5").unwrap(), + ); + splits.insert( + party_b_router_addr.to_string(), + Decimal::from_str("0.5").unwrap(), + ); + + let split_config = SplitConfig { receivers: splits }; + let mut denom_to_split_config_map = BTreeMap::new(); + denom_to_split_config_map.insert(DENOM_ATOM_ON_NTRN.to_string(), split_config.clone()); + denom_to_split_config_map.insert(DENOM_LS_ATOM_ON_NTRN.to_string(), split_config.clone()); + + let native_splitter_instantiate_msg = valence_native_splitter::msg::InstantiateMsg { + clock_address: clock_addr.to_string(), + splits: denom_to_split_config_map, + fallback_split: None, + }; + + builder.contract_init2( + builder.native_splitter_code_id, + NATIVE_SPLITTER_SALT, + &native_splitter_instantiate_msg, + &[], + ); + + let holder_instantiate_msg = SwapHolderInstantiate::default( + clock_addr.to_string(), + native_splitter_addr.to_string(), + party_a_controller_addr, + party_b_controller_addr, + party_a_router_addr.to_string(), + party_b_router_addr.to_string(), + ); + + Self { + builder, + instantiate_msg: holder_instantiate_msg, + } + } +} + +#[allow(dead_code)] +impl SwapHolderBuilder { + pub fn with_clock_address(mut self, addr: &str) -> Self { + self.instantiate_msg.with_clock_address(addr); + self + } + + pub fn with_next_contract(mut self, addr: &str) -> Self { + self.instantiate_msg.with_next_contract(addr); + self + } + + pub fn with_lockup_config(mut self, period: Expiration) -> Self { + self.instantiate_msg.with_lockup_config(period); + self + } + + pub fn with_covenant_terms(mut self, terms: CovenantTerms) -> Self { + self.instantiate_msg.with_covenant_terms(terms); + self + } + + pub fn with_parties_config(mut self, config: CovenantPartiesConfig) -> Self { + self.instantiate_msg.with_parties_config(config); + self + } + + pub fn build(mut self) -> Suite { + let holder_addr = self.builder.contract_init2( + self.builder.swap_holder_code_id, + SWAP_HOLDER_SALT, + &self.instantiate_msg.msg, + &[], + ); + + let clock_addr = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_swap_holder::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let lockup_config = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_swap_holder::msg::QueryMsg::LockupConfig {}, + ) + .unwrap(); + + let next_contract = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_swap_holder::msg::QueryMsg::NextContract {}, + ) + .unwrap(); + + let covenant_parties_config = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_swap_holder::msg::QueryMsg::CovenantParties {}, + ) + .unwrap(); + + let covenant_terms = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_swap_holder::msg::QueryMsg::CovenantTerms {}, + ) + .unwrap(); + + Suite { + faucet: self.builder.faucet.clone(), + admin: self.builder.admin.clone(), + clock_addr, + holder: holder_addr, + lockup_config, + next_contract, + covenant_parties_config, + covenant_terms, + emergency_committee_addr: None, + app: self.builder.build(), + } + } +} + +impl Suite { + pub fn expire_lockup_config(&mut self) { + let lockup_config = self.lockup_config; + let app = self.get_app(); + match lockup_config { + Expiration::AtHeight(h) => app.update_block(|b| b.height = h), + Expiration::AtTime(t) => app.update_block(|b| b.time = t), + Expiration::Never {} => (), + }; + } + + pub fn query_next_contract(&self) -> Addr { + self.get_app() + .wrap() + .query_wasm_smart( + self.holder.clone(), + &valence_swap_holder::msg::QueryMsg::NextContract {}, + ) + .unwrap() + } + + pub fn query_lockup_config(&self) -> Expiration { + self.get_app() + .wrap() + .query_wasm_smart( + self.holder.clone(), + &valence_swap_holder::msg::QueryMsg::LockupConfig {}, + ) + .unwrap() + } + + pub fn query_covenant_parties_config(&self) -> CovenantPartiesConfig { + self.get_app() + .wrap() + .query_wasm_smart( + self.holder.clone(), + &valence_swap_holder::msg::QueryMsg::CovenantParties {}, + ) + .unwrap() + } + + pub fn query_covenant_terms(&self) -> CovenantTerms { + self.get_app() + .wrap() + .query_wasm_smart( + self.holder.clone(), + &valence_swap_holder::msg::QueryMsg::CovenantTerms {}, + ) + .unwrap() + } + + pub fn query_clock_address(&self) -> Addr { + self.get_app() + .wrap() + .query_wasm_smart( + self.holder.clone(), + &valence_swap_holder::msg::QueryMsg::ClockAddress {}, + ) + .unwrap() + } + + pub fn query_contract_state(&self) -> valence_swap_holder::msg::ContractState { + self.get_app() + .wrap() + .query_wasm_smart( + self.holder.clone(), + &valence_swap_holder::msg::QueryMsg::ContractState {}, + ) + .unwrap() + } + + pub fn query_deposit_address(&self) -> Option { + self.get_app() + .wrap() + .query_wasm_smart( + self.holder.clone(), + &valence_swap_holder::msg::QueryMsg::DepositAddress {}, + ) + .unwrap() + } + + pub fn query_refund_config(&self) -> RefundConfig { + self.get_app() + .wrap() + .query_wasm_smart( + self.holder.clone(), + &valence_swap_holder::msg::QueryMsg::RefundConfig {}, + ) + .unwrap() + } +} + +#[allow(dead_code)] +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + + pub holder: Addr, + pub clock_addr: Addr, + pub lockup_config: Expiration, + pub next_contract: Addr, + pub covenant_parties_config: CovenantPartiesConfig, + pub covenant_terms: CovenantTerms, + pub emergency_committee_addr: Option, +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} diff --git a/unit-tests/src/test_swap_holder/tests.rs b/unit-tests/src/test_swap_holder/tests.rs new file mode 100644 index 00000000..91c2d55f --- /dev/null +++ b/unit-tests/src/test_swap_holder/tests.rs @@ -0,0 +1,288 @@ +use cosmwasm_std::{coin, coins, Addr, Event, Uint128}; +use covenant_utils::{CovenantTerms, SwapCovenantTerms}; +use cw_multi_test::Executor; +use cw_utils::Expiration; +use valence_swap_holder::msg::{ContractState, RefundConfig}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + ADMIN, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, +}; + +use super::suite::SwapHolderBuilder; + +#[test] +#[should_panic] +fn test_instantiate_validates_next_contract() { + SwapHolderBuilder::default() + .with_next_contract("invalid_address") + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_clock_address() { + SwapHolderBuilder::default() + .with_clock_address("invalid_address") + .build(); +} + +#[test] +#[should_panic(expected = "past lockup config")] +fn test_instantiate_validates_lockup_config() { + SwapHolderBuilder::default() + .with_lockup_config(Expiration::AtHeight(0)) + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_party_a_refund_addr() { + let mut builder = SwapHolderBuilder::default(); + builder + .instantiate_msg + .msg + .refund_config + .party_a_refund_address = "invalid".to_string(); + builder.build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_party_b_refund_addr() { + let mut builder = SwapHolderBuilder::default(); + builder + .instantiate_msg + .msg + .refund_config + .party_b_refund_address = "invalid".to_string(); + builder.build(); +} + +#[test] +#[should_panic(expected = "Caller is not the clock, only clock can tick contracts")] +fn test_execute_tick_validates_clock() { + let mut suite = SwapHolderBuilder::default().build(); + + suite + .app + .execute_contract( + suite.admin, + suite.holder.clone(), + &valence_swap_holder::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap(); +} + +#[test] +fn test_execute_tick_instantiated_expires() { + let mut suite = SwapHolderBuilder::default().build(); + + suite.expire_lockup_config(); + suite.tick_contract(suite.holder.clone()); + + let contract_state = suite.query_contract_state(); + assert!(matches!(contract_state, ContractState::Expired {})); +} + +#[test] +#[should_panic(expected = "Insufficient funds to forward")] +fn test_execute_tick_instantiated_validates_sufficient_funds() { + let mut suite = SwapHolderBuilder::default().build(); + + suite.tick_contract(suite.holder.clone()); +} + +#[test] +fn test_execute_tick_instantiated_forwards_and_completes() { + let mut suite = SwapHolderBuilder::default().build(); + + suite.fund_contract(&coins(100000, DENOM_ATOM_ON_NTRN), suite.holder.clone()); + suite.fund_contract(&coins(100000, DENOM_LS_ATOM_ON_NTRN), suite.holder.clone()); + + suite.tick_contract(suite.holder.clone()); + + let contract_state = suite.query_contract_state(); + assert!(matches!(contract_state, ContractState::Complete {})); +} + +#[test] +fn test_execute_expired_refund_both_parties() { + let mut suite = SwapHolderBuilder::default().build(); + + suite.fund_contract(&coins(10_000, DENOM_ATOM_ON_NTRN), suite.holder.clone()); + suite.fund_contract(&coins(10_000, DENOM_LS_ATOM_ON_NTRN), suite.holder.clone()); + + suite.assert_balance(suite.holder.clone(), coin(10_000, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(suite.holder.clone(), coin(10_000, DENOM_LS_ATOM_ON_NTRN)); + + suite.expire_lockup_config(); + suite.tick_contract(suite.holder.clone()); + let contract_state = suite.query_contract_state(); + assert!(matches!(contract_state, ContractState::Expired {})); + + suite.tick_contract(suite.holder.clone()); + suite.assert_balance(suite.holder.clone(), coin(0, DENOM_ATOM_ON_NTRN)); + suite.assert_balance(suite.holder.clone(), coin(0, DENOM_LS_ATOM_ON_NTRN)); + + suite.tick_contract(suite.holder.clone()); + let contract_state = suite.query_contract_state(); + assert!(matches!(contract_state, ContractState::Expired {})); + + let refund_config = suite.query_refund_config(); + suite.assert_balance( + refund_config.party_a_refund_address, + coin(10_000, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + refund_config.party_b_refund_address, + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ); +} + +#[test] +fn test_execute_expired_refund_party_a() { + let mut suite = SwapHolderBuilder::default().build(); + + suite.fund_contract(&coins(10_000, DENOM_ATOM_ON_NTRN), suite.holder.clone()); + suite.assert_balance(suite.holder.clone(), coin(10_000, DENOM_ATOM_ON_NTRN)); + + suite.expire_lockup_config(); + suite.tick_contract(suite.holder.clone()); + let contract_state = suite.query_contract_state(); + assert!(matches!(contract_state, ContractState::Expired {})); + + suite.tick_contract(suite.holder.clone()); + suite.assert_balance(suite.holder.clone(), coin(0, DENOM_ATOM_ON_NTRN)); + + suite.tick_contract(suite.holder.clone()); + let contract_state = suite.query_contract_state(); + assert!(matches!(contract_state, ContractState::Expired {})); + + let refund_config = suite.query_refund_config(); + suite.assert_balance( + refund_config.party_a_refund_address, + coin(10_000, DENOM_ATOM_ON_NTRN), + ); +} + +#[test] +fn test_execute_expired_refund_party_b() { + let mut suite = SwapHolderBuilder::default().build(); + + suite.fund_contract(&coins(10_000, DENOM_LS_ATOM_ON_NTRN), suite.holder.clone()); + suite.assert_balance(suite.holder.clone(), coin(10_000, DENOM_LS_ATOM_ON_NTRN)); + + suite.expire_lockup_config(); + suite.tick_contract(suite.holder.clone()); + let contract_state = suite.query_contract_state(); + assert!(matches!(contract_state, ContractState::Expired {})); + + suite.tick_contract(suite.holder.clone()); + suite.assert_balance(suite.holder.clone(), coin(0, DENOM_LS_ATOM_ON_NTRN)); + + suite.tick_contract(suite.holder.clone()); + let contract_state = suite.query_contract_state(); + assert!(matches!(contract_state, ContractState::Expired {})); + + let refund_config = suite.query_refund_config(); + suite.assert_balance( + refund_config.party_b_refund_address, + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ); +} + +#[test] +fn test_execute_tick_on_complete_noop() { + let mut suite = SwapHolderBuilder::default().build(); + suite.fund_contract(&coins(100_000, DENOM_LS_ATOM_ON_NTRN), suite.holder.clone()); + suite.fund_contract(&coins(100_000, DENOM_ATOM_ON_NTRN), suite.holder.clone()); + + suite.tick_contract(suite.holder.clone()); + + let contract_state = suite.query_contract_state(); + assert!(matches!(contract_state, ContractState::Complete {})); + + suite + .tick_contract(suite.holder.clone()) + .assert_event(&Event::new("wasm").add_attribute("contract_state", "complete")); +} + +#[test] +fn test_migrate_update_config() { + let mut suite = SwapHolderBuilder::default().build(); + + let clock_address = suite.query_clock_address(); + let next_contract = suite.query_next_contract(); + let mut parties_config = suite.query_covenant_parties_config(); + parties_config.party_a.native_denom = "new_native_denom".to_string(); + + let new_covenant_terms = CovenantTerms::TokenSwap(SwapCovenantTerms { + party_a_amount: Uint128::zero(), + party_b_amount: Uint128::one(), + }); + let new_expiration = Expiration::AtHeight(192837465); + let new_refund_config = RefundConfig { + party_a_refund_address: clock_address.to_string(), + party_b_refund_address: clock_address.to_string(), + }; + let resp = suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.holder.clone(), + &valence_swap_holder::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(next_contract.to_string()), + next_contract: Some(clock_address.to_string()), + lockup_config: Some(new_expiration), + parites_config: Box::new(Some(parties_config.clone())), + covenant_terms: Some(new_covenant_terms.clone()), + refund_config: Some(new_refund_config.clone()), + }, + 4, + ) + .unwrap(); + + resp.assert_event( + &Event::new("wasm") + .add_attribute("clock_addr", next_contract.to_string()) + .add_attribute("next_contract", clock_address.to_string()) + .add_attribute("lockup_config", new_expiration.to_string()) + .add_attribute("parites_config", format!("{parties_config:?}")) + .add_attribute("covenant_terms", format!("{new_covenant_terms:?}")) + .add_attribute("refund_config", format!("{new_refund_config:?}")), + ); + + assert_eq!(suite.query_clock_address(), next_contract); + assert_eq!(suite.query_next_contract(), clock_address); + assert_eq!(suite.query_contract_state(), ContractState::Instantiated {}); + assert_eq!( + suite.query_covenant_parties_config().party_a.native_denom, + "new_native_denom" + ); + assert_eq!(suite.query_covenant_terms(), new_covenant_terms); + assert_eq!(suite.query_refund_config(), new_refund_config); +} + +#[test] +#[should_panic(expected = "lockup config is already past")] +fn test_migrate_update_config_validates_lockup_config_expiration() { + let mut suite = SwapHolderBuilder::default().build(); + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.holder.clone(), + &valence_swap_holder::msg::MigrateMsg::UpdateConfig { + clock_addr: None, + next_contract: None, + lockup_config: Some(Expiration::AtHeight(1)), + parites_config: Box::new(None), + covenant_terms: None, + refund_config: None, + }, + 4, + ) + .unwrap(); +} diff --git a/unit-tests/src/test_two_party_covenant/mod.rs b/unit-tests/src/test_two_party_covenant/mod.rs new file mode 100644 index 00000000..755bfe37 --- /dev/null +++ b/unit-tests/src/test_two_party_covenant/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod test; diff --git a/unit-tests/src/test_two_party_covenant/suite.rs b/unit-tests/src/test_two_party_covenant/suite.rs new file mode 100644 index 00000000..dce042b8 --- /dev/null +++ b/unit-tests/src/test_two_party_covenant/suite.rs @@ -0,0 +1,305 @@ +use std::collections::BTreeMap; + +use cosmwasm_std::{coin, Addr, Decimal, Uint64}; +use covenant_utils::split::SplitConfig; +use cw_multi_test::{AppResponse, Executor}; +use cw_utils::Expiration; +use valence_covenant_two_party_pol::msg::{CovenantContractCodes, Timeouts}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::two_party_covenant::TwoPartyCovenantInstantiate, + suite_builder::SuiteBuilder, + CustomApp, ADMIN, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, TWO_PARTY_COVENANT_SALT, +}; + +pub struct TwoPartyCovenantBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: TwoPartyCovenantInstantiate, +} + +impl Default for TwoPartyCovenantBuilder { + fn default() -> Self { + let mut builder = SuiteBuilder::new(); + + // init astro pools + let (pool_addr, _lp_token_addr) = builder.init_astro_pool( + astroport::factory::PairType::Stable {}, + coin(10_000_000_000_000, DENOM_ATOM_ON_NTRN), + coin(10_000_000_000_000, DENOM_LS_ATOM_ON_NTRN), + ); + + let party_a_addr = builder.get_random_addr(); + let party_b_addr = builder.get_random_addr(); + + let instantiate_msg = TwoPartyCovenantInstantiate::default( + &builder, + party_a_addr.clone(), + party_b_addr.clone(), + pool_addr.clone(), + ); + + Self { + builder, + instantiate_msg, + } + } +} + +#[allow(dead_code)] +impl TwoPartyCovenantBuilder { + pub fn with_timeouts(mut self, timeouts: Timeouts) -> Self { + self.instantiate_msg.with_timeouts(timeouts); + self + } + + pub fn with_contract_codes( + mut self, + contract_codes: valence_covenant_two_party_pol::msg::CovenantContractCodeIds, + ) -> Self { + self.instantiate_msg.with_contract_codes(contract_codes); + self + } + + pub fn with_clock_tick_max_gas(mut self, clock_tick_max_gas: Option) -> Self { + self.instantiate_msg + .with_clock_tick_max_gas(clock_tick_max_gas); + self + } + + pub fn with_lockup_config(mut self, lockup_config: Expiration) -> Self { + self.instantiate_msg.with_lockup_config(lockup_config); + self + } + + pub fn with_ragequit_config( + mut self, + ragequit_config: Option, + ) -> Self { + self.instantiate_msg.with_ragequit_config(ragequit_config); + self + } + + pub fn with_deposit_deadline(mut self, deposit_deadline: Expiration) -> Self { + self.instantiate_msg.with_deposit_deadline(deposit_deadline); + self + } + + pub fn with_party_a_config( + mut self, + party_a_config: valence_covenant_two_party_pol::msg::CovenantPartyConfig, + ) -> Self { + self.instantiate_msg.with_party_a_config(party_a_config); + self + } + + pub fn with_party_b_config( + mut self, + party_b_config: valence_covenant_two_party_pol::msg::CovenantPartyConfig, + ) -> Self { + self.instantiate_msg.with_party_b_config(party_b_config); + self + } + + pub fn with_covenant_type( + mut self, + covenant_type: valence_two_party_pol_holder::msg::CovenantType, + ) -> Self { + self.instantiate_msg.with_covenant_type(covenant_type); + self + } + + pub fn with_party_a_share(mut self, party_a_share: Decimal) -> Self { + self.instantiate_msg.with_party_a_share(party_a_share); + self + } + + pub fn with_party_b_share(mut self, party_b_share: Decimal) -> Self { + self.instantiate_msg.with_party_b_share(party_b_share); + self + } + + pub fn with_pool_price_config( + mut self, + pool_price_config: covenant_utils::PoolPriceConfig, + ) -> Self { + self.instantiate_msg + .with_pool_price_config(pool_price_config); + self + } + + pub fn with_splits(mut self, splits: BTreeMap) -> Self { + self.instantiate_msg.with_splits(splits); + self + } + + pub fn with_fallback_split(mut self, fallback_split: Option) -> Self { + self.instantiate_msg.with_fallback_split(fallback_split); + self + } + + pub fn with_emergency_committee(mut self, emergency_committee: Option) -> Self { + self.instantiate_msg + .with_emergency_committee(emergency_committee); + self + } + + pub fn with_liquid_pooler_config( + mut self, + liquid_pooler_config: valence_covenant_two_party_pol::msg::LiquidPoolerConfig, + ) -> Self { + self.instantiate_msg + .with_liquid_pooler_config(liquid_pooler_config); + self + } + + pub fn build(mut self) -> Suite { + let covenant_addr = self.builder.contract_init2( + self.builder.two_party_covenant_code_id, + TWO_PARTY_COVENANT_SALT, + &self.instantiate_msg.msg, + &[], + ); + + let clock_addr = self + .builder + .app + .wrap() + .query_wasm_smart( + covenant_addr.clone(), + &valence_covenant_two_party_pol::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let holder_addr = self + .builder + .app + .wrap() + .query_wasm_smart( + covenant_addr.clone(), + &valence_covenant_two_party_pol::msg::QueryMsg::HolderAddress {}, + ) + .unwrap(); + + Suite { + faucet: self.builder.faucet.clone(), + admin: self.builder.admin.clone(), + covenant_addr, + app: self.builder.build(), + clock_addr, + holder_addr, + } + } +} + +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + + pub covenant_addr: Addr, + pub clock_addr: Addr, + pub holder_addr: Addr, +} + +impl Suite { + pub fn migrate_update( + &mut self, + code: u64, + msg: valence_covenant_two_party_pol::msg::MigrateMsg, + ) -> AppResponse { + self.app + .migrate_contract( + Addr::unchecked(ADMIN), + self.covenant_addr.clone(), + &msg, + code, + ) + .unwrap() + } + + pub fn query_clock_address(&self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.covenant_addr.clone(), + &valence_covenant_two_party_pol::msg::QueryMsg::ClockAddress {}, + ) + .unwrap() + } + + pub fn query_holder_address(&self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.covenant_addr.clone(), + &valence_covenant_two_party_pol::msg::QueryMsg::HolderAddress {}, + ) + .unwrap() + } + + pub fn query_ibc_forwarder_address(&self, party: &str) -> Addr { + self.app + .wrap() + .query_wasm_smart::( + self.covenant_addr.clone(), + &valence_covenant_two_party_pol::msg::QueryMsg::IbcForwarderAddress { + party: party.to_string(), + }, + ) + .unwrap() + } + + pub fn query_liquid_pooler_address(&self) -> Addr { + self.app + .wrap() + .query_wasm_smart::( + self.covenant_addr.clone(), + &valence_covenant_two_party_pol::msg::QueryMsg::LiquidPoolerAddress {}, + ) + .unwrap() + } + + pub fn query_interchain_router_address(&self, party: &str) -> Addr { + self.app + .wrap() + .query_wasm_smart::( + self.covenant_addr.clone(), + &valence_covenant_two_party_pol::msg::QueryMsg::InterchainRouterAddress { + party: party.to_string(), + }, + ) + .unwrap() + } + + pub fn query_contract_codes(&self) -> CovenantContractCodes { + self.app + .wrap() + .query_wasm_smart::( + self.covenant_addr.clone(), + &valence_covenant_two_party_pol::msg::QueryMsg::ContractCodes {}, + ) + .unwrap() + } +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} diff --git a/unit-tests/src/test_two_party_covenant/test.rs b/unit-tests/src/test_two_party_covenant/test.rs new file mode 100644 index 00000000..e6caf258 --- /dev/null +++ b/unit-tests/src/test_two_party_covenant/test.rs @@ -0,0 +1,470 @@ +use std::collections::BTreeMap; + +use cosmwasm_std::{coin, to_json_binary, Addr, Event, Uint64}; + +use crate::setup::{base_suite::BaseSuiteMut, DENOM_ATOM, DENOM_ATOM_ON_NTRN, NTRN_HUB_CHANNEL}; + +use super::suite::TwoPartyCovenantBuilder; + +#[test] +fn test_instantiate_both_native_parties_astroport() { + let _suite = TwoPartyCovenantBuilder::default().build(); +} + +#[test] +fn test_instantiate_party_a_interchain() { + let builder = TwoPartyCovenantBuilder::default(); + let party_address = builder + .instantiate_msg + .msg + .party_a_config + .get_final_receiver_address(); + builder + .with_party_a_config( + valence_covenant_two_party_pol::msg::CovenantPartyConfig::Interchain( + covenant_utils::InterchainCovenantParty { + party_receiver_addr: party_address.to_string(), + party_chain_connection_id: "connection-0".to_string(), + ibc_transfer_timeout: Uint64::new(100), + party_to_host_chain_channel_id: NTRN_HUB_CHANNEL.0.to_string(), + host_to_party_chain_channel_id: NTRN_HUB_CHANNEL.1.to_string(), + remote_chain_denom: DENOM_ATOM.to_string(), + addr: party_address.to_string(), + native_denom: DENOM_ATOM_ON_NTRN.to_string(), + contribution: coin(10_000, DENOM_ATOM_ON_NTRN), + denom_to_pfm_map: BTreeMap::new(), + fallback_address: None, + }, + ), + ) + .build(); +} + +#[test] +fn test_instantiate_party_b_interchain() { + let builder = TwoPartyCovenantBuilder::default(); + let party_address = builder + .instantiate_msg + .msg + .party_b_config + .get_final_receiver_address(); + builder + .with_party_b_config( + valence_covenant_two_party_pol::msg::CovenantPartyConfig::Interchain( + covenant_utils::InterchainCovenantParty { + party_receiver_addr: party_address.to_string(), + party_chain_connection_id: "connection-0".to_string(), + ibc_transfer_timeout: Uint64::new(100), + party_to_host_chain_channel_id: NTRN_HUB_CHANNEL.0.to_string(), + host_to_party_chain_channel_id: NTRN_HUB_CHANNEL.1.to_string(), + remote_chain_denom: DENOM_ATOM.to_string(), + addr: party_address.to_string(), + native_denom: DENOM_ATOM_ON_NTRN.to_string(), + contribution: coin(10_000, DENOM_ATOM_ON_NTRN), + denom_to_pfm_map: BTreeMap::new(), + fallback_address: None, + }, + ), + ) + .build(); +} + +#[test] +fn test_instantiate_with_fallback_split() { + let builder = TwoPartyCovenantBuilder::default(); + let fallback_split = builder + .instantiate_msg + .msg + .splits + .get(&DENOM_ATOM_ON_NTRN.to_string()) + .unwrap() + .clone(); + builder.with_fallback_split(Some(fallback_split)).build(); +} + +#[test] +fn test_migrate_update_config_party_a_interchain() { + let builder = TwoPartyCovenantBuilder::default(); + let party_address = builder + .instantiate_msg + .msg + .party_a_config + .get_final_receiver_address(); + let mut suite = builder + .with_party_a_config( + valence_covenant_two_party_pol::msg::CovenantPartyConfig::Interchain( + covenant_utils::InterchainCovenantParty { + party_receiver_addr: party_address.to_string(), + party_chain_connection_id: "connection-0".to_string(), + ibc_transfer_timeout: Uint64::new(100), + party_to_host_chain_channel_id: NTRN_HUB_CHANNEL.0.to_string(), + host_to_party_chain_channel_id: NTRN_HUB_CHANNEL.1.to_string(), + remote_chain_denom: DENOM_ATOM.to_string(), + addr: party_address.to_string(), + native_denom: DENOM_ATOM_ON_NTRN.to_string(), + contribution: coin(10_000, DENOM_ATOM_ON_NTRN), + denom_to_pfm_map: BTreeMap::new(), + fallback_address: None, + }, + ), + ) + .build(); + let random_address = suite.faucet.clone(); + + let holder_migrate_msg = valence_two_party_pol_holder::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(random_address.to_string()), + next_contract: None, + emergency_committee: None, + lockup_config: None, + deposit_deadline: None, + ragequit_config: None.into(), + covenant_config: None.into(), + denom_splits: None, + fallback_split: None, + }; + let astro_liquid_pooler_migrate_msg = + valence_astroport_liquid_pooler::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(random_address.to_string()), + holder_address: None, + lp_config: None, + }; + + let liquid_pooler_migrate_msg = + valence_covenant_two_party_pol::msg::LiquidPoolerMigrateMsg::Astroport( + astro_liquid_pooler_migrate_msg.clone(), + ); + let party_a_interchain_router_migrate_msg = + valence_interchain_router::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(random_address.to_string()), + destination_config: None, + target_denoms: None, + }; + let party_a_router_migrate_msg = + valence_covenant_two_party_pol::msg::RouterMigrateMsg::Interchain( + party_a_interchain_router_migrate_msg.clone(), + ); + let party_b_native_router_migrate_msg = valence_native_router::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(random_address.to_string()), + receiver_address: None, + target_denoms: None, + }; + let party_b_router_migrate_msg = valence_covenant_two_party_pol::msg::RouterMigrateMsg::Native( + party_b_native_router_migrate_msg.clone(), + ); + let party_a_forwarder_migrate_msg = valence_ibc_forwarder::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(random_address.to_string()), + next_contract: None, + remote_chain_info: None.into(), + transfer_amount: None, + fallback_address: None, + }; + let mut contract_codes = suite.query_contract_codes(); + contract_codes.clock = 1; + let resp = suite.migrate_update( + 22, + valence_covenant_two_party_pol::msg::MigrateMsg::UpdateCovenant { + codes: Some(contract_codes.clone()), + clock: None, + holder: Some(holder_migrate_msg.clone()), + liquid_pooler: Some(liquid_pooler_migrate_msg.clone()), + party_a_router: Some(party_a_router_migrate_msg.clone()), + party_b_router: Some(party_b_router_migrate_msg.clone()), + party_a_forwarder: Some(party_a_forwarder_migrate_msg.clone()), + party_b_forwarder: None, + }, + ); + + resp.assert_event( + &Event::new("wasm") + .add_attribute( + "contract_codes_migrate", + to_json_binary(&contract_codes).unwrap().to_string(), + ) + .add_attribute( + "party_a_router_migrate", + to_json_binary(&party_a_interchain_router_migrate_msg) + .unwrap() + .to_string(), + ) + .add_attribute( + "party_b_router_migrate", + to_json_binary(&party_b_native_router_migrate_msg) + .unwrap() + .to_string(), + ) + .add_attribute( + "party_a_forwarder_migrate", + to_json_binary(&party_a_forwarder_migrate_msg) + .unwrap() + .to_string(), + ) + .add_attribute( + "holder_migrate", + to_json_binary(&holder_migrate_msg).unwrap().to_string(), + ) + .add_attribute( + "liquid_pooler_migrate", + to_json_binary(&astro_liquid_pooler_migrate_msg) + .unwrap() + .to_string(), + ), + ); + + let holder_address = suite.query_holder_address(); + let liquid_pooler_address = suite.query_liquid_pooler_address(); + let party_a_router_address = suite.query_interchain_router_address("party_a"); + let party_b_router_address = suite.query_interchain_router_address("party_b"); + let party_a_forwarder_address = suite.query_ibc_forwarder_address("party_a"); + let new_contract_codes = suite.query_contract_codes(); + + suite.tick_contract(suite.clock_addr.clone()); + + let app = suite.get_app(); + + let holder_clock_address: Addr = app + .wrap() + .query_wasm_smart( + holder_address, + &valence_two_party_pol_holder::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + assert_eq!(holder_clock_address, random_address); + + let liquid_pooler_clock_address: Addr = app + .wrap() + .query_wasm_smart( + liquid_pooler_address, + &valence_astroport_liquid_pooler::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + assert_eq!(liquid_pooler_clock_address, random_address); + + let party_a_router_clock_address: Addr = app + .wrap() + .query_wasm_smart( + party_a_router_address, + &valence_interchain_router::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + assert_eq!(party_a_router_clock_address, random_address); + + let party_b_router_clock_address: Addr = app + .wrap() + .query_wasm_smart( + party_b_router_address, + &valence_native_router::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + assert_eq!(party_b_router_clock_address, random_address); + + let party_a_forwarder_clock_address: Addr = app + .wrap() + .query_wasm_smart( + party_a_forwarder_address, + &valence_ibc_forwarder::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + assert_eq!(party_a_forwarder_clock_address, random_address); + + assert_eq!(new_contract_codes, contract_codes); +} + +#[test] +fn test_migrate_update_config_party_b_interchain() { + let builder = TwoPartyCovenantBuilder::default(); + let party_address = builder + .instantiate_msg + .msg + .party_b_config + .get_final_receiver_address(); + let mut suite = builder + .with_party_b_config( + valence_covenant_two_party_pol::msg::CovenantPartyConfig::Interchain( + covenant_utils::InterchainCovenantParty { + party_receiver_addr: party_address.to_string(), + party_chain_connection_id: "connection-0".to_string(), + ibc_transfer_timeout: Uint64::new(100), + party_to_host_chain_channel_id: NTRN_HUB_CHANNEL.0.to_string(), + host_to_party_chain_channel_id: NTRN_HUB_CHANNEL.1.to_string(), + remote_chain_denom: DENOM_ATOM.to_string(), + addr: party_address.to_string(), + native_denom: DENOM_ATOM_ON_NTRN.to_string(), + contribution: coin(10_000, DENOM_ATOM_ON_NTRN), + denom_to_pfm_map: BTreeMap::new(), + fallback_address: None, + }, + ), + ) + .build(); + let random_address = suite.faucet.clone(); + + let clock_migrate_msg = valence_clock::msg::MigrateMsg::UpdateTickMaxGas { + new_value: Uint64::new(500_000), + }; + let holder_migrate_msg = valence_two_party_pol_holder::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(random_address.to_string()), + next_contract: None, + emergency_committee: None, + lockup_config: None, + deposit_deadline: None, + ragequit_config: None.into(), + covenant_config: None.into(), + denom_splits: None, + fallback_split: None, + }; + let astro_liquid_pooler_migrate_msg = + valence_astroport_liquid_pooler::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(random_address.to_string()), + holder_address: None, + lp_config: None, + }; + + let liquid_pooler_migrate_msg = + valence_covenant_two_party_pol::msg::LiquidPoolerMigrateMsg::Astroport( + astro_liquid_pooler_migrate_msg.clone(), + ); + let party_b_interchain_router_migrate_msg = + valence_interchain_router::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(random_address.to_string()), + destination_config: None, + target_denoms: None, + }; + let party_b_router_migrate_msg = + valence_covenant_two_party_pol::msg::RouterMigrateMsg::Interchain( + party_b_interchain_router_migrate_msg.clone(), + ); + let party_a_native_router_migrate_msg = valence_native_router::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(random_address.to_string()), + receiver_address: None, + target_denoms: None, + }; + let party_a_router_migrate_msg = valence_covenant_two_party_pol::msg::RouterMigrateMsg::Native( + party_a_native_router_migrate_msg.clone(), + ); + let party_b_forwarder_migrate_msg = valence_ibc_forwarder::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(random_address.to_string()), + next_contract: None, + remote_chain_info: None.into(), + transfer_amount: None, + fallback_address: None, + }; + let mut contract_codes = suite.query_contract_codes(); + contract_codes.party_a_forwarder = 1; + + let resp = suite.migrate_update( + 22, + valence_covenant_two_party_pol::msg::MigrateMsg::UpdateCovenant { + codes: Some(contract_codes.clone()), + clock: Some(clock_migrate_msg.clone()), + holder: Some(holder_migrate_msg.clone()), + liquid_pooler: Some(liquid_pooler_migrate_msg.clone()), + party_a_router: Some(party_a_router_migrate_msg.clone()), + party_b_router: Some(party_b_router_migrate_msg.clone()), + party_b_forwarder: Some(party_b_forwarder_migrate_msg.clone()), + party_a_forwarder: None, + }, + ); + + resp.assert_event( + &Event::new("wasm") + .add_attribute( + "contract_codes_migrate", + to_json_binary(&contract_codes).unwrap().to_string(), + ) + .add_attribute( + "clock_migrate", + to_json_binary(&clock_migrate_msg).unwrap().to_string(), + ) + .add_attribute( + "party_b_router_migrate", + to_json_binary(&party_b_interchain_router_migrate_msg) + .unwrap() + .to_string(), + ) + .add_attribute( + "party_a_router_migrate", + to_json_binary(&party_a_native_router_migrate_msg) + .unwrap() + .to_string(), + ) + .add_attribute( + "party_b_forwarder_migrate", + to_json_binary(&party_b_forwarder_migrate_msg) + .unwrap() + .to_string(), + ) + .add_attribute( + "holder_migrate", + to_json_binary(&holder_migrate_msg).unwrap().to_string(), + ) + .add_attribute( + "liquid_pooler_migrate", + to_json_binary(&astro_liquid_pooler_migrate_msg) + .unwrap() + .to_string(), + ), + ); + + let clock_address = suite.query_clock_address(); + let holder_address = suite.query_holder_address(); + let liquid_pooler_address = suite.query_liquid_pooler_address(); + let party_a_router_address = suite.query_interchain_router_address("party_a"); + let party_b_router_address = suite.query_interchain_router_address("party_b"); + let party_b_forwarder_address = suite.query_ibc_forwarder_address("party_b"); + let new_contract_codes = suite.query_contract_codes(); + suite.tick_contract(suite.clock_addr.clone()); + + let app = suite.get_app(); + + let clock_max_gas: Uint64 = app + .wrap() + .query_wasm_smart(clock_address, &valence_clock::msg::QueryMsg::TickMaxGas {}) + .unwrap(); + assert_eq!(clock_max_gas, Uint64::new(500_000)); + + let holder_clock_address: Addr = app + .wrap() + .query_wasm_smart( + holder_address, + &valence_two_party_pol_holder::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + assert_eq!(holder_clock_address, random_address); + + let liquid_pooler_clock_address: Addr = app + .wrap() + .query_wasm_smart( + liquid_pooler_address, + &valence_astroport_liquid_pooler::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + assert_eq!(liquid_pooler_clock_address, random_address); + + let party_b_router_clock_address: Addr = app + .wrap() + .query_wasm_smart( + party_b_router_address, + &valence_interchain_router::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + assert_eq!(party_b_router_clock_address, random_address); + + let party_a_router_clock_address: Addr = app + .wrap() + .query_wasm_smart( + party_a_router_address, + &valence_native_router::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + assert_eq!(party_a_router_clock_address, random_address); + + let party_b_forwarder_clock_address: Addr = app + .wrap() + .query_wasm_smart( + party_b_forwarder_address, + &valence_ibc_forwarder::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + assert_eq!(party_b_forwarder_clock_address, random_address); + assert_eq!(new_contract_codes, contract_codes); +} diff --git a/unit-tests/src/test_two_party_pol_holder/mod.rs b/unit-tests/src/test_two_party_pol_holder/mod.rs new file mode 100644 index 00000000..7b881830 --- /dev/null +++ b/unit-tests/src/test_two_party_pol_holder/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod tests; diff --git a/unit-tests/src/test_two_party_pol_holder/suite.rs b/unit-tests/src/test_two_party_pol_holder/suite.rs new file mode 100644 index 00000000..da003c0f --- /dev/null +++ b/unit-tests/src/test_two_party_pol_holder/suite.rs @@ -0,0 +1,460 @@ +use std::collections::BTreeMap; + +use astroport::factory::PairType; +use cosmwasm_std::{coin, Addr, Decimal, Uint128}; +use covenant_utils::{split::SplitConfig, PoolPriceConfig, SingleSideLpLimits}; +use cw_multi_test::{AppResponse, Executor}; +use cw_utils::Expiration; +use valence_two_party_pol_holder::msg::{ContractState, DenomSplits, RagequitConfig}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + instantiates::two_party_pol_holder::TwoPartyHolderInstantiate, + suite_builder::SuiteBuilder, + CustomApp, ASTRO_LIQUID_POOLER_SALT, CLOCK_SALT, DENOM_ATOM_ON_NTRN, DENOM_LS_ATOM_ON_NTRN, + TWO_PARTY_HOLDER_SALT, +}; + +pub struct TwoPartyHolderBuilder { + pub builder: SuiteBuilder, + pub instantiate_msg: TwoPartyHolderInstantiate, +} + +impl Default for TwoPartyHolderBuilder { + fn default() -> Self { + let mut builder = SuiteBuilder::new(); + + let holder_addr = + builder.get_contract_addr(builder.two_party_holder_code_id, TWO_PARTY_HOLDER_SALT); + let clock_addr = builder.get_contract_addr(builder.clock_code_id, CLOCK_SALT); + let liquid_pooler_addr = + builder.get_contract_addr(builder.astro_pooler_code_id, ASTRO_LIQUID_POOLER_SALT); + + // init astro pools + let (pool_addr, _lp_token_addr) = builder.init_astro_pool( + astroport::factory::PairType::Stable {}, + coin(10_000_000_000_000, DENOM_ATOM_ON_NTRN), + coin(10_000_000_000_000, DENOM_LS_ATOM_ON_NTRN), + ); + + let clock_instantiate_msg = valence_clock::msg::InstantiateMsg { + tick_max_gas: None, + whitelist: vec![holder_addr.to_string(), liquid_pooler_addr.to_string()], + }; + builder.contract_init2( + builder.clock_code_id, + CLOCK_SALT, + &clock_instantiate_msg, + &[], + ); + + let liquid_pooler_instantiate_msg = valence_astroport_liquid_pooler::msg::InstantiateMsg { + pool_address: pool_addr.to_string(), + clock_address: clock_addr.to_string(), + slippage_tolerance: None, + assets: valence_astroport_liquid_pooler::msg::AssetData { + asset_a_denom: DENOM_ATOM_ON_NTRN.to_string(), + asset_b_denom: DENOM_LS_ATOM_ON_NTRN.to_string(), + }, + single_side_lp_limits: SingleSideLpLimits { + asset_a_limit: Uint128::new(100000), + asset_b_limit: Uint128::new(100000), + }, + pool_price_config: PoolPriceConfig { + expected_spot_price: Decimal::one(), + acceptable_price_spread: Decimal::from_ratio(Uint128::one(), Uint128::new(2)), + }, + pair_type: PairType::Stable {}, + holder_address: holder_addr.to_string(), + }; + + builder.contract_init2( + builder.astro_pooler_code_id, + ASTRO_LIQUID_POOLER_SALT, + &liquid_pooler_instantiate_msg, + &[], + ); + + let party_a_controller_addr = builder.get_random_addr(); + let party_b_controller_addr = builder.get_random_addr(); + + let holder_instantiate_msg = TwoPartyHolderInstantiate::default( + clock_addr.to_string(), + liquid_pooler_addr.to_string(), + party_a_controller_addr, + party_b_controller_addr, + ); + + Self { + builder, + instantiate_msg: holder_instantiate_msg, + } + } +} + +#[allow(dead_code)] +impl TwoPartyHolderBuilder { + pub fn with_clock(mut self, addr: &str) -> Self { + self.instantiate_msg.with_clock(addr); + self + } + + pub fn with_next_contract(mut self, addr: &str) -> Self { + self.instantiate_msg.with_next_contract(addr); + self + } + + pub fn with_lockup_config(mut self, config: Expiration) -> Self { + self.instantiate_msg.with_lockup_config(config); + self + } + + pub fn with_ragequit_config( + mut self, + config: valence_two_party_pol_holder::msg::RagequitConfig, + ) -> Self { + self.instantiate_msg.with_ragequit_config(config); + self + } + + pub fn with_deposit_deadline(mut self, config: Expiration) -> Self { + self.instantiate_msg.with_deposit_deadline(config); + self + } + + pub fn with_covenant_config( + mut self, + config: valence_two_party_pol_holder::msg::TwoPartyPolCovenantConfig, + ) -> Self { + self.instantiate_msg.with_covenant_config(config); + self + } + + pub fn with_splits(mut self, splits: BTreeMap) -> Self { + self.instantiate_msg.with_splits(splits); + self + } + + pub fn with_fallback_split(mut self, split: SplitConfig) -> Self { + self.instantiate_msg.with_fallback_split(split); + self + } + + pub fn with_emergency_committee(mut self, addr: &str) -> Self { + self.instantiate_msg.with_emergency_committee(addr); + self + } + + pub fn build(mut self) -> Suite { + let holder_addr = self.builder.contract_init2( + self.builder.two_party_holder_code_id, + TWO_PARTY_HOLDER_SALT, + &self.instantiate_msg.msg, + &[], + ); + + let clock_addr = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::ClockAddress {}, + ) + .unwrap(); + + let ragequit_config = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::RagequitConfig {}, + ) + .unwrap(); + + let lockup_config = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::LockupConfig {}, + ) + .unwrap(); + + let deposit_deadline = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::DepositDeadline {}, + ) + .unwrap(); + + let covenant_config = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::Config {}, + ) + .unwrap(); + + let denom_splits: DenomSplits = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::DenomSplits {}, + ) + .unwrap(); + + let next_contract = self + .builder + .app + .wrap() + .query_wasm_smart( + holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::NextContract {}, + ) + .unwrap(); + + Suite { + faucet: self.builder.faucet.clone(), + admin: self.builder.admin.clone(), + holder_addr, + clock_addr, + next_contract, + lockup_config, + ragequit_config, + deposit_deadline, + covenant_config, + splits: denom_splits.clone().explicit_splits, + fallback_split: denom_splits.clone().fallback_split, + emergency_committee_addr: None, // todo after adding emergency committee query to holder contract + app: self.builder.build(), + } + } +} + +#[allow(dead_code)] +pub struct Suite { + pub app: CustomApp, + + pub faucet: Addr, + pub admin: Addr, + + pub holder_addr: Addr, + + pub clock_addr: Addr, + pub next_contract: Addr, + pub lockup_config: Expiration, + pub ragequit_config: valence_two_party_pol_holder::msg::RagequitConfig, + pub deposit_deadline: Expiration, + pub covenant_config: valence_two_party_pol_holder::msg::TwoPartyPolCovenantConfig, + pub splits: BTreeMap, + pub fallback_split: Option, + pub emergency_committee_addr: Option, +} + +impl Suite { + pub fn expire_deposit_deadline(&mut self) { + let expiration = self.deposit_deadline; + self.get_app().update_block(|b| match expiration { + Expiration::AtHeight(h) => b.height = h, + Expiration::AtTime(t) => b.time = t, + Expiration::Never {} => (), + }); + } + + pub fn expire_lockup_config(&mut self) { + let expiration = self.lockup_config; + self.get_app().update_block(|b| match expiration { + Expiration::AtHeight(h) => b.height = h, + Expiration::AtTime(t) => b.time = t, + Expiration::Never {} => (), + }); + } + + pub fn ragequit(&mut self, sender: &str) -> AppResponse { + self.app + .execute_contract( + Addr::unchecked(sender), + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::ExecuteMsg::Ragequit {}, + &[], + ) + .unwrap() + } + + pub fn claim(&mut self, sender: &str) -> AppResponse { + self.app + .execute_contract( + Addr::unchecked(sender), + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::ExecuteMsg::Claim {}, + &[], + ) + .unwrap() + } + + pub fn distribute(&mut self, sender: &str) -> AppResponse { + self.app + .execute_contract( + Addr::unchecked(sender), + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::ExecuteMsg::Distribute {}, + &[], + ) + .unwrap() + } + + pub fn withdraw_failed(&mut self, sender: &str) -> AppResponse { + self.app + .execute_contract( + Addr::unchecked(sender), + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::ExecuteMsg::WithdrawFailed {}, + &[], + ) + .unwrap() + } + + pub fn emergency_withdraw(&mut self, sender: &str) -> AppResponse { + self.app + .execute_contract( + Addr::unchecked(sender), + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::ExecuteMsg::EmergencyWithdraw {}, + &[], + ) + .unwrap() + } + + pub fn distribute_fallback_split(&mut self, sender: &str, denoms: Vec) -> AppResponse { + self.app + .execute_contract( + Addr::unchecked(sender), + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::ExecuteMsg::DistributeFallbackSplit { denoms }, + &[], + ) + .unwrap() + } + + pub fn query_covenant_config( + &mut self, + ) -> valence_two_party_pol_holder::msg::TwoPartyPolCovenantConfig { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::Config {}, + ) + .unwrap() + } + + pub fn query_contract_state(&mut self) -> ContractState { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::ContractState {}, + ) + .unwrap() + } + + pub fn query_ragequit_config(&mut self) -> RagequitConfig { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::RagequitConfig {}, + ) + .unwrap() + } + + pub fn query_lockup_config(&mut self) -> Expiration { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::LockupConfig {}, + ) + .unwrap() + } + + pub fn query_clock_addr(&mut self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::ClockAddress {}, + ) + .unwrap() + } + + pub fn query_next_contract(&mut self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::NextContract {}, + ) + .unwrap() + } + + pub fn query_deposit_deadline(&mut self) -> Expiration { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::DepositDeadline {}, + ) + .unwrap() + } + + pub fn query_denom_splits(&mut self) -> DenomSplits { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::DenomSplits {}, + ) + .unwrap() + } + + pub fn query_emergency_committee(&mut self) -> Addr { + self.app + .wrap() + .query_wasm_smart( + self.holder_addr.clone(), + &valence_two_party_pol_holder::msg::QueryMsg::EmergencyCommittee {}, + ) + .unwrap() + } +} + +impl BaseSuiteMut for Suite { + fn get_app(&mut self) -> &mut CustomApp { + &mut self.app + } + + fn get_clock_addr(&mut self) -> Addr { + self.clock_addr.clone() + } + + fn get_faucet_addr(&mut self) -> Addr { + self.faucet.clone() + } +} + +impl BaseSuite for Suite { + fn get_app(&self) -> &CustomApp { + &self.app + } +} diff --git a/unit-tests/src/test_two_party_pol_holder/tests.rs b/unit-tests/src/test_two_party_pol_holder/tests.rs new file mode 100644 index 00000000..9275532c --- /dev/null +++ b/unit-tests/src/test_two_party_pol_holder/tests.rs @@ -0,0 +1,969 @@ +use std::{collections::BTreeMap, str::FromStr}; + +use cosmwasm_std::{coin, coins, Addr, Decimal, Event, Timestamp, Uint128}; +use covenant_utils::split::SplitConfig; +use cw_multi_test::Executor; +use cw_utils::Expiration; +use valence_two_party_pol_holder::msg::{ContractState, RagequitConfig, RagequitTerms}; + +use crate::setup::{ + base_suite::{BaseSuite, BaseSuiteMut}, + ADMIN, DENOM_ATOM_ON_NTRN, DENOM_FALLBACK, DENOM_LS_ATOM_ON_NTRN, +}; + +use super::suite::TwoPartyHolderBuilder; + +#[test] +#[should_panic] +fn test_instantiate_validates_next_contract_addr() { + TwoPartyHolderBuilder::default() + .with_next_contract("invalid") + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_clock_addr() { + TwoPartyHolderBuilder::default() + .with_clock("invalid") + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_emergency_committee_addr() { + TwoPartyHolderBuilder::default() + .with_emergency_committee("invalid") + .build(); +} + +#[test] +#[should_panic(expected = "deposit deadline is already past")] +fn test_instantiate_validates_deposit_deadline() { + TwoPartyHolderBuilder::default() + .with_deposit_deadline(Expiration::AtHeight(1)) + .build(); +} + +#[test] +#[should_panic(expected = "lockup deadline must be after the deposit deadline")] +fn test_instantiate_validates_lockup_config() { + TwoPartyHolderBuilder::default() + .with_lockup_config(Expiration::AtHeight(1)) + .build(); +} + +#[test] +#[should_panic(expected = "Party contribution cannot be zero")] +fn test_instantiate_validates_party_a_contribution_amount() { + let mut builder = TwoPartyHolderBuilder::default(); + builder + .instantiate_msg + .msg + .covenant_config + .party_a + .contribution + .amount = Uint128::zero(); + builder.build(); +} + +#[test] +#[should_panic(expected = "Party contribution cannot be zero")] +fn test_instantiate_validates_party_b_contribution_amount() { + let mut builder = TwoPartyHolderBuilder::default(); + builder + .instantiate_msg + .msg + .covenant_config + .party_b + .contribution + .amount = Uint128::zero(); + builder.build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_party_a_host_addr() { + let mut builder = TwoPartyHolderBuilder::default(); + builder + .instantiate_msg + .msg + .covenant_config + .party_a + .host_addr = "invalid".to_string(); + builder.build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_party_b_host_addr() { + let mut builder = TwoPartyHolderBuilder::default(); + builder + .instantiate_msg + .msg + .covenant_config + .party_b + .host_addr = "invalid".to_string(); + builder.build(); +} + +#[test] +#[should_panic(expected = "cannot validate deposit and lockup expirations")] +fn test_instantiate_validates_incompatible_deposit_and_lockup_expirations() { + TwoPartyHolderBuilder::default() + .with_deposit_deadline(Expiration::AtHeight(200000)) + .with_lockup_config(Expiration::AtTime(Timestamp::from_seconds(10000999990))) + .build(); +} + +#[test] +#[should_panic(expected = "lockup deadline must be after the deposit deadline")] +fn test_instantiate_validates_deposit_deadline_prior_to_lockup_expiration() { + TwoPartyHolderBuilder::default() + .with_deposit_deadline(Expiration::AtHeight(200000)) + .with_lockup_config(Expiration::AtHeight(100000)) + .build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_covenant_config_router_a_addr() { + let mut default_builder = TwoPartyHolderBuilder::default(); + default_builder + .instantiate_msg + .msg + .covenant_config + .party_a + .router = "invalid".to_string(); + default_builder.build(); +} + +#[test] +#[should_panic] +fn test_instantiate_validates_covenant_config_router_b_addr() { + let mut default_builder = TwoPartyHolderBuilder::default(); + default_builder + .instantiate_msg + .msg + .covenant_config + .party_b + .router = "invalid".to_string(); + default_builder.build(); +} + +#[test] +#[should_panic(expected = "party allocations must add up to 1.0")] +fn test_instantiate_validates_covenant_config_allocations() { + let mut default_builder = TwoPartyHolderBuilder::default(); + default_builder + .instantiate_msg + .msg + .covenant_config + .party_b + .allocation = Decimal::from_str("1.1").unwrap(); + default_builder.build(); +} + +#[test] +#[should_panic(expected = "Ragequit penalty must be in range of [0.0, 1.0)")] +fn test_instantiate_validates_ragequit_config_range() { + TwoPartyHolderBuilder::default() + .with_ragequit_config(valence_two_party_pol_holder::msg::RagequitConfig::Enabled( + RagequitTerms { + penalty: Decimal::from_str("1.1").unwrap(), + state: None, + }, + )) + .build(); +} + +#[test] +#[should_panic(expected = "Ragequit penalty exceeds party allocation")] +fn test_instantiate_validates_ragequit_config_party_allocations() { + TwoPartyHolderBuilder::default() + .with_ragequit_config(valence_two_party_pol_holder::msg::RagequitConfig::Enabled( + RagequitTerms { + penalty: Decimal::from_str("0.6").unwrap(), + state: None, + }, + )) + .build(); +} + +#[test] +// #[should_panic] TODO: enable +fn test_instantiate_validates_explicit_splits() { + let mut default_builder = TwoPartyHolderBuilder::default(); + let entry: BTreeMap = default_builder + .instantiate_msg + .msg + .splits + .iter_mut() + .map(|(denom, split)| { + let val = split.receivers.last_key_value().unwrap().0; + split + .receivers + .insert(val.clone(), Decimal::from_str("0.6").unwrap()); + (denom.to_string(), split.clone()) + }) + .collect(); + + default_builder.instantiate_msg.msg.splits = entry; + default_builder.build(); +} + +#[test] +// #[should_panic] TODO +fn test_instantiate_validates_fallback_split() { + let mut default_builder = TwoPartyHolderBuilder::default(); + let mut fallback_split = SplitConfig { + receivers: default_builder + .instantiate_msg + .msg + .splits + .last_key_value() + .unwrap() + .1 + .receivers + .clone(), + }; + fallback_split + .receivers + .insert("invalid".to_string(), Decimal::from_str("0.6").unwrap()); + default_builder.instantiate_msg.msg.fallback_split = Some(fallback_split); + default_builder.build(); +} + +#[test] +#[should_panic(expected = "Caller is not the clock, only clock can tick contracts")] +fn test_execute_tick_validates_clock() { + let mut suite = TwoPartyHolderBuilder::default().build(); + + suite + .app + .execute_contract( + suite.faucet.clone(), + suite.holder_addr.clone(), + &valence_two_party_pol_holder::msg::ExecuteMsg::Tick {}, + &[], + ) + .unwrap(); +} + +#[test] +fn test_execute_tick_expired_deposit_refunds_both_parties() { + let mut suite = TwoPartyHolderBuilder::default().build(); + suite.expire_deposit_deadline(); + + suite.fund_contract( + &[ + coin(10_000, DENOM_ATOM_ON_NTRN), + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + + suite.tick_contract(suite.holder_addr.clone()).assert_event( + &Event::new("wasm") + .add_attribute("method", "try_deposit") + .add_attribute("deposit_deadline", "expired") + .add_attribute("action", "complete"), + ); + + assert!(matches!( + suite.query_contract_state(), + ContractState::Complete {} + )); + + suite.tick_contract(suite.holder_addr.clone()).assert_event( + &Event::new("wasm") + .add_attribute("contract_state", "complete") + .add_attribute("method", "try_refund"), + ); + + suite.assert_balance( + &suite.covenant_config.party_a.router, + coin(10_000, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance( + &suite.covenant_config.party_b.router, + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ); + + assert!(matches!( + suite.query_contract_state(), + ContractState::Complete {} + )); +} + +#[test] +fn test_execute_tick_expired_deposit_refunds_party_a() { + let mut suite = TwoPartyHolderBuilder::default().build(); + suite.expire_deposit_deadline(); + + suite.fund_contract( + &[coin(10_000, DENOM_ATOM_ON_NTRN)], + suite.holder_addr.clone(), + ); + + suite.tick_contract(suite.holder_addr.clone()).assert_event( + &Event::new("wasm") + .add_attribute("method", "try_deposit") + .add_attribute("deposit_deadline", "expired") + .add_attribute("action", "complete"), + ); + + assert!(matches!( + suite.query_contract_state(), + ContractState::Complete {} + )); + + suite.tick_contract(suite.holder_addr.clone()).assert_event( + &Event::new("wasm") + .add_attribute("contract_state", "complete") + .add_attribute("method", "try_refund"), + ); + + suite.assert_balance( + &suite.covenant_config.party_a.router, + coin(10_000, DENOM_ATOM_ON_NTRN), + ); + suite.assert_balance(suite.holder_addr.clone(), coin(0, DENOM_ATOM_ON_NTRN)); + assert!(matches!( + suite.query_contract_state(), + ContractState::Complete {} + )); +} + +#[test] +fn test_execute_tick_expired_deposit_refunds_party_b() { + let mut suite = TwoPartyHolderBuilder::default().build(); + suite.app.update_block(|b| b.height = 200000); + + suite.fund_contract( + &[coin(10_000, DENOM_LS_ATOM_ON_NTRN)], + suite.holder_addr.clone(), + ); + + suite.tick_contract(suite.holder_addr.clone()).assert_event( + &Event::new("wasm") + .add_attribute("method", "try_deposit") + .add_attribute("deposit_deadline", "expired") + .add_attribute("action", "complete"), + ); + + assert!(matches!( + suite.query_contract_state(), + ContractState::Complete {} + )); + + suite.tick_contract(suite.holder_addr.clone()).assert_event( + &Event::new("wasm") + .add_attribute("contract_state", "complete") + .add_attribute("method", "try_refund"), + ); + + suite.assert_balance( + &suite.covenant_config.party_b.router, + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ); + suite.assert_balance(suite.holder_addr.clone(), coin(0, DENOM_LS_ATOM_ON_NTRN)); + assert!(matches!( + suite.query_contract_state(), + ContractState::Complete {} + )); +} + +#[test] +fn test_execute_tick_expired_deposit_completes() { + let mut suite = TwoPartyHolderBuilder::default().build(); + suite.app.update_block(|b| b.height = 200000); + suite.tick_contract(suite.holder_addr.clone()).assert_event( + &Event::new("wasm") + .add_attribute("method", "try_deposit") + .add_attribute("deposit_deadline", "expired") + .add_attribute("action", "complete"), + ); + assert!(matches!( + suite.query_contract_state(), + ContractState::Complete {} + )); + // no funds in the contract to refund, therefore noop + suite.tick_contract(suite.holder_addr.clone()).assert_event( + &Event::new("wasm") + .add_attribute("contract_state", "complete") + .add_attribute("method", "try_refund"), + ); +} + +#[test] +#[should_panic(expected = "both parties have not deposited")] +fn test_execute_tick_deposit_validates_insufficient_deposits() { + let mut suite = TwoPartyHolderBuilder::default().build(); + + suite.fund_contract( + &[ + coin(10_000, DENOM_ATOM_ON_NTRN), + coin(5_000, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); +} + +#[test] +fn test_execute_tick_expired_noop() { + let mut suite = TwoPartyHolderBuilder::default().build(); + suite.fund_contract( + &[ + coin(10_000, DENOM_ATOM_ON_NTRN), + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); + + assert_eq!(suite.query_contract_state(), ContractState::Active {}); + + suite.expire_lockup_config(); + + suite.tick_contract(suite.holder_addr.clone()); + assert_eq!(suite.query_contract_state(), ContractState::Expired {}); + + suite.tick_contract(suite.holder_addr.clone()).assert_event( + &Event::new("wasm") + .add_attribute("method", "tick") + .add_attribute("contract_state", "expired"), + ); +} + +#[test] +fn test_execute_tick_ragequit_noop() { + let mut suite = TwoPartyHolderBuilder::default() + .with_ragequit_config(valence_two_party_pol_holder::msg::RagequitConfig::Enabled( + RagequitTerms { + penalty: Decimal::from_str("0.05").unwrap(), + state: None, + }, + )) + .build(); + suite.fund_contract( + &[ + coin(10_000, DENOM_ATOM_ON_NTRN), + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); + suite.tick_contract(suite.next_contract.clone()); + + assert_eq!(suite.query_contract_state(), ContractState::Active {}); + + suite.expire_deposit_deadline(); + suite.ragequit(&suite.covenant_config.party_a.host_addr.clone()); + + assert_eq!(suite.query_contract_state(), ContractState::Ragequit {}); + + suite.tick_contract(suite.holder_addr.clone()).assert_event( + &Event::new("wasm") + .add_attribute("method", "tick") + .add_attribute("contract_state", "ragequit"), + ); +} + +#[test] +#[should_panic(expected = "ragequit is disabled")] +fn test_execute_ragequit_validates_ragequit_config() { + let mut suite = TwoPartyHolderBuilder::default().build(); + suite.fund_contract( + &[ + coin(10_000, DENOM_ATOM_ON_NTRN), + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); + suite.tick_contract(suite.next_contract.clone()); + + assert_eq!(suite.query_contract_state(), ContractState::Active {}); + + suite.expire_deposit_deadline(); + suite.ragequit(&suite.covenant_config.party_a.host_addr.clone()); +} + +#[test] +#[should_panic(expected = "covenant is not in active state")] +fn test_execute_ragequit_validates_active_state() { + let mut suite = TwoPartyHolderBuilder::default() + .with_ragequit_config(valence_two_party_pol_holder::msg::RagequitConfig::Enabled( + RagequitTerms { + penalty: Decimal::from_str("0.05").unwrap(), + state: None, + }, + )) + .build(); + + suite.ragequit(&suite.covenant_config.party_a.host_addr.clone()); +} + +#[test] +fn test_execute_ragequit_validates_withdraw_started() { + // todo +} + +#[test] +#[should_panic(expected = "covenant is active but expired; tick to proceed")] +fn test_execute_ragequit_validates_lockup_config_expiration() { + let mut suite = TwoPartyHolderBuilder::default() + .with_ragequit_config(valence_two_party_pol_holder::msg::RagequitConfig::Enabled( + RagequitTerms { + penalty: Decimal::from_str("0.05").unwrap(), + state: None, + }, + )) + .build(); + suite.fund_contract( + &[ + coin(10_000, DENOM_ATOM_ON_NTRN), + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); + suite.tick_contract(suite.next_contract.clone()); + + assert_eq!(suite.query_contract_state(), ContractState::Active {}); + + suite.expire_lockup_config(); + suite.ragequit(&suite.covenant_config.party_a.host_addr.clone()); +} + +#[test] +#[should_panic(expected = "unauthorized")] +fn test_execute_ragequit_validates_sender() { + let mut suite = TwoPartyHolderBuilder::default() + .with_ragequit_config(valence_two_party_pol_holder::msg::RagequitConfig::Enabled( + RagequitTerms { + penalty: Decimal::from_str("0.05").unwrap(), + state: None, + }, + )) + .build(); + suite.fund_contract( + &[ + coin(10_000, DENOM_ATOM_ON_NTRN), + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); + suite.tick_contract(suite.next_contract.clone()); + + assert_eq!(suite.query_contract_state(), ContractState::Active {}); + + suite.ragequit(suite.faucet.clone().as_ref()); +} + +#[test] +#[should_panic(expected = "unauthorized")] +fn test_execute_claim_unauthorized() { + let mut suite = TwoPartyHolderBuilder::default() + .with_ragequit_config(valence_two_party_pol_holder::msg::RagequitConfig::Enabled( + RagequitTerms { + penalty: Decimal::from_str("0.05").unwrap(), + state: None, + }, + )) + .build(); + let clock = suite.clock_addr.clone(); + + suite.claim(clock.as_str()); +} + +#[test] +#[should_panic(expected = "Claimer already claimed his share")] +fn test_execute_claim_with_null_allocation() { + let mut suite = TwoPartyHolderBuilder::default() + .with_ragequit_config(valence_two_party_pol_holder::msg::RagequitConfig::Enabled( + RagequitTerms { + penalty: Decimal::from_str("0.05").unwrap(), + state: None, + }, + )) + .build(); + suite.fund_contract( + &[ + coin(10_000, DENOM_ATOM_ON_NTRN), + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); + suite.tick_contract(suite.next_contract.clone()); + + assert_eq!(suite.query_contract_state(), ContractState::Active {}); + + suite.expire_deposit_deadline(); + suite.ragequit(&suite.covenant_config.party_a.host_addr.clone()); + + suite.claim(&suite.covenant_config.party_a.host_addr.clone()); +} + +#[test] +#[should_panic(expected = "contract needs to be in ragequit or expired state in order to claim")] +fn test_execute_claim_validates_claim_state() { + let mut suite = TwoPartyHolderBuilder::default().build(); + suite.fund_contract( + &[ + coin(10_000, DENOM_ATOM_ON_NTRN), + coin(10_000, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); + + suite.claim(&suite.covenant_config.party_a.host_addr.clone()); +} + +#[test] +fn test_execute_claim_happy() { + let mut suite = TwoPartyHolderBuilder::default().build(); + suite.fund_contract( + &[ + coin(10_001, DENOM_ATOM_ON_NTRN), + coin(10_001, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); + suite.tick_contract(suite.next_contract.clone()); + + suite.expire_lockup_config(); + suite.tick_contract(suite.holder_addr.clone()); + + suite.claim(&suite.covenant_config.party_a.host_addr.clone()); + + let ls_atom_bal = suite.query_balance( + &Addr::unchecked(suite.covenant_config.party_a.host_addr.to_string()), + DENOM_LS_ATOM_ON_NTRN, + ); + let atom_bal = suite.query_balance( + &Addr::unchecked(suite.covenant_config.party_a.host_addr.to_string()), + DENOM_ATOM_ON_NTRN, + ); + assert_eq!(ls_atom_bal, coin(5_000, DENOM_LS_ATOM_ON_NTRN)); + assert_eq!(atom_bal, coin(5_000, DENOM_ATOM_ON_NTRN)); + assert_eq!( + suite.query_covenant_config().party_a.allocation, + Decimal::zero() + ); +} + +#[test] +#[should_panic(expected = "unauthorized")] +fn test_execute_emergency_withdraw_validates_committee_address() { + let builder = TwoPartyHolderBuilder::default(); + let clock = builder.instantiate_msg.msg.clock_address.clone(); + let mut suite = builder.with_emergency_committee(clock.as_str()).build(); + + suite.fund_contract( + &[ + coin(10_001, DENOM_ATOM_ON_NTRN), + coin(10_001, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); + suite.tick_contract(suite.next_contract.clone()); + + let sender = suite.faucet.clone(); + + suite.emergency_withdraw(sender.as_str()); +} + +#[test] +fn test_execute_emergency_withdraw_happy() { + let builder = TwoPartyHolderBuilder::default(); + let clock = builder.instantiate_msg.msg.clock_address.clone(); + let mut suite = builder.with_emergency_committee(clock.as_str()).build(); + + suite.fund_contract( + &[ + coin(10_001, DENOM_ATOM_ON_NTRN), + coin(10_001, DENOM_LS_ATOM_ON_NTRN), + ], + suite.holder_addr.clone(), + ); + suite.tick_contract(suite.holder_addr.clone()); + suite.tick_contract(suite.next_contract.clone()); + + suite.emergency_withdraw(clock.as_str()); + + let party_a = Addr::unchecked(suite.covenant_config.party_a.router.to_string()); + let party_b = Addr::unchecked(suite.covenant_config.party_b.router.to_string()); + + let party_a_atom_bal = suite.query_balance(&party_a, DENOM_ATOM_ON_NTRN).amount; + let party_b_atom_bal = suite.query_balance(&party_b, DENOM_ATOM_ON_NTRN).amount; + let party_a_ls_atom_bal = suite.query_balance(&party_a, DENOM_LS_ATOM_ON_NTRN).amount; + let party_b_ls_atom_bal = suite.query_balance(&party_b, DENOM_LS_ATOM_ON_NTRN).amount; + + assert_eq!(5000, party_a_atom_bal.u128()); + assert_eq!(5000, party_b_atom_bal.u128()); + assert_eq!(5000, party_a_ls_atom_bal.u128()); + assert_eq!(5000, party_b_ls_atom_bal.u128()); + assert!(matches!( + suite.query_contract_state(), + ContractState::Complete {} + )); +} + +#[test] +#[should_panic(expected = "unauthorized to distribute explicitly defined denom")] +fn test_distribute_fallback_validates_denoms() { + let mut suite = TwoPartyHolderBuilder::default().build(); + let sender = suite.clock_addr.to_string(); + suite.distribute_fallback_split(&sender, vec![DENOM_ATOM_ON_NTRN.to_string()]); +} + +#[test] +fn test_distribute_fallback_with_no_fallback_split_noop_happy() { + let mut suite = TwoPartyHolderBuilder::default().build(); + let sender = suite.clock_addr.to_string(); + + suite.fund_contract(&coins(1_000_000, DENOM_FALLBACK), suite.holder_addr.clone()); + + suite.distribute_fallback_split(&sender, vec![DENOM_FALLBACK.to_string()]); + + suite.assert_balance( + suite.holder_addr.to_string(), + coin(1_000_000, DENOM_FALLBACK), + ); +} + +#[test] +fn test_distribute_fallback_happy() { + let mut builder = TwoPartyHolderBuilder::default(); + let router_a_addr = builder + .instantiate_msg + .msg + .covenant_config + .party_a + .router + .to_string(); + let router_b_addr = builder + .instantiate_msg + .msg + .covenant_config + .party_b + .router + .to_string(); + builder.instantiate_msg.msg.fallback_split = Some(SplitConfig { + receivers: vec![ + (router_a_addr.to_string(), Decimal::percent(50)), + (router_b_addr.to_string(), Decimal::percent(50)), + ] + .into_iter() + .collect(), + }); + + let mut suite = builder.build(); + + let sender = suite.clock_addr.to_string(); + + suite.fund_contract(&coins(1_000_000, DENOM_FALLBACK), suite.holder_addr.clone()); + + suite.distribute_fallback_split(&sender, vec![DENOM_FALLBACK.to_string()]); + + suite.assert_balance(suite.holder_addr.to_string(), coin(0, DENOM_FALLBACK)); + suite.assert_balance(router_a_addr, coin(500_000, DENOM_FALLBACK)); + suite.assert_balance(router_b_addr, coin(500_000, DENOM_FALLBACK)); +} + +#[test] +fn test_migrate_update_config() { + let mut suite = TwoPartyHolderBuilder::default().build(); + + let clock = suite.query_clock_addr(); + let next_contract = suite.query_next_contract(); + let mut covenant_config = suite.query_covenant_config(); + let denom_splits = suite.query_denom_splits(); + covenant_config.party_a.contribution.amount = Uint128::one(); + let random_split = denom_splits + .explicit_splits + .get(DENOM_ATOM_ON_NTRN) + .unwrap(); + + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.holder_addr.clone(), + &valence_two_party_pol_holder::msg::MigrateMsg::UpdateConfig { + clock_addr: Some(next_contract.to_string()), + next_contract: Some(clock.to_string()), + emergency_committee: Some(clock.to_string()), + lockup_config: Some(Expiration::AtHeight(543210)), + deposit_deadline: Some(Expiration::AtHeight(543210)), + ragequit_config: Box::new(Some(RagequitConfig::Enabled(RagequitTerms { + penalty: Decimal::from_str("0.123").unwrap(), + state: None, + }))), + covenant_config: Box::new(Some(covenant_config)), + denom_splits: Some(denom_splits.explicit_splits.clone()), + fallback_split: Some(random_split.clone()), + }, + 13, + ) + .unwrap(); + + let new_clock = suite.query_clock_addr(); + let new_next_contract = suite.query_next_contract(); + let ragequit_config = suite.query_ragequit_config(); + let lockup_config = suite.query_lockup_config(); + let deposit_deadline = suite.query_deposit_deadline(); + let covenant_config = suite.query_covenant_config(); + let denom_splits = suite.query_denom_splits(); + let emergency_committee = suite.query_emergency_committee(); + + assert_eq!(random_split, &denom_splits.fallback_split.unwrap()); + assert_eq!(Uint128::one(), covenant_config.party_a.contribution.amount); + assert_eq!(Expiration::AtHeight(543210), deposit_deadline); + assert_eq!(Expiration::AtHeight(543210), lockup_config); + assert_eq!( + RagequitConfig::Enabled(RagequitTerms { + penalty: Decimal::from_str("0.123").unwrap(), + state: None, + }), + ragequit_config + ); + assert_eq!(next_contract, new_clock); + assert_eq!(clock, new_next_contract); + assert_eq!(clock, emergency_committee); +} + +#[test] +#[should_panic] +fn test_migrate_update_config_invalid_fallback_split() { + let mut suite = TwoPartyHolderBuilder::default().build(); + + let denom_splits = suite.query_denom_splits(); + let mut receivers = denom_splits + .explicit_splits + .get(DENOM_ATOM_ON_NTRN) + .unwrap() + .clone() + .receivers; + let mut receiver = receivers.pop_first().unwrap(); + receiver.1 = Decimal::zero(); + receivers.insert(receiver.0, receiver.1); + + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.holder_addr.clone(), + &valence_two_party_pol_holder::msg::MigrateMsg::UpdateConfig { + clock_addr: None, + next_contract: None, + emergency_committee: None, + lockup_config: None, + deposit_deadline: None, + ragequit_config: Box::new(None), + covenant_config: Box::new(None), + denom_splits: None, + fallback_split: Some(SplitConfig { receivers }), + }, + 13, + ) + .unwrap(); +} + +#[test] +#[should_panic] +fn test_migrate_update_config_invalid_explicit_splits() { + let mut suite = TwoPartyHolderBuilder::default().build(); + + let mut explicit_splits = suite.query_denom_splits().explicit_splits.clone(); + + let mut receivers = explicit_splits + .get(DENOM_ATOM_ON_NTRN) + .unwrap() + .clone() + .receivers; + + let mut receiver = receivers.pop_first().unwrap(); + receiver.1 = Decimal::zero(); + receivers.insert(receiver.0, receiver.1); + + explicit_splits.insert(DENOM_ATOM_ON_NTRN.to_string(), SplitConfig { receivers }); + + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.holder_addr.clone(), + &valence_two_party_pol_holder::msg::MigrateMsg::UpdateConfig { + clock_addr: None, + next_contract: None, + emergency_committee: None, + lockup_config: None, + deposit_deadline: None, + ragequit_config: Box::new(None), + covenant_config: Box::new(None), + denom_splits: Some(explicit_splits), + fallback_split: None, + }, + 13, + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "lockup config is already past")] +fn test_migrate_update_config_validates_lockup_config_expiration() { + let mut suite = TwoPartyHolderBuilder::default().build(); + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.holder_addr.clone(), + &valence_two_party_pol_holder::msg::MigrateMsg::UpdateConfig { + clock_addr: None, + next_contract: None, + emergency_committee: None, + lockup_config: Some(Expiration::AtHeight(1)), + deposit_deadline: None, + ragequit_config: Box::new(None), + covenant_config: Box::new(None), + denom_splits: None, + fallback_split: None, + }, + 13, + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "deposit deadline is already past")] +fn test_migrate_update_config_validates_deposit_deadline_expiration() { + let mut suite = TwoPartyHolderBuilder::default().build(); + suite + .app + .migrate_contract( + Addr::unchecked(ADMIN), + suite.holder_addr.clone(), + &valence_two_party_pol_holder::msg::MigrateMsg::UpdateConfig { + clock_addr: None, + next_contract: None, + emergency_committee: None, + lockup_config: None, + deposit_deadline: Some(Expiration::AtHeight(1)), + ragequit_config: Box::new(None), + covenant_config: Box::new(None), + denom_splits: None, + fallback_split: None, + }, + 13, + ) + .unwrap(); +} diff --git a/v1/covenant/.cargo/config b/v1/covenant/.cargo/config new file mode 100644 index 00000000..5f6aa466 --- /dev/null +++ b/v1/covenant/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" diff --git a/contracts/covenant/Cargo.toml b/v1/covenant/Cargo.toml similarity index 95% rename from contracts/covenant/Cargo.toml rename to v1/covenant/Cargo.toml index 090e9f39..2f95164f 100644 --- a/contracts/covenant/Cargo.toml +++ b/v1/covenant/Cargo.toml @@ -2,7 +2,7 @@ name = "covenant-covenant" edition = { workspace = true } authors = ["benskey bekauz@protonmail.com"] -description = "Covenant contract" +description = "Covenant module" license = { workspace = true } repository = { workspace = true } version = { workspace = true } @@ -14,7 +14,7 @@ exclude = [ [lib] -crate-type = ["cdylib", "rlib"] +# crate-type = ["cdylib", "rlib"] [features] diff --git a/v1/covenant/LICENSE b/v1/covenant/LICENSE new file mode 100644 index 00000000..0bdef96b --- /dev/null +++ b/v1/covenant/LICENSE @@ -0,0 +1,29 @@ +Copyright 2023 Timewave + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/contracts/covenant/README.md b/v1/covenant/README.md similarity index 96% rename from contracts/covenant/README.md rename to v1/covenant/README.md index 5d3f0bdf..6a4e420f 100644 --- a/contracts/covenant/README.md +++ b/v1/covenant/README.md @@ -9,7 +9,6 @@ clock -> holder -> lp -> ls -> depositor 1. instantiate clock, holder 1. instantiate lp with clock and holder addresses 1. instantiate ls with clock and lper addresses -1. tick ls, instantiate ICA 1. instantiate depositor with stride ICA, lper, and clock addresses 1. tick depositor to instantiate gaia ICA 1. tick depositor to LS on stride diff --git a/contracts/covenant/examples/schema.rs b/v1/covenant/examples/schema.rs similarity index 100% rename from contracts/covenant/examples/schema.rs rename to v1/covenant/examples/schema.rs diff --git a/contracts/covenant/src/contract.rs b/v1/covenant/src/contract.rs similarity index 98% rename from contracts/covenant/src/contract.rs rename to v1/covenant/src/contract.rs index 610d36f5..e5074202 100644 --- a/contracts/covenant/src/contract.rs +++ b/v1/covenant/src/contract.rs @@ -49,7 +49,7 @@ pub fn instantiate( let pool_addr = deps.api.addr_validate(&msg.pool_address)?; POOL_ADDRESS.save(deps.storage, &pool_addr)?; - // store all the preset fields for each contract instantiation + // store all the preset fields for each module instantiation PRESET_CLOCK_FIELDS.save(deps.storage, &msg.preset_clock_fields)?; PRESET_LP_FIELDS.save(deps.storage, &msg.preset_lp_fields)?; PRESET_LS_FIELDS.save(deps.storage, &msg.preset_ls_fields)?; @@ -60,7 +60,7 @@ pub fn instantiate( TIMEOUTS.save(deps.storage, &msg.timeouts)?; IBC_FEE.save(deps.storage, &msg.preset_ibc_fee.to_ibc_fee())?; - // we start the contract instantiation chain with the clock + // we start the module instantiation chain with the clock let clock_instantiate_tx = CosmosMsg::Wasm(WasmMsg::Instantiate { admin: Some(env.contract.address.to_string()), code_id: msg.preset_clock_fields.clock_code, @@ -142,7 +142,7 @@ pub fn handle_holder_reply(deps: DepsMut, env: Env, msg: Reply) -> Result Result Result = Item::new("ls_code"); -/// contract code for the liquid pooler contract +/// contract code for the liquid pooler module pub const LP_CODE: Item = Item::new("lp_code"); -/// contract code for the depositor contract +/// contract code for the depositor module pub const DEPOSITOR_CODE: Item = Item::new("depositor_code"); -/// contract code for the clock contract +/// contract code for the clock module pub const CLOCK_CODE: Item = Item::new("clock_code"); -/// contract code for the holder contract +/// contract code for the holder module pub const HOLDER_CODE: Item = Item::new("holder_code"); /// address of the liquidity pool we wish to provide liquidity to @@ -21,36 +21,36 @@ pub const POOL_ADDRESS: Item = Item::new("pool_address"); /// ibc fee for the relayers pub const IBC_FEE: Item = Item::new("ibc_fee"); /// ibc transfer and ica timeouts that will be passed down to -/// contracts dealing with ICA +/// modules dealing with ICA pub const TIMEOUTS: Item = Item::new("timeouts"); -/// fields related to the liquid staker contract known prior to covenant instatiation. +/// fields related to the liquid staker module known prior to covenant instatiation. /// remaining fields are filled and converted to an InstantiateMsg during the /// instantiation chain. pub const PRESET_LS_FIELDS: Item = Item::new("preset_ls_fields"); -/// fields related to the liquid pooler contract known prior to covenant instatiation. +/// fields related to the liquid pooler module known prior to covenant instatiation. /// remaining fields are filled and converted to an InstantiateMsg during the /// instantiation chain. pub const PRESET_LP_FIELDS: Item = Item::new("preset_lp_fields"); -/// fields related to the depositor contract known prior to covenant instatiation. +/// fields related to the depositor module known prior to covenant instatiation. /// remaining fields are filled and converted to an InstantiateMsg during the /// instantiation chain. pub const PRESET_DEPOSITOR_FIELDS: Item = Item::new("preset_depositor_fields"); -/// fields related to the clock contract known prior to covenant instatiation. +/// fields related to the clock module known prior to covenant instatiation. pub const PRESET_CLOCK_FIELDS: Item = Item::new("preset_clock_fields"); -/// fields related to the holder contract known prior to covenant instatiation. +/// fields related to the holder module known prior to covenant instatiation. pub const PRESET_HOLDER_FIELDS: Item = Item::new("preset_holder_fields"); -/// address of the clock contract associated with this covenant +/// address of the clock module associated with this covenant pub const COVENANT_CLOCK_ADDR: Item = Item::new("covenant_clock_addr"); -/// address of the liquid pooler contract associated with this covenant +/// address of the liquid pooler module associated with this covenant pub const COVENANT_LP_ADDR: Item = Item::new("covenant_lp_addr"); -/// address of the liquid staker contract associated with this covenant +/// address of the liquid staker module associated with this covenant pub const COVENANT_LS_ADDR: Item = Item::new("covenant_ls_addr"); -/// address of the depositor contract associated with this covenant +/// address of the depositor module associated with this covenant pub const COVENANT_DEPOSITOR_ADDR: Item = Item::new("covenant_depositor_addr"); -/// address of the holder contract associated with this covenant +/// address of the holder module associated with this covenant pub const COVENANT_HOLDER_ADDR: Item = Item::new("covenant_holder_addr"); diff --git a/contracts/covenant/src/suite_test/mod.rs b/v1/covenant/src/suite_test/mod.rs similarity index 100% rename from contracts/covenant/src/suite_test/mod.rs rename to v1/covenant/src/suite_test/mod.rs diff --git a/contracts/covenant/src/suite_test/suite.rs b/v1/covenant/src/suite_test/suite.rs similarity index 97% rename from contracts/covenant/src/suite_test/suite.rs rename to v1/covenant/src/suite_test/suite.rs index 2d24c2ee..5fbacc71 100644 --- a/contracts/covenant/src/suite_test/suite.rs +++ b/v1/covenant/src/suite_test/suite.rs @@ -64,6 +64,7 @@ impl Default for SuiteBuilder { ls_denom: "stuatom".to_string(), stride_neutron_ibc_transfer_channel_id: TODO.to_string(), neutron_stride_ibc_connection_id: TODO.to_string(), + autopilot_format: "{{\"autopilot\": {{\"receiver\": \"{st_ica}\",\"stakeibc\": {{\"stride_address\": \"{st_ica}\",\"action\": \"LiquidStake\"}}}}}}".to_string(), }, preset_depositor_fields: covenant_depositor::msg::PresetDepositorFields { gaia_neutron_ibc_transfer_channel_id: TODO.to_string(), diff --git a/contracts/covenant/src/suite_test/tests.rs b/v1/covenant/src/suite_test/tests.rs similarity index 100% rename from contracts/covenant/src/suite_test/tests.rs rename to v1/covenant/src/suite_test/tests.rs diff --git a/contracts/covenant/src/suite_test/unit_tests.rs b/v1/covenant/src/suite_test/unit_tests.rs similarity index 97% rename from contracts/covenant/src/suite_test/unit_tests.rs rename to v1/covenant/src/suite_test/unit_tests.rs index e7617812..9c1e5a87 100644 --- a/contracts/covenant/src/suite_test/unit_tests.rs +++ b/v1/covenant/src/suite_test/unit_tests.rs @@ -33,6 +33,7 @@ fn get_init_msg() -> InstantiateMsg { ls_denom: "stuatom".to_string(), stride_neutron_ibc_transfer_channel_id: TODO.to_string(), neutron_stride_ibc_connection_id: TODO.to_string(), + autopilot_format: "{{\"autopilot\": {{\"receiver\": \"{st_ica}\",\"stakeibc\": {{\"stride_address\": \"{st_ica}\",\"action\": \"LiquidStake\"}}}}}}".to_string(), }, preset_depositor_fields: covenant_depositor::msg::PresetDepositorFields { gaia_neutron_ibc_transfer_channel_id: TODO.to_string(), @@ -341,8 +342,8 @@ fn test_init() { ) .unwrap(); assert_eq!( - from_binary::(&depositor_addr).unwrap().as_ref(), - "contract_depositor" + from_binary::>(&depositor_addr).unwrap(), + Some("contract_depositor".to_string()) ); let lp_addr = query( diff --git a/v1/depositor/.cargo/config b/v1/depositor/.cargo/config new file mode 100644 index 00000000..5f6aa466 --- /dev/null +++ b/v1/depositor/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" diff --git a/contracts/depositor/Cargo.toml b/v1/depositor/Cargo.toml similarity index 91% rename from contracts/depositor/Cargo.toml rename to v1/depositor/Cargo.toml index 25d1bfb2..629b94aa 100644 --- a/contracts/depositor/Cargo.toml +++ b/v1/depositor/Cargo.toml @@ -2,7 +2,7 @@ name = "covenant-depositor" edition = { workspace = true } authors = ["benskey bekauz@protonmail.com"] -description = "Depositor contract for stride covenant" +description = "Depositor module for stride covenant" license = { workspace = true } repository = { workspace = true } version = { workspace = true } @@ -22,9 +22,10 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -covenant-clock-derive = { workspace = true} +covenant-macros = { workspace = true} covenant-ls = { workspace = true, features=["library"] } covenant-clock = { workspace = true, features=["library"]} +covenant-utils = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } diff --git a/v1/depositor/LICENSE b/v1/depositor/LICENSE new file mode 100644 index 00000000..0bdef96b --- /dev/null +++ b/v1/depositor/LICENSE @@ -0,0 +1,29 @@ +Copyright 2023 Timewave + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/contracts/depositor/README.md b/v1/depositor/README.md similarity index 84% rename from contracts/depositor/README.md rename to v1/depositor/README.md index 9c9678f4..fda8e5dd 100644 --- a/contracts/depositor/README.md +++ b/v1/depositor/README.md @@ -1,10 +1,10 @@ # Depositor -This contract is the depositor in stride-covenant system. +This module is the depositor in stride-covenant system. It is responsible for the following tasks: 1. Instantiating an ICA on gaia 1. Transfering Atom from gaia ICA to itself via IBC -1. Splitting the available Atom in half and funding the LP and LS contracts +1. Splitting the available Atom in half and funding the LP and LS modules The contract determines the next actions to take purely based on its own state. After receiving a `tick: {}` message from the clock, it attempts to advance the state. diff --git a/v1/depositor/examples/schema.rs b/v1/depositor/examples/schema.rs new file mode 100644 index 00000000..b2645920 --- /dev/null +++ b/v1/depositor/examples/schema.rs @@ -0,0 +1,22 @@ +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; +use covenant_depositor::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use neutron_sdk::bindings::query::QueryInterchainAccountAddressResponse; +use neutron_sdk::sudo::msg::SudoMsg; +use std::env::current_dir; +use std::fs::create_dir_all; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(SudoMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema( + &schema_for!(QueryInterchainAccountAddressResponse), + &out_dir, + ); +} diff --git a/v1/depositor/schema/execute_msg.json b/v1/depositor/schema/execute_msg.json new file mode 100644 index 00000000..43ee8861 --- /dev/null +++ b/v1/depositor/schema/execute_msg.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "tick" + ], + "properties": { + "tick": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "received" + ], + "properties": { + "received": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/v1/depositor/schema/instantiate_msg.json b/v1/depositor/schema/instantiate_msg.json new file mode 100644 index 00000000..1dc0445e --- /dev/null +++ b/v1/depositor/schema/instantiate_msg.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "atom_receiver", + "clock_address", + "st_atom_receiver" + ], + "properties": { + "atom_receiver": { + "$ref": "#/definitions/WeightedReceiver" + }, + "clock_address": { + "$ref": "#/definitions/Addr" + }, + "st_atom_receiver": { + "$ref": "#/definitions/WeightedReceiver" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "WeightedReceiver": { + "type": "object", + "required": [ + "address", + "amount" + ], + "properties": { + "address": { + "type": "string" + }, + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + } +} diff --git a/v1/depositor/schema/query_interchain_account_address_response.json b/v1/depositor/schema/query_interchain_account_address_response.json new file mode 100644 index 00000000..acc40bcc --- /dev/null +++ b/v1/depositor/schema/query_interchain_account_address_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryInterchainAccountAddressResponse", + "type": "object", + "required": [ + "interchain_account_address" + ], + "properties": { + "interchain_account_address": { + "description": "*interchain_account_address** is a interchain account address on the remote chain", + "type": "string" + } + } +} diff --git a/v1/depositor/schema/query_msg.json b/v1/depositor/schema/query_msg.json new file mode 100644 index 00000000..17f885d0 --- /dev/null +++ b/v1/depositor/schema/query_msg.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "st_atom_receiver" + ], + "properties": { + "st_atom_receiver": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "atom_receiver" + ], + "properties": { + "atom_receiver": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "clock_address" + ], + "properties": { + "clock_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/v1/depositor/schema/sudo_msg.json b/v1/depositor/schema/sudo_msg.json new file mode 100644 index 00000000..92582ab6 --- /dev/null +++ b/v1/depositor/schema/sudo_msg.json @@ -0,0 +1,269 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SudoMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "response" + ], + "properties": { + "response": { + "type": "object", + "required": [ + "data", + "request" + ], + "properties": { + "data": { + "$ref": "#/definitions/Binary" + }, + "request": { + "$ref": "#/definitions/RequestPacket" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "object", + "required": [ + "details", + "request" + ], + "properties": { + "details": { + "type": "string" + }, + "request": { + "$ref": "#/definitions/RequestPacket" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "timeout" + ], + "properties": { + "timeout": { + "type": "object", + "required": [ + "request" + ], + "properties": { + "request": { + "$ref": "#/definitions/RequestPacket" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "open_ack" + ], + "properties": { + "open_ack": { + "type": "object", + "required": [ + "channel_id", + "counterparty_channel_id", + "counterparty_version", + "port_id" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "counterparty_channel_id": { + "type": "string" + }, + "counterparty_version": { + "type": "string" + }, + "port_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "tx_query_result" + ], + "properties": { + "tx_query_result": { + "type": "object", + "required": [ + "data", + "height", + "query_id" + ], + "properties": { + "data": { + "$ref": "#/definitions/Binary" + }, + "height": { + "$ref": "#/definitions/Height" + }, + "query_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "kv_query_result" + ], + "properties": { + "kv_query_result": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Height": { + "type": "object", + "properties": { + "revision_height": { + "description": "*height** is a height of remote chain", + "default": 0, + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "description": "the revision that the client is currently on", + "default": 0, + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "RequestPacket": { + "type": "object", + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "destination_channel": { + "type": [ + "string", + "null" + ] + }, + "destination_port": { + "type": [ + "string", + "null" + ] + }, + "sequence": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "source_channel": { + "type": [ + "string", + "null" + ] + }, + "source_port": { + "type": [ + "string", + "null" + ] + }, + "timeout_height": { + "anyOf": [ + { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + { + "type": "null" + } + ] + }, + "timeout_timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + } +} diff --git a/contracts/depositor/src/contract.rs b/v1/depositor/src/contract.rs similarity index 73% rename from contracts/depositor/src/contract.rs rename to v1/depositor/src/contract.rs index 0c8843a8..89766925 100644 --- a/contracts/depositor/src/contract.rs +++ b/v1/depositor/src/contract.rs @@ -15,10 +15,12 @@ use prost::Message; use crate::{ msg::{ - ContractState, ExecuteMsg, IbcConfig, InstantiateMsg, MigrateMsg, OpenAckVersion, QueryMsg, + ContractState, ExecuteMsg, InstantiateMsg, MigrateMsg, OpenAckVersion, QueryMsg, SudoPayload, }, - state::{IBC_CONFIG, NEUTRON_ATOM_IBC_DENOM, PENDING_NATIVE_TRANSFER_TIMEOUT}, + state::{ + IBC_TRANSFER_TIMEOUT, ICA_TIMEOUT, NEUTRON_ATOM_IBC_DENOM, PENDING_NATIVE_TRANSFER_TIMEOUT, + }, }; use neutron_sdk::{ bindings::{ @@ -33,8 +35,9 @@ use neutron_sdk::{ use crate::state::{ read_errors_from_queue, read_reply_payload, read_sudo_payload, save_reply_payload, save_sudo_payload, ACKNOWLEDGEMENT_RESULTS, AUTOPILOT_FORMAT, CLOCK_ADDRESS, CONTRACT_STATE, - GAIA_NEUTRON_IBC_TRANSFER_CHANNEL_ID, GAIA_STRIDE_IBC_TRANSFER_CHANNEL_ID, INTERCHAIN_ACCOUNTS, - LS_ADDRESS, NATIVE_ATOM_RECEIVER, NEUTRON_GAIA_CONNECTION_ID, STRIDE_ATOM_RECEIVER, + GAIA_NEUTRON_IBC_TRANSFER_CHANNEL_ID, GAIA_STRIDE_IBC_TRANSFER_CHANNEL_ID, IBC_FEE, + INTERCHAIN_ACCOUNTS, LS_ADDRESS, NATIVE_ATOM_RECEIVER, NEUTRON_GAIA_CONNECTION_ID, + STRIDE_ATOM_RECEIVER, }; type QueryDeps<'a> = Deps<'a, NeutronQuery>; @@ -61,13 +64,13 @@ pub fn instantiate( // contract begins at Instantiated state CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; - // validate and store other contract addresses + // validate and store other module addresses let clock_addr = deps.api.addr_validate(&msg.clock_address)?; let ls_addr = deps.api.addr_validate(&msg.ls_address)?; CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; LS_ADDRESS.save(deps.storage, &ls_addr)?; - // store information needed to forward funds to next contracts + // store information needed to forward funds to next modules STRIDE_ATOM_RECEIVER.save(deps.storage, &msg.st_atom_receiver)?; NATIVE_ATOM_RECEIVER.save(deps.storage, &msg.atom_receiver)?; NEUTRON_ATOM_IBC_DENOM.save(deps.storage, &msg.neutron_atom_ibc_denom)?; @@ -83,14 +86,9 @@ pub fn instantiate( AUTOPILOT_FORMAT.save(deps.storage, &msg.autopilot_format)?; // ibc fees and timeouts - IBC_CONFIG.save( - deps.storage, - &IbcConfig { - ibc_fee: msg.ibc_fee, - ibc_transfer_timeout: msg.ibc_transfer_timeout, - ica_timeout: msg.ica_timeout, - }, - )?; + IBC_FEE.save(deps.storage, &msg.ibc_fee)?; + ICA_TIMEOUT.save(deps.storage, &msg.ica_timeout)?; + IBC_TRANSFER_TIMEOUT.save(deps.storage, &msg.ibc_transfer_timeout)?; Ok(Response::default() .add_attribute("method", "depositor_instantiate") @@ -134,19 +132,13 @@ fn try_tick(deps: ExecuteDeps, env: Env, info: MessageInfo) -> NeutronResult try_register_gaia_ica(deps, env), - ContractState::ICACreated => { + ContractState::IcaCreated => { let ica_address = get_ica(deps.as_ref(), &env, INTERCHAIN_ACCOUNT_ID); match ica_address { - Ok((address, controller_conn_id)) => { - try_send_native_token(env, deps, address, controller_conn_id) - } - Err(_) => { - // reverting state to instantiated to recreate the ICA - CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; - Ok(Response::default() - .add_attribute("method", "try_tick") - .add_attribute("ica_status", "not_created")) - } + Ok((_, _)) => try_send_native_token(env, deps), + Err(_) => Ok(Response::default() + .add_attribute("method", "try_tick") + .add_attribute("ica_status", "not_created")), } } ContractState::VerifyNativeToken => try_verify_native_token(env, deps), @@ -158,7 +150,7 @@ fn try_tick(deps: ExecuteDeps, env: Env, info: MessageInfo) -> NeutronResult NeutronResult { +fn to_proto_msg_transfer(msg: impl Message) -> NeutronResult { // Serialize the Transfer message let mut buf = Vec::with_capacity(msg.encoded_len()); if let Err(e) = msg.encode(&mut buf) { @@ -171,74 +163,79 @@ pub fn to_proto_msg_transfer(msg: impl Message) -> NeutronResult { }) } -/// attempts to forward the funds to LP contract -fn try_send_native_token( - env: Env, - mut deps: ExecuteDeps, - address: String, - controller_conn_id: String, -) -> NeutronResult> { +/// attempts to forward the funds to LP module +fn try_send_native_token(env: Env, mut deps: ExecuteDeps) -> NeutronResult> { let port_id = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + let interchain_account = INTERCHAIN_ACCOUNTS.load(deps.storage, port_id.clone())?; + + match interchain_account { + Some((address, controller_conn_id)) => { + let ibc_transfer_timeout = IBC_TRANSFER_TIMEOUT.load(deps.storage)?; + let ica_timeout = ICA_TIMEOUT.load(deps.storage)?; + let source_channel = GAIA_NEUTRON_IBC_TRANSFER_CHANNEL_ID.load(deps.storage)?; + let receiver = NATIVE_ATOM_RECEIVER.load(deps.storage)?; + let fee = IBC_FEE.load(deps.storage)?; + + let coin = Coin { + denom: ATOM_DENOM.to_string(), + amount: receiver.amount.to_string(), + }; - let ibc_config = IBC_CONFIG.load(deps.storage)?; - let source_channel = GAIA_NEUTRON_IBC_TRANSFER_CHANNEL_ID.load(deps.storage)?; - let receiver = NATIVE_ATOM_RECEIVER.load(deps.storage)?; - - let coin = Coin { - denom: ATOM_DENOM.to_string(), - amount: receiver.amount.to_string(), - }; - - // we define the gaia->neutron timeout to be equal to: - // current block + ICA timeout + ibc transfer timeout. - // this assumes the worst possible time of delivery for the ICA message - // which wraps the underlying MsgTransfer. - let msg_transfer_timeout = env - .block - .time - // we take the wrapping ICA tx timeout into account and assume the worst - .plus_seconds(ibc_config.ica_timeout.u64()) - // and then add the preset ibc transfer timeout - .plus_seconds(ibc_config.ibc_transfer_timeout.u64()); - - // we store that timeout for later validation of pending transfers - PENDING_NATIVE_TRANSFER_TIMEOUT.save(deps.storage, &msg_transfer_timeout)?; - - // transfer message that will send funds from the ICA on gaia to our LP contract - let lper_msg = MsgTransfer { - source_port: "transfer".to_string(), - source_channel, - token: Some(coin), - sender: address, - receiver: receiver.address, - timeout_height: None, - timeout_timestamp: msg_transfer_timeout.nanos(), - }; - - let lp_protobuf = to_proto_msg_transfer(lper_msg)?; - - // tx to our ICA that wraps the transfer message defined above - let submit_msg = NeutronMsg::submit_tx( - controller_conn_id, - INTERCHAIN_ACCOUNT_ID.to_string(), - vec![lp_protobuf], - "".to_string(), - ibc_config.ica_timeout.u64(), - ibc_config.ibc_fee, - ); - - let submsg = msg_with_sudo_callback( - deps.branch(), - submit_msg, - SudoPayload { - port_id, - message: "try_send_native_token".to_string(), - }, - )?; + // we define the gaia->neutron timeout to be equal to: + // current block + ICA timeout + ibc transfer timeout. + // this assumes the worst possible time of delivery for the ICA message + // which wraps the underlying MsgTransfer. + let msg_transfer_timeout = env + .block + .time + // we take the wrapping ICA tx timeout into account and assume the worst + .plus_seconds(ica_timeout.u64()) + // and then add the preset ibc transfer timeout + .plus_seconds(ibc_transfer_timeout.u64()); + + // we store that timeout for later validation of pending transfers + PENDING_NATIVE_TRANSFER_TIMEOUT.save(deps.storage, &msg_transfer_timeout)?; + + // transfer message that will send funds from the ICA on gaia to our LP module + let lper_msg = MsgTransfer { + source_port: "transfer".to_string(), + source_channel, + token: Some(coin), + sender: address, + receiver: receiver.address, + timeout_height: None, + timeout_timestamp: msg_transfer_timeout.nanos(), + }; - Ok(Response::default() - .add_attribute("method", "try_send_native_token") - .add_submessage(submsg)) + let lp_protobuf = to_proto_msg_transfer(lper_msg)?; + + // tx to our ICA that wraps the transfer message defined above + let submit_msg = NeutronMsg::submit_tx( + controller_conn_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + vec![lp_protobuf], + "".to_string(), + ica_timeout.u64(), + fee, + ); + + let submsg = msg_with_sudo_callback( + deps.branch(), + submit_msg, + SudoPayload { + port_id, + message: "try_send_native_token".to_string(), + }, + )?; + + Ok(Response::default() + .add_attribute("method", "try_send_native_token") + .add_submessage(submsg)) + } + None => Ok(Response::default() + .add_attribute("method", "try_send_native_token") + .add_attribute("error", "no_ica_found")), + } } fn query_lper_balance(deps: QueryDeps, lper: &str) -> StdResult { @@ -247,18 +244,14 @@ fn query_lper_balance(deps: QueryDeps, lper: &str) -> StdResult NeutronResult> { - // first we load the LS contract address which is responsible for creating +fn try_send_ls_token(env: Env, mut deps: ExecuteDeps) -> NeutronResult> { + // first we load the LS module address which is responsible for creating // an ICA on stride so that we can query for that ICA address let ls_address = LS_ADDRESS.load(deps.storage)?; - let stride_ica_query: Option = deps - .querier - .query_wasm_smart(ls_address, &covenant_ls::msg::QueryMsg::StrideICA {})?; + let stride_ica_query: Option = deps.querier.query_wasm_smart( + ls_address, + &covenant_utils::neutron_ica::QueryMsg::DepositAddress {}, + )?; let stride_ica_addr = match stride_ica_query { Some(addr) => addr, None => return Err(NeutronError::Std(StdError::not_found("no LS ica found"))), @@ -271,66 +264,76 @@ fn try_send_ls_token( })?; let port_id = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); - let gaia_stride_channel = GAIA_STRIDE_IBC_TRANSFER_CHANNEL_ID.load(deps.storage)?; - let ibc_config = IBC_CONFIG.load(deps.storage)?; - let stride_coin = Coin { - denom: ATOM_DENOM.to_string(), - amount: stride_receiver.amount.to_string(), - }; + let interchain_account = INTERCHAIN_ACCOUNTS.load(deps.storage, port_id.clone())?; + + match interchain_account { + Some((address, controller_conn_id)) => { + let gaia_stride_channel = GAIA_STRIDE_IBC_TRANSFER_CHANNEL_ID.load(deps.storage)?; + let ibc_transfer_timeout = IBC_TRANSFER_TIMEOUT.load(deps.storage)?; + let ica_timeout = ICA_TIMEOUT.load(deps.storage)?; + let fee = IBC_FEE.load(deps.storage)?; + + let stride_coin = Coin { + denom: ATOM_DENOM.to_string(), + amount: stride_receiver.amount.to_string(), + }; - // we load the stored format string of autopilot and replace the dynamic fields - // with our queried data - let autopilot_receiver = AUTOPILOT_FORMAT - .load(deps.storage)? - .replace("{st_ica}", &stride_ica_addr); - - // we define the gaia->neutron timeout to be equal to: - // current block + ICA timeout + ibc transfer timeout. - // this assumes the worst possible time of delivery for the ICA message - // which wraps the underlying MsgTransfer. - let msg_transfer_timeout = env - .block - .time - // we take the wrapping ICA tx timeout into account and assume the worst - .plus_seconds(ibc_config.ica_timeout.u64()) - // and then add the preset ibc transfer timeout - .plus_seconds(ibc_config.ibc_transfer_timeout.u64()); - - // transfer message that will send funds from the ICA on gaia to our ICA on stride - let stride_msg = MsgTransfer { - source_port: "transfer".to_string(), - source_channel: gaia_stride_channel, - token: Some(stride_coin), - sender: address, - receiver: autopilot_receiver, - timeout_height: None, - timeout_timestamp: msg_transfer_timeout.nanos(), - }; + // we load the stored format string of autopilot and replace the dynamic fields + // with our queried data + let autopilot_receiver = AUTOPILOT_FORMAT + .load(deps.storage)? + .replace("{st_ica}", &stride_ica_addr); + + // we define the gaia->neutron timeout to be equal to: + // current block + ICA timeout + ibc transfer timeout. + // this assumes the worst possible time of delivery for the ICA message + // which wraps the underlying MsgTransfer. + let msg_transfer_timeout = env + .block + .time + // we take the wrapping ICA tx timeout into account and assume the worst + .plus_seconds(ica_timeout.u64()) + // and then add the preset ibc transfer timeout + .plus_seconds(ibc_transfer_timeout.u64()); + + // transfer message that will send funds from the ICA on gaia to our ICA on stride + let stride_msg = MsgTransfer { + source_port: "transfer".to_string(), + source_channel: gaia_stride_channel, + token: Some(stride_coin), + sender: address, + receiver: autopilot_receiver, + timeout_height: None, + timeout_timestamp: msg_transfer_timeout.nanos(), + }; - let stride_protobuf = to_proto_msg_transfer(stride_msg)?; - - // tx to our ICA that wraps the transfer message defined above - let submit_msg = NeutronMsg::submit_tx( - controller_conn_id, - INTERCHAIN_ACCOUNT_ID.to_string(), - vec![stride_protobuf], - "".to_string(), - ibc_config.ica_timeout.u64(), - ibc_config.ibc_fee, - ); - - Ok(msg_with_sudo_callback( - deps.branch(), - submit_msg, - SudoPayload { - port_id, - message: "try_send_st_token".to_string(), - }, - )?) + let stride_protobuf = to_proto_msg_transfer(stride_msg)?; + + // tx to our ICA that wraps the transfer message defined above + let submit_msg = NeutronMsg::submit_tx( + controller_conn_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + vec![stride_protobuf], + "".to_string(), + ica_timeout.u64(), + fee, + ); + + Ok(msg_with_sudo_callback( + deps.branch(), + submit_msg, + SudoPayload { + port_id, + message: "try_send_st_token".to_string(), + }, + )?) + } + None => Err(NeutronError::Std(StdError::not_found("no ica found"))), + } } -/// attempts to advance the state machine past the sending native tokens to LP contract phase. -/// it queries the balances of the LP contract and validates the amount there against our +/// attempts to advance the state machine past the sending native tokens to LP module phase. +/// it queries the balances of the LP module and validates the amount there against our /// expectations. if funds are not yet there, the timeout of previous transfer is validated, /// taking an extra 5 minutes buffer into account. /// if timeout is not yet due, and the funds did not arrive, we wait. @@ -338,8 +341,9 @@ fn try_verify_native_token(env: Env, deps: ExecuteDeps) -> NeutronResult= receiver.amount { - // if funds have arrived on LP contract, we advance the state + // if funds have arrived on LP module, we advance the state CONTRACT_STATE.save(deps.storage, &ContractState::VerifyLp)?; // nullifying any previous timeouts PENDING_NATIVE_TRANSFER_TIMEOUT.remove(deps.storage); @@ -353,10 +357,10 @@ fn try_verify_native_token(env: Env, deps: ExecuteDeps) -> NeutronResult= active_timeout.plus_minutes(5).nanos() { - // funds are still not on the LP contract and the msgTransfer timeout is due + // funds are still not on the LP module and the msgTransfer timeout is due // we can safely retry sending the funds again by reverting the state - // to ICACreated - CONTRACT_STATE.save(deps.storage, &ContractState::ICACreated)?; + // to IcaCreated + CONTRACT_STATE.save(deps.storage, &ContractState::IcaCreated)?; PENDING_NATIVE_TRANSFER_TIMEOUT.remove(deps.storage); return Ok(Response::default() .add_attribute("method", "try_verify_native_token") @@ -365,7 +369,7 @@ fn try_verify_native_token(env: Env, deps: ExecuteDeps) -> NeutronResult NeutronResult NeutronResult { - try_send_ls_token(env, deps, address, controller_conn_id) - } - Err(_) => { - // reverting state to instantiated to recreate the ICA - CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; - return Ok(Response::default() - .add_attribute("method", "try_verify_lp") - .add_attribute("ica_status", "not_created")); - } - }?; + let ls_token_msg = try_send_ls_token(env, deps)?; Ok(Response::default() .add_submessage(ls_token_msg) @@ -421,7 +412,7 @@ fn try_verify_lp(env: Env, deps: ExecuteDeps) -> NeutronResult NeutronResult> { let gaia_acc_id = INTERCHAIN_ACCOUNT_ID.to_string(); let connection_id = NEUTRON_GAIA_CONNECTION_ID.load(deps.storage)?; - let register = NeutronMsg::register_interchain_account(connection_id, gaia_acc_id); + let register = NeutronMsg::register_interchain_account(connection_id, gaia_acc_id.clone()); let key = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); // we are saving empty data here because we handle response of registering ICA in sudo_open_ack method @@ -574,8 +565,9 @@ pub fn migrate(deps: ExecuteDeps, _env: Env, msg: MigrateMsg) -> StdResult { let mut resp = Response::default().add_attribute("method", "update_config"); @@ -622,29 +614,26 @@ pub fn migrate(deps: ExecuteDeps, _env: Env, msg: MigrateMsg) -> StdResult Std if let Some(payload) = payload { if payload.message == *"try_send_native_token" { // we advance the state machine to validation phase where we will query the balances of - // LP contract to confirm that funds have arrived + // LP module to confirm that funds have arrived CONTRACT_STATE.save(deps.storage, &ContractState::VerifyNativeToken)?; } } diff --git a/contracts/depositor/src/error.rs b/v1/depositor/src/error.rs similarity index 100% rename from contracts/depositor/src/error.rs rename to v1/depositor/src/error.rs diff --git a/contracts/depositor/src/lib.rs b/v1/depositor/src/lib.rs similarity index 100% rename from contracts/depositor/src/lib.rs rename to v1/depositor/src/lib.rs diff --git a/contracts/depositor/src/msg.rs b/v1/depositor/src/msg.rs similarity index 95% rename from contracts/depositor/src/msg.rs rename to v1/depositor/src/msg.rs index 3d232a31..9d50dc25 100644 --- a/contracts/depositor/src/msg.rs +++ b/v1/depositor/src/msg.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Binary, Uint128, Uint64}; -use covenant_clock_derive::clocked; +use covenant_macros::clocked; use neutron_sdk::bindings::{msg::IbcFee, query::QueryInterchainAccountAddressResponse}; #[cw_serde] @@ -23,7 +23,7 @@ pub struct InstantiateMsg { /// This is used to ibc transfer uatom on gaia /// to the ica on stride pub gaia_stride_ibc_transfer_channel_id: String, - /// address of the liquid staker contract that will be used + /// address of the liquid staker module that will be used /// to query for the ICA address on stride pub ls_address: String, /// json formatted string meant to be used for one-click @@ -165,8 +165,9 @@ pub enum MigrateMsg { gaia_stride_ibc_transfer_channel_id: Option, ls_address: Option, autopilot_format: Option, - ibc_config: Option, - neutron_atom_ibc_denom: Option, + ibc_fee: Option, + ibc_transfer_timeout: Option, + ica_timeout: Option, }, UpdateCodeId { data: Option, @@ -188,7 +189,7 @@ pub enum ContractState { /// Contract was instantiated, create ica Instantiated, /// ICA was created, send native token to lper - ICACreated, + IcaCreated, /// Verify native token was sent to lper and send ls msg VerifyNativeToken, /// Verify the lper entered a position, if not try to resend ls msg again @@ -215,10 +216,3 @@ pub enum AcknowledgementResult { /// Timeout - Got timeout acknowledgement in sudo with payload message in it Timeout(String), } - -#[cw_serde] -pub struct IbcConfig { - pub ibc_fee: IbcFee, - pub ibc_transfer_timeout: Uint64, - pub ica_timeout: Uint64, -} diff --git a/contracts/depositor/src/state.rs b/v1/depositor/src/state.rs similarity index 81% rename from contracts/depositor/src/state.rs rename to v1/depositor/src/state.rs index 843db181..960fb84c 100644 --- a/contracts/depositor/src/state.rs +++ b/v1/depositor/src/state.rs @@ -1,15 +1,18 @@ -use crate::msg::{AcknowledgementResult, ContractState, IbcConfig, SudoPayload, WeightedReceiver}; -use cosmwasm_std::{from_binary, to_vec, Addr, Binary, Order, StdResult, Storage, Timestamp}; +use crate::msg::{AcknowledgementResult, ContractState, SudoPayload, WeightedReceiver}; +use cosmwasm_std::{ + from_binary, to_vec, Addr, Binary, Order, StdResult, Storage, Timestamp, Uint64, +}; use cw_storage_plus::{Item, Map}; +use neutron_sdk::bindings::msg::IbcFee; /// tracks the current state of state machine pub const CONTRACT_STATE: Item = Item::new("contract_state"); -/// clock contract address to verify the sender of incoming ticks +/// clock module address to verify the sender of incoming ticks pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); -/// liquid staker contract address to query the stride ICA address to autopilot to +/// liquid staker module address to query the stride ICA address to autopilot to pub const LS_ADDRESS: Item = Item::new("ls_address"); -/// liquid pooler contract address to forward the native tokens to +/// liquid pooler module address to forward the native tokens to pub const LP_ADDRESS: Item = Item::new("lp_address"); /// formatting of stride autopilot message. @@ -31,8 +34,12 @@ pub const GAIA_STRIDE_IBC_TRANSFER_CHANNEL_ID: Item = Item::new("gs_ibc_ /// connection id of gaia on neutron pub const NEUTRON_GAIA_CONNECTION_ID: Item = Item::new("ng_conn_id"); -/// config containing ibc fee, ica timeout, and ibc transfer -pub const IBC_CONFIG: Item = Item::new("ibc_config"); +/// timeout in seconds for inner ibc MsgTransfer +pub const IBC_TRANSFER_TIMEOUT: Item = Item::new("ibc_transfer_timeout"); +/// time in seconds for ICA SubmitTX messages from neutron +pub const ICA_TIMEOUT: Item = Item::new("ica_timeout"); +/// neutron IbcFee for relayers +pub const IBC_FEE: Item = Item::new("ibc_fee"); /// interchain accounts storage in form of (port_id) -> (address, controller_connection_id) pub const INTERCHAIN_ACCOUNTS: Map> = diff --git a/contracts/depositor/src/suite_test/mod.rs b/v1/depositor/src/suite_test/mod.rs similarity index 100% rename from contracts/depositor/src/suite_test/mod.rs rename to v1/depositor/src/suite_test/mod.rs diff --git a/contracts/depositor/src/suite_test/suite.rs b/v1/depositor/src/suite_test/suite.rs similarity index 100% rename from contracts/depositor/src/suite_test/suite.rs rename to v1/depositor/src/suite_test/suite.rs diff --git a/contracts/depositor/src/suite_test/tests.rs b/v1/depositor/src/suite_test/tests.rs similarity index 100% rename from contracts/depositor/src/suite_test/tests.rs rename to v1/depositor/src/suite_test/tests.rs diff --git a/contracts/depositor/src/suite_test/unit_helpers.rs b/v1/depositor/src/suite_test/unit_helpers.rs similarity index 89% rename from contracts/depositor/src/suite_test/unit_helpers.rs rename to v1/depositor/src/suite_test/unit_helpers.rs index ffa010ae..a48d697a 100644 --- a/contracts/depositor/src/suite_test/unit_helpers.rs +++ b/v1/depositor/src/suite_test/unit_helpers.rs @@ -4,8 +4,8 @@ use cosmos_sdk_proto::{cosmos::base::v1beta1::Coin, ibc::applications::transfer: use cosmwasm_std::{ from_binary, testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}, - to_binary, Addr, ContractResult, DepsMut, Empty, MemoryStorage, MessageInfo, OwnedDeps, Reply, - Response, StdError, StdResult, SystemResult, Uint128, Uint64, WasmQuery, + to_binary, Addr, ContractResult, DepsMut, MemoryStorage, MessageInfo, OwnedDeps, Response, + SystemResult, Uint128, Uint64, WasmQuery, }; use neutron_sdk::{ bindings::{ @@ -19,7 +19,7 @@ use neutron_sdk::{ use prost::Message; use crate::{ - contract::{execute, instantiate, reply, sudo, INTERCHAIN_ACCOUNT_ID}, + contract::{execute, instantiate, INTERCHAIN_ACCOUNT_ID}, msg::{ ContractState, ExecuteMsg, InstantiateMsg, OpenAckVersion, PresetDepositorFields, WeightedReceiverAmount, @@ -56,7 +56,7 @@ pub fn wasm_handler(wasm_query: &WasmQuery) -> SystemResult match contract_addr.as_ref() { LS_ADDR => match from_binary::(msg).unwrap() { - covenant_ls::msg::QueryMsg::StrideICA {} => SystemResult::Ok(ContractResult::Ok( + covenant_ls::msg::QueryMsg::IcaAddress {} => SystemResult::Ok(ContractResult::Ok( to_binary(&Addr::unchecked("some_ica_addr")).unwrap(), )), _ => unimplemented!(), @@ -166,15 +166,3 @@ pub fn verify_state(deps: &Owned, contract_state: ContractState) { let state = CONTRACT_STATE.load(deps.as_ref().storage).unwrap(); assert_eq!(state, contract_state) } - -pub fn sudo_execute( - deps: DepsMut, - msg: SudoMsg, -) -> Result, StdError> { - // let info = mock_info(CLOCK_ADDR, &[]); - sudo(deps, mock_env(), msg) -} - -pub fn reply_execute(deps: DepsMut, msg: Reply) -> StdResult { - reply(deps, mock_env(), msg) -} diff --git a/contracts/depositor/src/suite_test/unit_test.rs b/v1/depositor/src/suite_test/unit_test.rs similarity index 54% rename from contracts/depositor/src/suite_test/unit_test.rs rename to v1/depositor/src/suite_test/unit_test.rs index c8a80783..9aecf33a 100644 --- a/contracts/depositor/src/suite_test/unit_test.rs +++ b/v1/depositor/src/suite_test/unit_test.rs @@ -1,21 +1,12 @@ -use cosmwasm_std::{ - coins, testing::mock_env, to_binary, Binary, CosmosMsg, Reply, SubMsgResponse, WasmMsg, -}; -use neutron_sdk::{ - bindings::{ - msg::{MsgSubmitTxResponse, NeutronMsg}, - types::ProtobufAny, - }, - sudo::msg::{RequestPacket, SudoMsg}, -}; +use cosmwasm_std::{coins, testing::mock_env, to_binary, Binary, CosmosMsg, WasmMsg}; +use neutron_sdk::bindings::{msg::NeutronMsg, types::ProtobufAny}; use crate::{ - contract::{sudo, to_proto_msg_transfer, INTERCHAIN_ACCOUNT_ID, SUDO_PAYLOAD_REPLY_ID}, + contract::{sudo, INTERCHAIN_ACCOUNT_ID}, msg::ContractState, suite_test::unit_helpers::{ get_default_ibc_fee, get_default_init_msg, get_default_msg_transfer, - get_default_sudo_open_ack, reply_execute, sudo_execute, to_proto, CLOCK_ADDR, LP_ADDR, - NATIVE_ATOM_DENOM, + get_default_sudo_open_ack, to_proto, CLOCK_ADDR, LP_ADDR, NATIVE_ATOM_DENOM, }, }; @@ -53,7 +44,7 @@ fn test_tick_1() { let (mut deps, _) = do_instantiate(); deps = do_tick_1(deps); - verify_state(&deps, ContractState::ICACreated); + verify_state(&deps, ContractState::IcaCreated); } // This test should send the native token to the lper and set state to VerifyNativeToken @@ -72,40 +63,6 @@ fn test_tick_2() { lp_transfer_msg.receiver = LP_ADDR.to_string(); // env.block.time + ibc transfer timeout (100sec) lp_transfer_msg.timeout_timestamp = 1571797619879305533; - reply_execute( - deps.as_mut(), - Reply { - id: SUDO_PAYLOAD_REPLY_ID, - result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - to_binary(&MsgSubmitTxResponse { - sequence_id: 1, - channel: "channel-0".to_string(), - }) - .unwrap(), - ), - }), - }, - ) - .unwrap(); - sudo_execute( - deps.as_mut(), - SudoMsg::Response { - request: RequestPacket { - sequence: Some(1), - source_port: None, - source_channel: Some("channel-0".to_string()), - destination_port: None, - destination_channel: None, - data: None, - timeout_height: None, - timeout_timestamp: None, - }, - data: to_binary(&1).unwrap(), - }, - ) - .unwrap(); verify_state(&deps, ContractState::VerifyNativeToken); assert_eq!(tick_res.messages.len(), 1); assert_eq!( @@ -133,67 +90,36 @@ fn test_tick_3() { deps = do_tick_1(deps); //tick 2 do_tick(deps.as_mut()).unwrap(); - reply_execute( - deps.as_mut(), - Reply { - id: SUDO_PAYLOAD_REPLY_ID, - result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - to_binary(&MsgSubmitTxResponse { - sequence_id: 1, - channel: "channel-0".to_string(), - }) - .unwrap(), - ), - }), - }, - ) - .unwrap(); - sudo_execute( - deps.as_mut(), - SudoMsg::Response { - request: RequestPacket { - sequence: Some(1), - source_port: None, - source_channel: Some("channel-0".to_string()), - destination_port: None, - destination_channel: None, - data: None, - timeout_height: None, - timeout_timestamp: None, - }, - data: to_binary(&1).unwrap(), - }, - ) - .unwrap(); + + // Balance is incorrect, so it should still be in VerifyNativeToken state + do_tick(deps.as_mut()).unwrap(); verify_state(&deps, ContractState::VerifyNativeToken); // Increase balance of lper deps.querier .update_balance(LP_ADDR, coins(1000, NATIVE_ATOM_DENOM)); - // Balance is incorrect, so it should still be in VerifyNativeToken state - do_tick(deps.as_mut()).unwrap(); - // do another tick let tick_res = do_tick(deps.as_mut()).unwrap(); let mut stride_transfer_msg = get_default_msg_transfer(); - stride_transfer_msg.timeout_timestamp = 1571797619879305533; - - let proto_msg = to_proto_msg_transfer(stride_transfer_msg).unwrap(); + stride_transfer_msg.timeout_timestamp = 1571797519879305533; let (_, default_version) = get_default_sudo_open_ack(); - let msg = CosmosMsg::Custom(NeutronMsg::SubmitTx { - connection_id: default_version.controller_connection_id, - interchain_account_id: INTERCHAIN_ACCOUNT_ID.to_string(), - msgs: vec![proto_msg], - memo: "".to_string(), - timeout: 100, - fee: get_default_ibc_fee(), - }); verify_state(&deps, ContractState::VerifyLp); - assert_eq!(tick_res.messages[0].msg, msg,); + assert_eq!( + tick_res.messages[0].msg, + CosmosMsg::Custom(NeutronMsg::SubmitTx { + connection_id: default_version.controller_connection_id, + interchain_account_id: INTERCHAIN_ACCOUNT_ID.to_string(), + msgs: vec![ProtobufAny { + type_url: "/ibc.applications.transfer.v1.MsgTransfer".to_string(), + value: Binary::from(to_proto(stride_transfer_msg)), + }], + memo: "".to_string(), + timeout: 100, + fee: get_default_ibc_fee() + }) + ); } // This tests the final tick, where the balance of the lper is reduced to 0 @@ -203,43 +129,9 @@ fn test_tick_4() { // tick 1 deps = do_tick_1(deps); - //tick 2 do_tick(deps.as_mut()).unwrap(); - reply_execute( - deps.as_mut(), - Reply { - id: SUDO_PAYLOAD_REPLY_ID, - result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: Some( - to_binary(&MsgSubmitTxResponse { - sequence_id: 1, - channel: "channel-0".to_string(), - }) - .unwrap(), - ), - }), - }, - ) - .unwrap(); - sudo_execute( - deps.as_mut(), - SudoMsg::Response { - request: RequestPacket { - sequence: Some(1), - source_port: None, - source_channel: Some("channel-0".to_string()), - destination_port: None, - destination_channel: None, - data: None, - timeout_height: None, - timeout_timestamp: None, - }, - data: to_binary(&1).unwrap(), - }, - ) - .unwrap(); + // Increase balance of lper deps.querier .update_balance(LP_ADDR, coins(1000, NATIVE_ATOM_DENOM)); @@ -250,7 +142,7 @@ fn test_tick_4() { let tick_res = do_tick(deps.as_mut()).unwrap(); let mut stride_transfer_msg = get_default_msg_transfer(); - stride_transfer_msg.timeout_timestamp = 1571797619879305533; + stride_transfer_msg.timeout_timestamp = 1571797519879305533; let (_, default_version) = get_default_sudo_open_ack(); verify_state(&deps, ContractState::VerifyLp); diff --git a/v1/holder/.cargo/config b/v1/holder/.cargo/config new file mode 100644 index 00000000..6fac702a --- /dev/null +++ b/v1/holder/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --target wasm32-unknown-unknown --release" +wasm-debug = "build --target wasm32-unknown-unknown" \ No newline at end of file diff --git a/contracts/holder/Cargo.toml b/v1/holder/Cargo.toml similarity index 97% rename from contracts/holder/Cargo.toml rename to v1/holder/Cargo.toml index e10985f4..61692b53 100644 --- a/contracts/holder/Cargo.toml +++ b/v1/holder/Cargo.toml @@ -4,7 +4,7 @@ authors = ["udit "] description = "A holder can hold funds in a covenant" edition = { workspace = true } license = { workspace = true } -rust-version = { workspace = true } +# rust-version = { workspace = true } version = { workspace = true } [lib] diff --git a/contracts/holder/README.md b/v1/holder/README.md similarity index 100% rename from contracts/holder/README.md rename to v1/holder/README.md diff --git a/contracts/holder/src/contract.rs b/v1/holder/src/contract.rs similarity index 100% rename from contracts/holder/src/contract.rs rename to v1/holder/src/contract.rs diff --git a/contracts/holder/src/error.rs b/v1/holder/src/error.rs similarity index 100% rename from contracts/holder/src/error.rs rename to v1/holder/src/error.rs diff --git a/contracts/holder/src/lib.rs b/v1/holder/src/lib.rs similarity index 100% rename from contracts/holder/src/lib.rs rename to v1/holder/src/lib.rs diff --git a/contracts/holder/src/msg.rs b/v1/holder/src/msg.rs similarity index 100% rename from contracts/holder/src/msg.rs rename to v1/holder/src/msg.rs diff --git a/contracts/holder/src/state.rs b/v1/holder/src/state.rs similarity index 100% rename from contracts/holder/src/state.rs rename to v1/holder/src/state.rs diff --git a/contracts/holder/src/suite_tests/mod.rs b/v1/holder/src/suite_tests/mod.rs similarity index 100% rename from contracts/holder/src/suite_tests/mod.rs rename to v1/holder/src/suite_tests/mod.rs diff --git a/contracts/holder/src/suite_tests/suite.rs b/v1/holder/src/suite_tests/suite.rs similarity index 100% rename from contracts/holder/src/suite_tests/suite.rs rename to v1/holder/src/suite_tests/suite.rs diff --git a/contracts/holder/src/suite_tests/tests.rs b/v1/holder/src/suite_tests/tests.rs similarity index 100% rename from contracts/holder/src/suite_tests/tests.rs rename to v1/holder/src/suite_tests/tests.rs diff --git a/v1/lper/.cargo/config b/v1/lper/.cargo/config new file mode 100644 index 00000000..6a6f2852 --- /dev/null +++ b/v1/lper/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" \ No newline at end of file diff --git a/contracts/lper/Cargo.toml b/v1/lper/Cargo.toml similarity index 94% rename from contracts/lper/Cargo.toml rename to v1/lper/Cargo.toml index b59debb1..f4041622 100644 --- a/contracts/lper/Cargo.toml +++ b/v1/lper/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "covenant-lp" authors = ["benskey bekauz@protonmail.com"] -description = "LP contract for stride covenant" +description = "LP module for stride covenant" license = { workspace = true } repository = { workspace = true } version = { workspace = true } @@ -23,7 +23,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -covenant-clock-derive = { workspace = true } +covenant-macros = { workspace = true } covenant-clock = { workspace = true, features=["library"] } cosmwasm-schema = { workspace = true } @@ -59,4 +59,3 @@ astroport-factory = {git = "https://github.com/astroport-fi/astroport-core.git" astroport-native-coin-registry = {git = "https://github.com/astroport-fi/astroport-core.git"} astroport-pair-stable = {git = "https://github.com/astroport-fi/astroport-core.git"} cw1-whitelist = "1.1.0" -covenant-holder = { workspace = true } \ No newline at end of file diff --git a/v1/lper/LICENSE b/v1/lper/LICENSE new file mode 100644 index 00000000..0bdef96b --- /dev/null +++ b/v1/lper/LICENSE @@ -0,0 +1,29 @@ +Copyright 2023 Timewave + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the + distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/v1/lper/examples/schema.rs b/v1/lper/examples/schema.rs new file mode 100644 index 00000000..2c858de2 --- /dev/null +++ b/v1/lper/examples/schema.rs @@ -0,0 +1,15 @@ +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; +use covenant_lp::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use std::env::current_dir; +use std::fs::create_dir_all; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); +} diff --git a/contracts/lper/src/contract.rs b/v1/lper/src/contract.rs similarity index 78% rename from contracts/lper/src/contract.rs rename to v1/lper/src/contract.rs index 93bfadf2..3e278358 100644 --- a/contracts/lper/src/contract.rs +++ b/v1/lper/src/contract.rs @@ -46,35 +46,29 @@ pub fn instantiate( // validate the contract addresses let clock_addr = deps.api.addr_validate(&msg.clock_address)?; - let pool_address = deps.api.addr_validate(&msg.pool_address)?; + let pool_addr = deps.api.addr_validate(&msg.pool_address)?; let holder_addr = deps.api.addr_validate(&msg.holder_address)?; - // zero expected native token amount would result in division - // by zero when validate_price_range is called - if msg.expected_native_token_amount.is_zero() { - return Err(ContractError::ZeroExpectedNativeTokenAmountError {}); - } - // contract starts at Instantiated state CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; - // store the relevant contract addresses + // store the relevant module addresses CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; HOLDER_ADDRESS.save(deps.storage, &holder_addr)?; - // store fields needed for liquidity provision ASSETS.save(deps.storage, &msg.assets)?; let lp_config = LpConfig { expected_native_token_amount: msg.expected_native_token_amount, expected_ls_token_amount: msg.expected_ls_token_amount, allowed_return_delta: msg.allowed_return_delta, - pool_address, + pool_address: pool_addr, single_side_lp_limits: msg.single_side_lp_limits, autostake: msg.autostake, slippage_tolerance: msg.slippage_tolerance, }; LP_CONFIG.save(deps.storage, &lp_config)?; + // we begin with no liquidity provided PROVIDED_LIQUIDITY_INFO.save( deps.storage, @@ -122,12 +116,14 @@ fn try_tick(deps: DepsMut, env: Env, info: MessageInfo) -> Result Result { let asset_data = ASSETS.load(deps.storage)?; - let contract = env.contract.address; + // first we query our own balances and filter out any unexpected denoms - let bal_coins = deps.querier.query_all_balances(contract)?; + let bal_coins = deps + .querier + .query_all_balances(env.contract.address.to_string())?; let (native_bal, ls_bal) = get_relevant_balances( bal_coins, - asset_data.clone().ls_asset_denom, + asset_data.ls_asset_denom, asset_data.native_asset_denom, ); @@ -176,24 +172,19 @@ fn try_get_double_side_lp_submsg( let lp_config = LP_CONFIG.load(deps.storage)?; let asset_data = ASSETS.load(deps.storage)?; let holder_address = HOLDER_ADDRESS.load(deps.storage)?; + // we now query the pool to know the balances let pool_response: PoolResponse = deps .querier .query_wasm_smart(&lp_config.pool_address, &astroport::pair::QueryMsg::Pool {})?; let (pool_native_bal, pool_ls_bal) = get_pool_asset_amounts( pool_response.assets, - asset_data.clone().ls_asset_denom, - asset_data.clone().native_asset_denom, + asset_data.ls_asset_denom.as_str(), + asset_data.native_asset_denom.as_str(), )?; // we validate the pool to match our price expectations - validate_price_range( - pool_native_bal, - pool_ls_bal, - lp_config.expected_native_token_amount, - lp_config.expected_ls_token_amount, - lp_config.allowed_return_delta, - )?; + lp_config.validate_price_range(pool_native_bal, pool_ls_bal)?; // we derive the ratio of native to ls. // using this ratio we know how many native tokens we should provide for every one ls token @@ -209,59 +200,55 @@ fn try_get_double_side_lp_submsg( if native_bal.amount >= required_native_amount { // if we are able to satisfy the required amount, we do that: // provide all statom tokens along with required amount of native tokens - let ls_asset_double_sided = Asset { - info: asset_data.get_ls_asset_info(), - amount: ls_bal.amount, - }; - let native_asset_double_sided = Asset { - info: asset_data.get_native_asset_info(), - amount: required_native_amount, - }; - - (native_asset_double_sided, ls_asset_double_sided) + ( + Asset { + info: asset_data.get_native_asset_info(), + amount: required_native_amount, + }, + Asset { + info: asset_data.get_ls_asset_info(), + amount: ls_bal.amount, + }, + ) } else { // otherwise, our native token amount is insufficient to provide double // sided liquidity using all of our ls tokens. // this means that we should provide all of our available native tokens, // and as many ls tokens as needed to satisfy the existing ratio - let native_asset_double_sided = Asset { - info: asset_data.get_native_asset_info(), - amount: native_bal.amount, - }; - let ls_asset_double_sided = Asset { - info: asset_data.get_ls_asset_info(), - amount: Decimal::from_ratio(pool_ls_bal, pool_native_bal) - .checked_mul_uint128(native_bal.amount)?, - }; - - (native_asset_double_sided, ls_asset_double_sided) + ( + Asset { + info: asset_data.get_native_asset_info(), + amount: native_bal.amount, + }, + Asset { + info: asset_data.get_ls_asset_info(), + amount: Decimal::from_ratio(pool_ls_bal, pool_native_bal) + .checked_mul_uint128(native_bal.amount)?, + }, + ) }; + let (native_coin, ls_coin) = ( + native_asset_double_sided.to_coin()?, + ls_asset_double_sided.to_coin()?, + ); + // craft a ProvideLiquidity message with the determined assets let double_sided_liq_msg = ProvideLiquidity { - assets: vec![ - native_asset_double_sided.clone(), - ls_asset_double_sided.clone(), - ], + assets: vec![native_asset_double_sided, ls_asset_double_sided], slippage_tolerance: lp_config.slippage_tolerance, auto_stake: lp_config.autostake, receiver: Some(holder_address.to_string()), }; - let (native_coin, ls_coin) = ( - native_asset_double_sided.to_coin()?, - ls_asset_double_sided.to_coin()?, - ); // update the provided amounts and leftover assets PROVIDED_LIQUIDITY_INFO.update( deps.storage, |mut info: ProvidedLiquidityInfo| -> StdResult<_> { - info.provided_amount_ls = info - .provided_amount_ls - .checked_add(ls_coin.clone().amount)?; + info.provided_amount_ls = info.provided_amount_ls.checked_add(ls_coin.amount)?; info.provided_amount_native = info .provided_amount_native - .checked_add(native_coin.clone().amount)?; + .checked_add(native_coin.amount)?; Ok(info) }, )?; @@ -285,22 +272,15 @@ fn try_get_single_side_lp_submsg( native_bal: Coin, ls_bal: Coin, ) -> Result, ContractError> { - let lp_config = LP_CONFIG.load(deps.storage)?; - let holder_address = HOLDER_ADDRESS.load(deps.storage)?; let asset_data = ASSETS.load(deps.storage)?; + let holder_address = HOLDER_ADDRESS.load(deps.storage)?; + let lp_config = LP_CONFIG.load(deps.storage)?; - let native_asset = Asset { - info: asset_data.get_native_asset_info(), - amount: native_bal.amount, - }; - let ls_asset = Asset { - info: asset_data.get_ls_asset_info(), - amount: ls_bal.amount, - }; + let assets = asset_data.to_asset_vec(native_bal.amount, ls_bal.amount); // given one non-zero asset, we build the ProvideLiquidity message let single_sided_liq_msg = ProvideLiquidity { - assets: vec![ls_asset, native_asset], + assets, slippage_tolerance: lp_config.slippage_tolerance, auto_stake: lp_config.autostake, receiver: Some(holder_address.to_string()), @@ -310,37 +290,43 @@ fn try_get_single_side_lp_submsg( if native_bal.amount.is_zero() && ls_bal.amount <= lp_config.single_side_lp_limits.ls_asset_limit { + // update the provided liquidity info + PROVIDED_LIQUIDITY_INFO.update(deps.storage, |mut info| -> StdResult<_> { + info.provided_amount_ls = info.provided_amount_ls.checked_add(ls_bal.amount)?; + Ok(info) + })?; + // if available ls token amount is within single side limits we build a single side msg let submsg = SubMsg::reply_on_success( CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: lp_config.pool_address.to_string(), msg: to_binary(&single_sided_liq_msg)?, - funds: vec![ls_bal.clone()], + funds: vec![ls_bal], }), SINGLE_SIDED_REPLY_ID, ); - PROVIDED_LIQUIDITY_INFO.update(deps.storage, |mut info| -> StdResult<_> { - info.provided_amount_ls = info.provided_amount_ls.checked_add(ls_bal.amount)?; - Ok(info) - })?; + return Ok(Some(submsg)); } else if ls_bal.amount.is_zero() && native_bal.amount <= lp_config.single_side_lp_limits.native_asset_limit { + // update the provided liquidity info + PROVIDED_LIQUIDITY_INFO.update(deps.storage, |mut info| -> StdResult<_> { + info.provided_amount_native = + info.provided_amount_native.checked_add(native_bal.amount)?; + Ok(info) + })?; + // if available native token amount is within single side limits we build a single side msg let submsg = SubMsg::reply_on_success( CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: lp_config.pool_address.to_string(), msg: to_binary(&single_sided_liq_msg)?, - funds: vec![native_bal.clone()], + funds: vec![native_bal], }), SINGLE_SIDED_REPLY_ID, ); - PROVIDED_LIQUIDITY_INFO.update(deps.storage, |mut info| -> StdResult<_> { - info.provided_amount_native = - info.provided_amount_native.checked_add(native_bal.amount)?; - Ok(info) - })?; + return Ok(Some(submsg)); } @@ -364,48 +350,16 @@ fn get_relevant_balances(coins: Vec, ls_denom: String, native_denom: Strin (native_bal, ls_bal) } -/// validates the existing pool balances to match our initial expectations. -/// if `PriceRangeError` is returned, it most likely means that the pool had a -/// significant shift in its balance ratio. -fn validate_price_range( - pool_native_amount: Uint128, - pool_ls_amount: Uint128, - expected_native_token_amount: Uint128, - expected_ls_token_amount: Uint128, - allowed_return_delta: Uint128, -) -> Result<(), ContractError> { - // find the min and max return amounts allowed by deviating away from expected return amount - // by allowed delta - let min_return_amount = expected_ls_token_amount.checked_sub(allowed_return_delta)?; - let max_return_amount = expected_ls_token_amount.checked_add(allowed_return_delta)?; - - // derive allowed proportions - let min_accepted_ratio = Decimal::from_ratio(min_return_amount, expected_native_token_amount); - let max_accepted_ratio = Decimal::from_ratio(max_return_amount, expected_native_token_amount); - - // we find the proportion of the price range being validated - let validation_ratio = Decimal::from_ratio(pool_ls_amount, pool_native_amount); - - // if current return to offer amount ratio falls out of [min_accepted_ratio, max_accepted_ratio], - // return price range error - if validation_ratio < min_accepted_ratio || validation_ratio > max_accepted_ratio { - return Err(ContractError::PriceRangeError {}); - } - - Ok(()) -} - /// filters out irrelevant balances and returns ls and native amounts fn get_pool_asset_amounts( assets: Vec, - ls_denom: String, - native_denom: String, + ls_denom: &str, + native_denom: &str, ) -> Result<(Uint128, Uint128), StdError> { let (mut native_bal, mut ls_bal) = (Uint128::zero(), Uint128::zero()); for asset in assets { let coin = asset.to_coin()?; - if coin.denom == ls_denom { // found ls balance ls_bal = coin.amount; @@ -419,13 +373,15 @@ fn get_pool_asset_amounts( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::ClockAddress {} => Ok(to_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), QueryMsg::ContractState {} => Ok(to_binary(&CONTRACT_STATE.may_load(deps.storage)?)?), QueryMsg::HolderAddress {} => Ok(to_binary(&HOLDER_ADDRESS.may_load(deps.storage)?)?), QueryMsg::Assets {} => Ok(to_binary(&ASSETS.may_load(deps.storage)?)?), QueryMsg::LpConfig {} => Ok(to_binary(&LP_CONFIG.may_load(deps.storage)?)?), + // the deposit address for LP module is the contract itself + QueryMsg::DepositAddress {} => Ok(to_binary(&Some(&env.contract.address.to_string()))?), } } @@ -454,11 +410,13 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> NeutronResult NeutronResult Result { deps.api.debug("WASMDEBUG: reply"); - println!("{:?}", msg.clone().result.unwrap()); match msg.id { DOUBLE_SIDED_REPLY_ID => handle_double_sided_reply_id(deps, _env, msg), SINGLE_SIDED_REPLY_ID => handle_single_sided_reply_id(deps, _env, msg), diff --git a/contracts/lper/src/error.rs b/v1/lper/src/error.rs similarity index 88% rename from contracts/lper/src/error.rs rename to v1/lper/src/error.rs index 1e4bba80..40ae9aec 100644 --- a/contracts/lper/src/error.rs +++ b/v1/lper/src/error.rs @@ -36,7 +36,4 @@ pub enum ContractError { #[error("Price range error")] PriceRangeError {}, - - #[error("zero expected native token amount can result in division by 0")] - ZeroExpectedNativeTokenAmountError {}, } diff --git a/contracts/lper/src/lib.rs b/v1/lper/src/lib.rs similarity index 100% rename from contracts/lper/src/lib.rs rename to v1/lper/src/lib.rs diff --git a/contracts/lper/src/msg.rs b/v1/lper/src/msg.rs similarity index 76% rename from contracts/lper/src/msg.rs rename to v1/lper/src/msg.rs index 4a2cc7b7..7afb12ba 100644 --- a/contracts/lper/src/msg.rs +++ b/v1/lper/src/msg.rs @@ -1,7 +1,9 @@ use astroport::asset::{Asset, AssetInfo}; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Attribute, Binary, Decimal, Uint128}; -use covenant_clock_derive::clocked; +use covenant_macros::{clocked, covenant_clock_address, covenant_deposit_address}; + +use crate::error::ContractError; #[cw_serde] pub struct InstantiateMsg { @@ -17,6 +19,97 @@ pub struct InstantiateMsg { pub expected_native_token_amount: Uint128, } +#[cw_serde] +pub struct LpConfig { + /// the native token amount we expect to be funded with + pub expected_native_token_amount: Uint128, + /// stride redemption rate is variable so we set the expected ls token amount + pub expected_ls_token_amount: Uint128, + /// accepted return amount fluctuation that gets applied to EXPECTED_LS_TOKEN_AMOUNT + pub allowed_return_delta: Uint128, + /// address of the liquidity pool we plan to enter + pub pool_address: Addr, + /// amounts of native and ls tokens we consider ok to single-side lp + pub single_side_lp_limits: SingleSideLpLimits, + /// boolean flag for enabling autostaking of LP tokens upon liquidity provisioning + pub autostake: Option, + /// slippage tolerance parameter for liquidity provisioning + pub slippage_tolerance: Option, +} + +impl LpConfig { + pub fn to_response_attributes(self) -> Vec { + let autostake = match self.autostake { + Some(val) => val.to_string(), + None => "None".to_string(), + }; + let slippage_tolerance = match self.slippage_tolerance { + Some(val) => val.to_string(), + None => "None".to_string(), + }; + vec![ + Attribute::new( + "expected_native_token_amount", + self.expected_native_token_amount.to_string(), + ), + Attribute::new( + "expected_ls_token_amount", + self.expected_ls_token_amount.to_string(), + ), + Attribute::new( + "allowed_return_delta", + self.allowed_return_delta.to_string(), + ), + Attribute::new("pool_address", self.pool_address.to_string()), + Attribute::new( + "single_side_lp_limit_native", + self.single_side_lp_limits.native_asset_limit.to_string(), + ), + Attribute::new( + "single_side_lp_limit_ls", + self.single_side_lp_limits.ls_asset_limit.to_string(), + ), + Attribute::new("autostake", autostake), + Attribute::new("slippage_tolerance", slippage_tolerance), + ] + } + + /// validates the existing pool balances to match our initial expectations. + /// if `PriceRangeError` is returned, it most likely means that the pool had a + /// significant shift in its balance ratio. + pub fn validate_price_range( + &self, + pool_native_bal: Uint128, + pool_ls_bal: Uint128, + ) -> Result<(), ContractError> { + // find the min return amount by subtracting the delta from expected amount + let min_return_amount = self + .expected_ls_token_amount + .checked_sub(self.allowed_return_delta)?; + // find the max return amount by adding the delta to expected amount + let max_return_amount = self + .expected_ls_token_amount + .checked_add(self.allowed_return_delta)?; + + // derive allowed proportions + let min_accepted_ratio = + Decimal::from_ratio(min_return_amount, self.expected_native_token_amount); + let max_accepted_ratio = + Decimal::from_ratio(max_return_amount, self.expected_native_token_amount); + + // we find the proportion of the price range being validated + let validation_ratio = Decimal::from_ratio(pool_ls_bal, pool_native_bal); + + // if current return to offer amount ratio falls out of [min_accepted_ratio, max_return_amount], + // return price range error + if validation_ratio < min_accepted_ratio || validation_ratio > max_accepted_ratio { + return Err(ContractError::PriceRangeError {}); + } + + Ok(()) + } +} + /// holds the native and ls asset denoms relevant for providing liquidity. #[cw_serde] pub struct AssetData { @@ -38,6 +131,19 @@ impl AssetData { denom: self.ls_asset_denom.to_string(), } } + + pub fn to_asset_vec(&self, native_bal: Uint128, ls_bal: Uint128) -> Vec { + vec![ + Asset { + info: self.get_native_asset_info(), + amount: native_bal, + }, + Asset { + info: self.get_ls_asset_info(), + amount: ls_bal, + }, + ] + } } /// single side lp limits define the highest amount (in `Uint128`) that @@ -107,11 +213,11 @@ impl PresetLpFields { #[cw_serde] pub enum ExecuteMsg {} +#[covenant_clock_address] +#[covenant_deposit_address] #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - #[returns(Addr)] - ClockAddress {}, #[returns(ContractState)] ContractState {}, #[returns(Addr)] @@ -128,7 +234,7 @@ pub enum MigrateMsg { clock_addr: Option, holder_address: Option, assets: Option, - lp_config: Box>, + lp_config: Option, }, UpdateCodeId { data: Option, @@ -147,59 +253,3 @@ pub struct ProvidedLiquidityInfo { pub enum ContractState { Instantiated, } - -#[cw_serde] -pub struct LpConfig { - /// the native token amount we expect to be funded with - pub expected_native_token_amount: Uint128, - /// stride redemption rate is variable so we set the expected ls token amount - pub expected_ls_token_amount: Uint128, - /// accepted return amount fluctuation that gets applied to EXPECTED_LS_TOKEN_AMOUNT - pub allowed_return_delta: Uint128, - /// address of the liquidity pool we plan to enter - pub pool_address: Addr, - /// amounts of native and ls tokens we consider ok to single-side lp - pub single_side_lp_limits: SingleSideLpLimits, - /// boolean flag for enabling autostaking of LP tokens upon liquidity provisioning - pub autostake: Option, - /// slippage tolerance parameter for liquidity provisioning - pub slippage_tolerance: Option, -} - -impl LpConfig { - pub fn to_response_attributes(self) -> Vec { - let autostake = match self.autostake { - Some(val) => val.to_string(), - None => "None".to_string(), - }; - let slippage_tolerance = match self.slippage_tolerance { - Some(val) => val.to_string(), - None => "None".to_string(), - }; - vec![ - Attribute::new( - "expected_native_token_amount", - self.expected_native_token_amount.to_string(), - ), - Attribute::new( - "expected_ls_token_amount", - self.expected_ls_token_amount.to_string(), - ), - Attribute::new( - "allowed_return_delta", - self.allowed_return_delta.to_string(), - ), - Attribute::new("pool_address", self.pool_address.to_string()), - Attribute::new( - "single_side_lp_limit_native", - self.single_side_lp_limits.native_asset_limit.to_string(), - ), - Attribute::new( - "single_side_lp_limit_ls", - self.single_side_lp_limits.ls_asset_limit.to_string(), - ), - Attribute::new("autostake", autostake), - Attribute::new("slippage_tolerance", slippage_tolerance), - ] - } -} diff --git a/contracts/lper/src/state.rs b/v1/lper/src/state.rs similarity index 86% rename from contracts/lper/src/state.rs rename to v1/lper/src/state.rs index 5fe980e4..8223dd58 100644 --- a/contracts/lper/src/state.rs +++ b/v1/lper/src/state.rs @@ -8,9 +8,9 @@ pub const CONTRACT_STATE: Item = Item::new("contract_state"); /// native and ls asset denom information pub const ASSETS: Item = Item::new("assets"); -/// clock contract address to verify the incoming ticks sender +/// clock module address to verify the incoming ticks sender pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); -/// holder contract address to verify withdrawal requests +/// holder module address to verify withdrawal requests pub const HOLDER_ADDRESS: Item = Item::new("holder_address"); /// keeps track of ls and native token amounts we provided to the pool diff --git a/v1/lper/src/suite_test/mod.rs b/v1/lper/src/suite_test/mod.rs new file mode 100644 index 00000000..7b881830 --- /dev/null +++ b/v1/lper/src/suite_test/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod tests; diff --git a/contracts/lper/src/suite_test/suite.rs b/v1/lper/src/suite_test/suite.rs similarity index 98% rename from contracts/lper/src/suite_test/suite.rs rename to v1/lper/src/suite_test/suite.rs index 710216b3..f6266729 100644 --- a/contracts/lper/src/suite_test/suite.rs +++ b/v1/lper/src/suite_test/suite.rs @@ -238,31 +238,26 @@ impl Default for SuiteBuilder { #[allow(unused)] impl SuiteBuilder { - pub fn with_slippage_tolerance(mut self, decimal: Decimal) -> Self { + fn with_slippage_tolerance(mut self, decimal: Decimal) -> Self { self.lp_instantiate.slippage_tolerance = Some(decimal); self } - pub fn with_autostake(mut self, autosake: Option) -> Self { + fn with_autostake(mut self, autosake: Option) -> Self { self.lp_instantiate.autostake = autosake; self } - pub fn with_assets(mut self, assets: AssetData) -> Self { + fn with_assets(mut self, assets: AssetData) -> Self { self.lp_instantiate.assets = assets; self } - pub fn with_token_instantiate_msg(mut self, msg: TokenInstantiateMsg) -> Self { + fn with_token_instantiate_msg(mut self, msg: TokenInstantiateMsg) -> Self { self.token_instantiate = msg; self } - pub fn with_expected_native_token_amount(mut self, amt: Uint128) -> Self { - self.lp_instantiate.expected_native_token_amount = amt; - self - } - pub fn build(mut self) -> Suite { // let mut app = BasicAppBuilder::::new_custom().build(|_,_,_| {}); diff --git a/contracts/lper/src/suite_test/tests.rs b/v1/lper/src/suite_test/tests.rs similarity index 97% rename from contracts/lper/src/suite_test/tests.rs rename to v1/lper/src/suite_test/tests.rs index a340c0b5..49ce0e70 100644 --- a/contracts/lper/src/suite_test/tests.rs +++ b/v1/lper/src/suite_test/tests.rs @@ -71,14 +71,6 @@ fn test_instantiate_happy() { assert_ne!(Uint128::zero(), holder_native_balances[1].amount); } -#[test] -#[should_panic(expected = "zero expected native token amount can result in division by 0")] -fn test_instantiate_with_zero_expected_native_token_amount() { - SuiteBuilder::default() - .with_expected_native_token_amount(Uint128::zero()) - .build(); -} - // tests todo: // 1. randomly funded contracts/wallets // 2. existing pool ratios (imbalanced, equal, extremely imbalanced, providing more liq than exists) diff --git a/v1/ls/.cargo/config b/v1/ls/.cargo/config new file mode 100644 index 00000000..5f6aa466 --- /dev/null +++ b/v1/ls/.cargo/config @@ -0,0 +1,3 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" diff --git a/contracts/ls/Cargo.toml b/v1/ls/Cargo.toml similarity index 84% rename from contracts/ls/Cargo.toml rename to v1/ls/Cargo.toml index 3854ab08..e205681b 100644 --- a/contracts/ls/Cargo.toml +++ b/v1/ls/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "covenant-ls" authors = ["benskey bekauz@protonmail.com"] -description = "Liquid Staker contract for stride covenant" +description = "Liquid Staker module for stride covenant" edition = { workspace = true } license = { workspace = true } -rust-version = { workspace = true } +# rust-version = { workspace = true } version = { workspace = true } [lib] @@ -17,9 +17,9 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -covenant-clock-derive = { workspace = true } +covenant-macros = { workspace = true } covenant-clock = { workspace = true, features=["library"] } - +covenant-utils = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } diff --git a/contracts/ls/README.md b/v1/ls/README.md similarity index 100% rename from contracts/ls/README.md rename to v1/ls/README.md diff --git a/contracts/ls/examples/schema.rs b/v1/ls/examples/schema.rs similarity index 100% rename from contracts/ls/examples/schema.rs rename to v1/ls/examples/schema.rs diff --git a/contracts/ls/src/contract.rs b/v1/ls/src/contract.rs similarity index 63% rename from contracts/ls/src/contract.rs rename to v1/ls/src/contract.rs index 1db20ea7..a12b0950 100644 --- a/contracts/ls/src/contract.rs +++ b/v1/ls/src/contract.rs @@ -1,33 +1,29 @@ -use cosmos_sdk_proto::cosmos::base::v1beta1::Coin; use cosmos_sdk_proto::ibc::applications::transfer::v1::MsgTransfer; -use cosmos_sdk_proto::traits::Message; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_binary, Addr, Binary, CosmosMsg, CustomQuery, Deps, DepsMut, Env, MessageInfo, Reply, - Response, StdError, StdResult, SubMsg, Uint128, + to_binary, Binary, CosmosMsg, CustomQuery, Deps, DepsMut, Env, MessageInfo, Reply, Response, + StdError, StdResult, SubMsg, Uint128, }; use covenant_clock::helpers::verify_clock; +use covenant_utils::neutron_ica::{ + self, get_proto_coin, OpenAckVersion, RemoteChainInfo, SudoPayload, +}; use cw2::set_contract_version; -use neutron_sdk::bindings::types::ProtobufAny; -use crate::msg::{ - ContractState, ExecuteMsg, InstantiateMsg, MigrateMsg, OpenAckVersion, QueryMsg, - RemoteChainInfo, SudoPayload, -}; +use crate::msg::{ContractState, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; use crate::state::{ - read_errors_from_queue, read_reply_payload, save_reply_payload, save_sudo_payload, - ACKNOWLEDGEMENT_RESULTS, CLOCK_ADDRESS, CONTRACT_STATE, INTERCHAIN_ACCOUNTS, LP_ADDRESS, - REMOTE_CHAIN_INFO, + read_reply_payload, save_reply_payload, save_sudo_payload, AUTOPILOT_FORMAT, CLOCK_ADDRESS, + CONTRACT_STATE, INTERCHAIN_ACCOUNTS, NEXT_CONTRACT, REMOTE_CHAIN_INFO, }; use neutron_sdk::{ bindings::{ msg::{MsgSubmitTxResponse, NeutronMsg}, - query::{NeutronQuery, QueryInterchainAccountAddressResponse}, + query::NeutronQuery, }, interchain_txs::helpers::get_port_id, sudo::msg::{RequestPacket, SudoMsg}, - NeutronResult, + NeutronError, NeutronResult, }; const INTERCHAIN_ACCOUNT_ID: &str = "stride-ica"; @@ -47,15 +43,12 @@ pub fn instantiate( deps.api.debug("WASMDEBUG: instantiate"); set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - // contract begins at Instantiated state - CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; - - // validate and store other contract addresses + // validate the addresses let clock_addr = deps.api.addr_validate(&msg.clock_address)?; - let lp_address = deps.api.addr_validate(&msg.lp_address)?; - CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; - LP_ADDRESS.save(deps.storage, &lp_address)?; + let next_contract = deps.api.addr_validate(&msg.next_contract)?; + CLOCK_ADDRESS.save(deps.storage, &clock_addr)?; + NEXT_CONTRACT.save(deps.storage, &next_contract)?; let remote_chain_info = RemoteChainInfo { connection_id: msg.neutron_stride_ibc_connection_id, channel_id: msg.stride_neutron_ibc_transfer_channel_id, @@ -65,11 +58,12 @@ pub fn instantiate( ibc_fee: msg.ibc_fee, }; REMOTE_CHAIN_INFO.save(deps.storage, &remote_chain_info)?; + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; Ok(Response::default() .add_attribute("method", "ls_instantiate") .add_attribute("clock_address", clock_addr) - .add_attribute("lp_address", lp_address) + .add_attribute("next_contract", next_contract) .add_attributes(remote_chain_info.get_response_attributes())) } @@ -85,11 +79,9 @@ pub fn execute( match msg { ExecuteMsg::Tick {} => try_tick(deps, env, info), ExecuteMsg::Transfer { amount } => { - match get_ica(deps.as_ref(), &env, INTERCHAIN_ACCOUNT_ID) { - Ok((address, controller_conn_id)) => { - try_execute_transfer(deps, env, info, amount, address, controller_conn_id) - } - // if no ICA is available yet we keep waiting for ticks to create one + let ica_address = get_ica(deps.as_ref(), &env, INTERCHAIN_ACCOUNT_ID); + match ica_address { + Ok(_) => try_execute_transfer(deps, env, info, amount), Err(_) => Ok(Response::default() .add_attribute("method", "try_permisionless_transfer") .add_attribute("ica_status", "not_created")), @@ -106,15 +98,15 @@ fn try_tick(deps: DepsMut, env: Env, info: MessageInfo) -> NeutronResult try_register_stride_ica(deps, env), - ContractState::ICACreated => Ok(Response::default()), + ContractState::IcaCreated => Ok(Response::default()), } } /// registers an interchain account on stride with port_id associated with `INTERCHAIN_ACCOUNT_ID` fn try_register_stride_ica(deps: DepsMut, env: Env) -> NeutronResult> { - let rc_info = REMOTE_CHAIN_INFO.load(deps.storage)?; - let register = NeutronMsg::register_interchain_account( - rc_info.connection_id, + let remote_chain_info = REMOTE_CHAIN_INFO.load(deps.storage)?; + let register: NeutronMsg = NeutronMsg::register_interchain_account( + remote_chain_info.connection_id, INTERCHAIN_ACCOUNT_ID.to_string(), ); let key = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); @@ -129,74 +121,87 @@ fn try_register_stride_ica(deps: DepsMut, env: Env) -> NeutronResult NeutronResult> { - let port_id = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); - let rc_info = REMOTE_CHAIN_INFO.load(deps.storage)?; - let lp_receiver = LP_ADDRESS.load(deps.storage)?; - let coin = Coin { - denom: rc_info.denom, - amount: amount.to_string(), - }; + // first we verify whether the next contract is ready for receiving the funds + let next_contract = NEXT_CONTRACT.load(deps.storage)?; + let deposit_address_query = deps.querier.query_wasm_smart( + next_contract, + &covenant_utils::neutron_ica::QueryMsg::DepositAddress {}, + )?; - // inner MsgTransfer that will be sent from stride to neutron. - // because of this message delivery depending on the ica wrapper below, - // timeout_timestamp = current block + ica timeout + ibc_transfer_timeout - let msg = MsgTransfer { - source_port: "transfer".to_string(), - source_channel: rc_info.channel_id, - token: Some(coin), - sender: address, - receiver: lp_receiver.to_string(), - timeout_height: None, - timeout_timestamp: env - .block - .time - .plus_seconds(rc_info.ica_timeout.u64()) - .plus_seconds(rc_info.ibc_transfer_timeout.u64()) - .nanos(), + // if query returns None, then we error and wait + let Some(deposit_address) = deposit_address_query else { + return Err(NeutronError::Std( + StdError::not_found("Next contract is not ready for receiving the funds yet") + )) }; - // Serialize the Transfer message - let mut buf = Vec::with_capacity(msg.encoded_len()); - if let Err(e) = msg.encode(&mut buf) { - return Err(StdError::generic_err(format!("Encode error: {e}",)).into()); + let port_id = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); + let interchain_account = INTERCHAIN_ACCOUNTS.load(deps.storage, port_id.clone())?; + + match interchain_account { + Some((address, controller_conn_id)) => { + let remote_chain_info = REMOTE_CHAIN_INFO.load(deps.storage)?; + + // inner MsgTransfer that will be sent from stride to neutron. + // because of this message delivery depending on the ica wrapper below, + // timeout_timestamp = current block + ica timeout + ibc_transfer_timeout + let msg = MsgTransfer { + source_port: "transfer".to_string(), + source_channel: remote_chain_info.channel_id, + token: Some(get_proto_coin(remote_chain_info.denom, amount)), + sender: address, + receiver: deposit_address, + timeout_height: None, + timeout_timestamp: env + .block + .time + .plus_seconds(remote_chain_info.ica_timeout.u64()) + .plus_seconds(remote_chain_info.ibc_transfer_timeout.u64()) + .nanos(), + }; + + let protobuf = neutron_ica::to_proto_msg_transfer(msg)?; + + // wrap the protobuf of MsgTransfer into a message to be executed + // by our interchain account + let submit_msg = NeutronMsg::submit_tx( + controller_conn_id, + INTERCHAIN_ACCOUNT_ID.to_string(), + vec![protobuf], + "".to_string(), + remote_chain_info.ica_timeout.u64(), + remote_chain_info.ibc_fee, + ); + + let sudo_msg = msg_with_sudo_callback( + deps, + submit_msg, + SudoPayload { + port_id, + message: "permisionless_transfer".to_string(), + }, + )?; + Ok(Response::default() + .add_submessage(sudo_msg) + .add_attribute("method", "try_execute_transfer")) + } + None => { + // I can't think of a case of how we could end up here as `sudo_open_ack` + // callback advances the state to `ICACreated` and stores the ICA. + // just in case, we revert the state to `Instantiated` to restart the flow. + CONTRACT_STATE.save(deps.storage, &ContractState::Instantiated)?; + Ok(Response::default() + .add_attribute("method", "try_execute_transfer") + .add_attribute("error", "no_ica_found")) + } } - - let protobuf = ProtobufAny { - type_url: "/ibc.applications.transfer.v1.MsgTransfer".to_string(), - value: Binary::from(buf), - }; - - // wrap the protobuf of MsgTransfer into a message to be executed - // by our interchain account - let submit_msg = NeutronMsg::submit_tx( - controller_conn_id, - INTERCHAIN_ACCOUNT_ID.to_string(), - vec![protobuf], - "".to_string(), - rc_info.ica_timeout.u64(), - rc_info.ibc_fee, - ); - - let sudo_msg = msg_with_sudo_callback( - deps, - submit_msg, - SudoPayload { - port_id, - message: "permisionless_transfer".to_string(), - }, - )?; - Ok(Response::default() - .add_submessage(sudo_msg) - .add_attribute("method", "try_execute_transfer")) } #[allow(unused)] @@ -212,62 +217,41 @@ fn msg_with_sudo_callback>, T>( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> NeutronResult { match msg { - QueryMsg::LpAddress {} => Ok(to_binary(&LP_ADDRESS.may_load(deps.storage)?)?), QueryMsg::ClockAddress {} => Ok(to_binary(&CLOCK_ADDRESS.may_load(deps.storage)?)?), - QueryMsg::StrideICA {} => Ok(to_binary(&Addr::unchecked( - get_ica(deps, &env, INTERCHAIN_ACCOUNT_ID)?.0, - ))?), + QueryMsg::IcaAddress {} => Ok(to_binary(&get_ica(deps, &env, INTERCHAIN_ACCOUNT_ID)?.0)?), QueryMsg::ContractState {} => Ok(to_binary(&CONTRACT_STATE.may_load(deps.storage)?)?), + QueryMsg::DepositAddress {} => { + let ica = query_deposit_address(deps, env)?; + // up to the querying module to make sense of the response + Ok(to_binary(&ica)?) + } QueryMsg::RemoteChainInfo {} => Ok(to_binary(&REMOTE_CHAIN_INFO.may_load(deps.storage)?)?), - QueryMsg::AcknowledgementResult { - interchain_account_id, - sequence_id, - } => query_acknowledgement_result(deps, env, interchain_account_id, sequence_id), - QueryMsg::ErrorsQueue {} => query_errors_queue(deps), } } -// returns ICA address from Neutron ICA SDK module -pub fn query_interchain_address( - deps: Deps, - env: Env, - interchain_account_id: String, - connection_id: String, -) -> NeutronResult { - let query = NeutronQuery::InterchainAccountAddress { - owner_address: env.contract.address.to_string(), - interchain_account_id, - connection_id, - }; - - let res: QueryInterchainAccountAddressResponse = deps.querier.query(&query.into())?; - Ok(to_binary(&res)?) -} - -// returns ICA address from the contract storage. The address was saved in sudo_open_ack method -pub fn query_interchain_address_contract( - deps: Deps, - env: Env, - interchain_account_id: String, -) -> NeutronResult { - Ok(to_binary(&get_ica(deps, &env, &interchain_account_id)?)?) -} +fn query_deposit_address(deps: Deps, env: Env) -> Result, StdError> { + let key = get_port_id(env.contract.address.as_str(), INTERCHAIN_ACCOUNT_ID); -// returns the result -pub fn query_acknowledgement_result( - deps: Deps, - env: Env, - interchain_account_id: String, - sequence_id: u64, -) -> NeutronResult { - let port_id = get_port_id(env.contract.address.as_str(), &interchain_account_id); - let res = ACKNOWLEDGEMENT_RESULTS.may_load(deps.storage, (port_id, sequence_id))?; - Ok(to_binary(&res)?) -} + // here we cover three cases: + match INTERCHAIN_ACCOUNTS.may_load(deps.storage, key)? { + Some(entry) => { + // 1. ICA had been created -> fetch the autopilot string and return Some(autopilot) + if let Some((addr, _)) = entry { + let autopilot_receiver = AUTOPILOT_FORMAT + .load(deps.storage)? + .replace("{st_ica}", &addr); -pub fn query_errors_queue(deps: Deps) -> NeutronResult { - let res = read_errors_from_queue(deps.storage)?; - Ok(to_binary(&res)?) + Ok(Some(autopilot_receiver)) + } + // 2. ICA creation request had been submitted but did not receive + // the channel_open_ack yet -> None + else { + Ok(None) + } + } + // 3. ICA creation request hadn't been submitted yet -> None + None => Ok(None), + } } #[cfg_attr(not(feature = "library"), entry_point)] @@ -317,21 +301,23 @@ fn sudo_open_ack( let parsed_version: Result = serde_json_wasm::from_str(counterparty_version.as_str()); + // get the parsed OpenAckVersion or return an error if we fail + let Ok(parsed_version) = parsed_version else { + return Err(StdError::generic_err("Can't parse counterparty_version")) + }; + // Update the storage record associated with the interchain account. - if let Ok(parsed_version) = parsed_version { - INTERCHAIN_ACCOUNTS.save( - deps.storage, - port_id, - &Some(( - parsed_version.clone().address, - parsed_version.controller_connection_id, - )), - )?; - // we advance the state now that the channel is established - CONTRACT_STATE.save(deps.storage, &ContractState::ICACreated)?; - return Ok(Response::default().add_attribute("method", "sudo_open_ack")); - } - Err(StdError::generic_err("Can't parse counterparty_version")) + INTERCHAIN_ACCOUNTS.save( + deps.storage, + port_id, + &Some(( + parsed_version.clone().address, + parsed_version.controller_connection_id, + )), + )?; + CONTRACT_STATE.save(deps.storage, &ContractState::IcaCreated)?; + + Ok(Response::default().add_attribute("method", "sudo_open_ack")) } fn sudo_response(deps: DepsMut, request: RequestPacket, data: Binary) -> StdResult { @@ -437,7 +423,7 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult match msg { MigrateMsg::UpdateConfig { clock_addr, - lp_address, + next_contract, remote_chain_info, } => { let mut resp = Response::default().add_attribute("method", "update_config"); @@ -448,10 +434,10 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult resp = resp.add_attribute("clock_addr", addr.to_string()); } - if let Some(addr) = lp_address { + if let Some(addr) = next_contract { let addr = deps.api.addr_validate(&addr)?; - resp = resp.add_attribute("lp_address", addr.to_string()); - LP_ADDRESS.save(deps.storage, &addr)?; + resp = resp.add_attribute("next_contract", addr.to_string()); + NEXT_CONTRACT.save(deps.storage, &addr)?; } if let Some(rci) = remote_chain_info { diff --git a/contracts/clock-tester/src/tests.rs b/v1/ls/src/error.rs similarity index 100% rename from contracts/clock-tester/src/tests.rs rename to v1/ls/src/error.rs diff --git a/contracts/ls/src/lib.rs b/v1/ls/src/lib.rs similarity index 100% rename from contracts/ls/src/lib.rs rename to v1/ls/src/lib.rs diff --git a/v1/ls/src/msg.rs b/v1/ls/src/msg.rs new file mode 100644 index 00000000..d146c3e7 --- /dev/null +++ b/v1/ls/src/msg.rs @@ -0,0 +1,124 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Binary, Uint128, Uint64}; +use covenant_macros::{ + clocked, covenant_clock_address, covenant_deposit_address, covenant_ica_address, + covenant_remote_chain, +}; +use covenant_utils::neutron_ica::RemoteChainInfo; +use neutron_sdk::bindings::msg::IbcFee; + +#[cw_serde] +pub struct InstantiateMsg { + /// Address for the clock. This contract verifies + /// that only the clock can execute Ticks + pub clock_address: String, + /// IBC transfer channel on Stride for Neutron + /// This is used to IBC transfer stuatom on Stride + /// to the LP contract + pub stride_neutron_ibc_transfer_channel_id: String, + /// IBC connection ID on Neutron for Stride + /// We make an Interchain Account over this connection + pub neutron_stride_ibc_connection_id: String, + /// Address of the next contract to query for the deposit address + pub next_contract: String, + /// The liquid staked denom (e.g., stuatom). This is + /// required because we only allow transfers of this denom + /// out of the LSer + pub ls_denom: String, + /// Neutron requires fees to be set to refund relayers for + /// submission of ack and timeout messages. + /// recv_fee and ack_fee paid in untrn from this contract + pub ibc_fee: IbcFee, + /// Time in seconds for ICA SubmitTX messages from Neutron + /// Note that ICA uses ordered channels, a timeout implies + /// channel closed. We can reopen the channel by reregistering + /// the ICA with the same port id and connection id + pub ica_timeout: Uint64, + /// Timeout in seconds. This is used to craft a timeout timestamp + /// that will be attached to the IBC transfer message from the ICA + /// on the host chain (Stride) to its destination. Typically + /// this timeout should be greater than the ICA timeout, otherwise + /// if the ICA times out, the destination chain receiving the funds + /// will also receive the IBC packet with an expired timestamp. + pub ibc_transfer_timeout: Uint64, + /// json formatted string meant to be used for one-click + /// liquid staking on stride + pub autopilot_format: String, +} + +#[cw_serde] +pub struct PresetLsFields { + pub ls_code: u64, + pub label: String, + pub ls_denom: String, + pub stride_neutron_ibc_transfer_channel_id: String, + pub neutron_stride_ibc_connection_id: String, + pub autopilot_format: String, +} + +impl PresetLsFields { + pub fn to_instantiate_msg( + self, + clock_address: String, + next_contract: String, + ibc_fee: IbcFee, + ica_timeout: Uint64, + ibc_transfer_timeout: Uint64, + ) -> InstantiateMsg { + InstantiateMsg { + clock_address, + stride_neutron_ibc_transfer_channel_id: self.stride_neutron_ibc_transfer_channel_id, + neutron_stride_ibc_connection_id: self.neutron_stride_ibc_connection_id, + next_contract, + ls_denom: self.ls_denom, + ibc_fee, + ica_timeout, + ibc_transfer_timeout, + autopilot_format: self.autopilot_format, + } + } +} + +#[clocked] +#[cw_serde] +pub enum ExecuteMsg { + /// The transfer message allows anybody to permissionlessly + /// transfer a specified amount of tokens of the preset ls_denom + /// from the ICA of the host chain to the preset lp_address + Transfer { amount: Uint128 }, +} + +#[covenant_clock_address] +#[covenant_remote_chain] +#[covenant_deposit_address] +#[covenant_ica_address] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(ContractState)] + ContractState {}, +} + +#[cw_serde] +pub enum MigrateMsg { + UpdateConfig { + clock_addr: Option, + // stride_neutron_ibc_transfer_channel_id: Option, + next_contract: Option, + // neutron_stride_ibc_connection_id: Option, + // ls_denom: Option, + // ibc_fee: Option, + // ibc_transfer_timeout: Option, + // ica_timeout: Option, + remote_chain_info: Option, + }, + UpdateCodeId { + data: Option, + }, +} + +#[cw_serde] +pub enum ContractState { + Instantiated, + IcaCreated, +} diff --git a/contracts/ls/src/state.rs b/v1/ls/src/state.rs similarity index 76% rename from contracts/ls/src/state.rs rename to v1/ls/src/state.rs index 4a7302a5..41bb244d 100644 --- a/contracts/ls/src/state.rs +++ b/v1/ls/src/state.rs @@ -1,15 +1,18 @@ -use cosmwasm_std::{from_binary, to_vec, Addr, Binary, Order, StdResult, Storage}; +use cosmwasm_std::{from_binary, to_vec, Addr, Binary, Order, StdResult, Storage, Uint128}; +use covenant_utils::neutron_ica::{RemoteChainInfo, SudoPayload}; use cw_storage_plus::{Item, Map}; -use crate::msg::{AcknowledgementResult, ContractState, RemoteChainInfo, SudoPayload}; +use crate::msg::ContractState; /// tracks the current state of state machine pub const CONTRACT_STATE: Item = Item::new("contract_state"); -/// clock contract address to verify the sender of incoming ticks +/// clock module address to verify the sender of incoming ticks pub const CLOCK_ADDRESS: Item = Item::new("clock_address"); -/// liquid pooler contract address to forward the liquid staked funds to -pub const LP_ADDRESS: Item = Item::new("lp_address"); +/// next contract address to forward the liquid staked funds to +pub const NEXT_CONTRACT: Item = Item::new("next_contract"); + +pub const TRANSFER_AMOUNT: Item = Item::new("transfer_amount"); /// information needed for an ibc transfer to the remote chain pub const REMOTE_CHAIN_INFO: Item = Item::new("r_c_info"); @@ -18,9 +21,13 @@ pub const REMOTE_CHAIN_INFO: Item = Item::new("r_c_info"); pub const INTERCHAIN_ACCOUNTS: Map> = Map::new("interchain_accounts"); +/// formatting of stride autopilot message. +/// we use string match & replace with relevant fields to obtain the valid message. +pub const AUTOPILOT_FORMAT: Item = Item::new("autopilot_format"); + /// interchain transaction responses - ack/err/timeout state to query later -pub const ACKNOWLEDGEMENT_RESULTS: Map<(String, u64), AcknowledgementResult> = - Map::new("acknowledgement_results"); +// pub const ACKNOWLEDGEMENT_RESULTS: Map<(String, u64), AcknowledgementResult> = +// Map::new("acknowledgement_results"); pub const REPLY_ID_STORAGE: Item> = Item::new("reply_queue_id"); pub const SUDO_PAYLOAD: Map<(String, u64), Vec> = Map::new("sudo_payload"); pub const ERRORS_QUEUE: Map = Map::new("errors_queue"); diff --git a/v1/ls/src/suite_test/mod.rs b/v1/ls/src/suite_test/mod.rs new file mode 100644 index 00000000..7b881830 --- /dev/null +++ b/v1/ls/src/suite_test/mod.rs @@ -0,0 +1,2 @@ +mod suite; +mod tests; diff --git a/contracts/ls/src/suite_test/suite.rs b/v1/ls/src/suite_test/suite.rs similarity index 100% rename from contracts/ls/src/suite_test/suite.rs rename to v1/ls/src/suite_test/suite.rs diff --git a/contracts/ls/src/suite_test/tests.rs b/v1/ls/src/suite_test/tests.rs similarity index 100% rename from contracts/ls/src/suite_test/tests.rs rename to v1/ls/src/suite_test/tests.rs diff --git a/stride-covenant/17-08-2023-informal-timewave-covenants-audit.pdf b/v1/stride-covenant/17-08-2023-informal-timewave-covenants-audit.pdf similarity index 100% rename from stride-covenant/17-08-2023-informal-timewave-covenants-audit.pdf rename to v1/stride-covenant/17-08-2023-informal-timewave-covenants-audit.pdf diff --git a/stride-covenant/README.md b/v1/stride-covenant/README.md similarity index 100% rename from stride-covenant/README.md rename to v1/stride-covenant/README.md diff --git a/v1/stride-covenant/astroport/astroport_factory.wasm b/v1/stride-covenant/astroport/astroport_factory.wasm new file mode 100644 index 00000000..32215872 Binary files /dev/null and b/v1/stride-covenant/astroport/astroport_factory.wasm differ diff --git a/v1/stride-covenant/astroport/astroport_native_coin_registry.wasm b/v1/stride-covenant/astroport/astroport_native_coin_registry.wasm new file mode 100644 index 00000000..b3e79d9c Binary files /dev/null and b/v1/stride-covenant/astroport/astroport_native_coin_registry.wasm differ diff --git a/v1/stride-covenant/astroport/astroport_pair.wasm b/v1/stride-covenant/astroport/astroport_pair.wasm new file mode 100644 index 00000000..5d98eb87 Binary files /dev/null and b/v1/stride-covenant/astroport/astroport_pair.wasm differ diff --git a/v1/stride-covenant/astroport/astroport_pair_stable.wasm b/v1/stride-covenant/astroport/astroport_pair_stable.wasm new file mode 100644 index 00000000..47caf760 Binary files /dev/null and b/v1/stride-covenant/astroport/astroport_pair_stable.wasm differ diff --git a/v1/stride-covenant/astroport/astroport_token.wasm b/v1/stride-covenant/astroport/astroport_token.wasm new file mode 100644 index 00000000..6663163a Binary files /dev/null and b/v1/stride-covenant/astroport/astroport_token.wasm differ diff --git a/v1/stride-covenant/astroport/astroport_whitelist.wasm b/v1/stride-covenant/astroport/astroport_whitelist.wasm new file mode 100644 index 00000000..0d6fadf8 Binary files /dev/null and b/v1/stride-covenant/astroport/astroport_whitelist.wasm differ diff --git a/stride-covenant/justfile b/v1/stride-covenant/justfile similarity index 83% rename from stride-covenant/justfile rename to v1/stride-covenant/justfile index d9027eaf..7a33d842 100644 --- a/stride-covenant/justfile +++ b/v1/stride-covenant/justfile @@ -4,7 +4,15 @@ build: gen: build gen-schema gen-schema: - ./../scripts/schema.sh + START_DIR=$(pwd); \ + for f in ./packages/*; do \ + echo "generating schema"; \ + cd "$f"; \ + CMD="cargo run --example schema"; \ + eval ${CMD} > /dev/null; \ + rm -rf ./schema/raw; \ + cd "$START_DIR"; \ + done test: cargo test @@ -13,7 +21,7 @@ lint: cargo +nightly clippy --all-targets -- -D warnings && cargo +nightly fmt --all --check optimize: - ./../scripts/optimize.sh + ./optimize.sh simtest: optimize if [[ $(uname -m) =~ "arm64" ]]; then \ diff --git a/scripts/optimize.sh b/v1/stride-covenant/optimize.sh similarity index 100% rename from scripts/optimize.sh rename to v1/stride-covenant/optimize.sh diff --git a/stride-covenant/stride-contracts-overview.png b/v1/stride-covenant/stride-contracts-overview.png similarity index 100% rename from stride-covenant/stride-contracts-overview.png rename to v1/stride-covenant/stride-contracts-overview.png diff --git a/stride-covenant/tests/interchaintest/README.md b/v1/stride-covenant/tests/interchaintest/README.md similarity index 100% rename from stride-covenant/tests/interchaintest/README.md rename to v1/stride-covenant/tests/interchaintest/README.md diff --git a/stride-covenant/tests/interchaintest/connection_helpers.go b/v1/stride-covenant/tests/interchaintest/connection_helpers.go similarity index 100% rename from stride-covenant/tests/interchaintest/connection_helpers.go rename to v1/stride-covenant/tests/interchaintest/connection_helpers.go diff --git a/stride-covenant/tests/interchaintest/genesis_helpers.go b/v1/stride-covenant/tests/interchaintest/genesis_helpers.go similarity index 100% rename from stride-covenant/tests/interchaintest/genesis_helpers.go rename to v1/stride-covenant/tests/interchaintest/genesis_helpers.go diff --git a/stride-covenant/tests/interchaintest/ics_test.go b/v1/stride-covenant/tests/interchaintest/ics_test.go similarity index 94% rename from stride-covenant/tests/interchaintest/ics_test.go rename to v1/stride-covenant/tests/interchaintest/ics_test.go index 4067487d..b75f9f48 100644 --- a/stride-covenant/tests/interchaintest/ics_test.go +++ b/v1/stride-covenant/tests/interchaintest/ics_test.go @@ -1175,8 +1175,8 @@ func TestICS(t *testing.T) { } _, _, err := cosmosNeutron.Exec(ctx, cmd, nil) require.NoError(t, err) - - err = testutil.WaitForBlocks(ctx, 5, atom, neutron, stride) + // print("\n clock response: ", string(resp), "\n") + err = testutil.WaitForBlocks(ctx, 10, atom, neutron, stride) require.NoError(t, err, "failed to wait for blocks") var response ContractStateQueryResponse @@ -1246,6 +1246,8 @@ func TestICS(t *testing.T) { }) t.Run("Query depositor ICA", func(t *testing.T) { + // Give atom some time before querying + err = testutil.WaitForBlocks(ctx, 10, atom, neutron, stride) var response QueryResponse err = cosmosNeutron.QueryContract(ctx, depositorContractAddress, DepositorICAAddressQuery{}, &response) require.NoError(t, err, "failed to query ICA account address") @@ -1312,6 +1314,8 @@ func TestICS(t *testing.T) { for i := 1; i < maxTicks; i++ { print("\n Ticking clock ", i, " of ", maxTicks) tickClock() + err = testutil.WaitForBlocks(ctx, 2, atom, neutron, stride) + require.NoError(t, err, "failed to wait for blocks") } // now we restart the relayer and try again @@ -1326,6 +1330,9 @@ func TestICS(t *testing.T) { print("\n Ticking clock ", i, " of ", maxTicks) tickClock() + err = testutil.WaitForBlocks(ctx, 5, atom, neutron, stride) + require.NoError(t, err, "failed to wait for blocks") + strideICABal, err := stride.GetBalance(ctx, strideICAAddress, "stuatom") require.NoError(t, err, "failed to query ICA balance") print("\n stride ica bal: ", strideICABal, "\n") @@ -1355,6 +1362,55 @@ func TestICS(t *testing.T) { transferCmd := getLsPermisionlessTransferMsg(strideRedemptionRate * atomToLiquidStake) cosmosNeutron.Exec(ctx, transferCmd, nil) + // switch off the relayer + // stopRelayer() + // trigger sudo_timeout which rolls back the state + // cosmosNeutron.Exec(ctx, transferCmd, nil) + + // err = testutil.WaitForBlocks(ctx, 40, atom, neutron, stride) + // require.NoError(t, err, "failed to wait for blocks") + + // maxTicks := 10 + // // do some ticks with relayer switched off + // for i := 1; i < maxTicks; i++ { + // print("\n Ticking clock ", i, " of ", maxTicks) + // tickClock() + // err = testutil.WaitForBlocks(ctx, 2, atom, neutron, stride) + // require.NoError(t, err, "failed to wait for blocks") + // } + + // now we restart the relayer and go again + // startRelayer() + + // err = testutil.WaitForBlocks(ctx, 30, atom, neutron, stride) + // require.NoError(t, err, "failed to wait for blocks") + + // r.FlushPackets(ctx, eRep, neutronStrideIBCPath, strideNeutronChannelId) + // r.FlushPackets(ctx, eRep, neutronStrideIBCPath, neutronStrideChannelId) + // r.FlushAcknowledgements(ctx, eRep, neutronStrideIBCPath, strideNeutronChannelId) + // r.FlushAcknowledgements(ctx, eRep, neutronStrideIBCPath, neutronStrideChannelId) + // err = testutil.WaitForBlocks(ctx, 15, atom, neutron, stride) + // require.NoError(t, err, "failed to wait for blocks") + + // _, lsState, _ := tickClock() + // // require.EqualValues(t, "instantiated", lsState, "ls did not rollback the state") + + // maxTicks := 20 + // for i := 1; i < maxTicks; i++ { + // _, lsState, _ = tickClock() + // err = testutil.WaitForBlocks(ctx, 5, atom, neutron, stride) + // require.NoError(t, err, "failed to wait for blocks") + // if lsState == "i_c_a_created" { + // break + // } + // } + + // retry the transfer again + // print("\n attempting permisionless transfer\n") + // resp, _, err := cosmosNeutron.Exec(ctx, transferCmd, nil) + // require.NoError(t, err) + // print("\ntransfer response: ", string(resp), "\n") + err = testutil.WaitForBlocks(ctx, 10, atom, neutron, stride) require.NoError(t, err) @@ -1366,8 +1422,12 @@ func TestICS(t *testing.T) { require.NoError(t, err, "failed to query ICA balance") print("\n lp statom bal: ", lpStatomBalance, "\n") + // err = testutil.WaitForBlocks(ctx, 10, atom, neutron, stride) + // require.NoError(t, err) + require.Equal(t, int64(0), strideICABal) require.Equal(t, int64(strideRedemptionRate*atomToLiquidStake), lpStatomBalance) + }) queryLpTokenBalance := func(token string, addr string) string { @@ -1402,14 +1462,35 @@ func TestICS(t *testing.T) { require.NoError(t, err, "failed to query ICA balance") print("\n lp statom bal: ", lpStatomBalance, "\n") + // holderbalance, err := cosmosNeutron.GetBalance() + + /* + let holder_balances = suite.query_cw20_bal( + pairinfo.liquidity_token.to_string(), + suite.holder_addr.to_string(), + ); + + pub fn query_cw20_bal(&self, token: String, addr: String) -> cw20::BalanceResponse { + self.app + .wrap() + .query_wasm_smart(token, &cw20::Cw20QueryMsg::Balance { address: addr }) + .unwrap() + } + */ + // cosmosNeutron.QueryContract(ctx, liquiditytokenaddr, Cw20QueryMsg) if lpAtomBalance == int64(0) && lpStatomBalance == int64(0) { break } + err = testutil.WaitForBlocks(ctx, 5, neutron) + require.NoError(t, err, "failed to wait for blocks") tick += 1 + // queryAllLpHolders(liquidityTokenAddress) } // fail if we haven't transferred funds in under maxTicks require.LessOrEqual(t, tick, maxTicks) + // TODO check if they are in holder + }) t.Run("holder can withdraw liquidity", func(t *testing.T) { diff --git a/stride-covenant/tests/interchaintest/types.go b/v1/stride-covenant/tests/interchaintest/types.go similarity index 100% rename from stride-covenant/tests/interchaintest/types.go rename to v1/stride-covenant/tests/interchaintest/types.go