diff --git a/.cargo/config b/.cargo/config.toml similarity index 100% rename from .cargo/config rename to .cargo/config.toml diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..cdeb8792 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +## 👀 What is Changed? + + * + +## 📣 API Changes + + * + +## 📝 Notes + + * + +## ☑️ Checklist + - [ ] The code compiles successfully + - [ ] I've run `cargo clippy` to lint and check code style + - [ ] I've written the test code and runs successfully + - [ ] I've updated API docs and noticed to the co-workers for updates diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml new file mode 100644 index 00000000..787cddcc --- /dev/null +++ b/.github/workflows/release-artifacts.yml @@ -0,0 +1,37 @@ +name: Release Artifacts + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10 + - "v[0-9]+.[0-9]+.[0-9]+-rc*" # Push events to matching v*, i.e. v1.0-rc1, v20.15.10-rc5 + +jobs: + release-artifacts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install latest stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + components: rustfmt, clippy + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Generate Cargo.lock + run: | + cargo build --workspace + cargo fetch --verbose + - name: Build Artifacts + 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.15.1 + tar -zcvf cosmwasm-artifacts.tar.gz artifacts + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: cosmwasm-artifacts.tar.gz + body_path: CHANGELOG.md diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..58f0e6fc --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,61 @@ +name: Formatting Check & Test + +on: + pull_request: + push: + branches: + - main + - develop + - "release/*" + +jobs: + clippy: + name: Actions - clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy + profile: minimal + override: true + - run: cargo fetch --verbose + - run: cargo clippy --all --all-targets -- -D warnings + + rustfmt: + name: Actions - rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + components: rustfmt + profile: minimal + override: true + - run: cargo +nightly fmt -- --check + + unit-test: + name: Actions - unit test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ ubuntu-latest ] + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + - run: cargo fetch --verbose + - run: cargo build + - run: cargo test --verbose --all + env: + RUST_BACKTRACE: 1 diff --git a/.gitignore b/.gitignore index 4b75762a..a362c29d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ scripts/data/ scripts/keys/ # Misc notes -notes.txt +/notes*.txt +/notes +/prepared-tx.json \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..4997e6fb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,4 @@ +# changelog + +## 3.0.17 +* started changelog \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b54699a6..74f8027e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,17 +2,23 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + [[package]] name = "base16ct" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.13.0" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -20,6 +26,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "block-buffer" version = "0.9.0" @@ -29,12 +41,33 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + [[package]] name = "cfg-if" version = "1.0.0" @@ -43,47 +76,76 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "const-oid" -version = "0.7.1" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cosmwasm-crypto" -version = "1.0.0" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" +checksum = "58535cbcd599b3c193e3967c8292fe1dbbb5de7c2a2d87380661091dd4744044" dependencies = [ - "digest", + "digest 0.10.7", "ed25519-zebra", "k256", - "rand_core 0.6.3", + "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.0.0" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" +checksum = "a8e07de16c800ac82fd188d055ecdb923ead0cf33960d3350089260bb982c09f" dependencies = [ - "syn", + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d388adfa9cb449557a92e9318121ac1a481fc4f599213b03a5b62699b403b4" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2411b389e56e6484f81ba955b758d02522d620c98fc960c4bd2251d48b7aa19f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] name = "cosmwasm-std" -version = "1.0.0" +version = "1.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" +checksum = "c21fde95ccd20044a23c0ac6fd8c941f3e8c158169dc94b5aa6491a2d9551a8d" dependencies = [ "base64", + "bech32", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", + "derivative", "forward_ref", + "hex", "schemars", "serde", "serde-json-wasm", + "sha2 0.10.6", + "static_assertions", "thiserror", - "uint", ] [[package]] @@ -95,32 +157,26 @@ dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" -version = "0.3.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", "zeroize", ] [[package]] -name = "crypto-mac" -version = "0.11.1" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "subtle", + "typenum", ] [[package]] @@ -130,12 +186,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", - "digest", + "digest 0.9.0", "rand_core 0.5.1", "subtle", "zeroize", ] +[[package]] +name = "cw-address-like" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451a4691083a88a3c0630a8a88799e9d4cd6679b7ce8ff22b8da2873ff31d380" +dependencies = [ + "cosmwasm-std", +] + +[[package]] +name = "cw-item-set" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea5a233bd67babedbe96a514178a64b0c597f1f38bc474fa8d63e3f26bdceb2" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 1.2.0", +] + +[[package]] +name = "cw-ownable" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093dfb4520c48b5848274dd88ea99e280a04bc08729603341c7fb0d758c74321" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-address-like", + "cw-ownable-derive", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.1", + "thiserror", +] + +[[package]] +name = "cw-ownable-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d3bf2e0f341bb6cc100d7d441d31cf713fbd3ce0c511f91e79f14b40a889af" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cw-storage-plus" version = "0.13.4" @@ -147,6 +248,17 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-storage-plus" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-utils" version = "0.13.4" @@ -159,6 +271,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 1.1.2", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.13.4" @@ -166,9 +293,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" dependencies = [ "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", "schemars", + "semver", "serde", + "thiserror", ] [[package]] @@ -178,7 +320,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" dependencies = [ "cosmwasm-std", - "cw-utils", + "cw-utils 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw20" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.1", "schemars", "serde", ] @@ -190,22 +345,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0306e606581f4fb45e82bcbb7f0333179ed53dd949c6523f01a99b4bfc1475a0" dependencies = [ "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw2", - "cw20", + "cw-storage-plus 0.13.4", + "cw-utils 0.13.4", + "cw2 0.13.4", + "cw20 0.13.4", "schemars", "serde", "thiserror", ] +[[package]] +name = "cw20-base" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ad79e86ea3707229bf78df94e08732e8f713207b4a77b2699755596725e7d9" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "cw20 1.1.2", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "der" -version = "0.5.1" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -217,6 +401,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.3", + "const-oid", + "crypto-common", + "subtle", +] + [[package]] name = "dyn-clone" version = "1.0.5" @@ -225,14 +421,16 @@ checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" [[package]] name = "ecdsa" -version = "0.13.4" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", + "spki", ] [[package]] @@ -243,26 +441,33 @@ checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" dependencies = [ "curve25519-dalek", "hex", - "rand_core 0.6.3", + "rand_core 0.6.4", "serde", - "sha2", + "sha2 0.9.9", "thiserror", "zeroize", ] +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "elliptic-curve" -version = "0.11.12" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "der", + "digest 0.10.7", "ff", "generic-array", "group", - "rand_core 0.6.3", + "pkcs8", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -270,11 +475,11 @@ dependencies = [ [[package]] name = "ff" -version = "0.11.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -284,14 +489,26 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +[[package]] +name = "funds-distributor-api" +version = "1.0.0" +source = "git+https://github.com/terra-money/enterprise-contracts.git?branch=main#60f2fb438d283dc8dd28b94f54f2a49ffd6b8678" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw20 1.1.2", + "thiserror", +] + [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -318,12 +535,12 @@ dependencies = [ [[package]] name = "group" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -335,12 +552,20 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hmac" -version = "0.11.0" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "itertools" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ - "crypto-mac", - "digest", + "either", ] [[package]] @@ -351,22 +576,29 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "k256" -version = "0.10.4" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sec1", - "sha2", + "once_cell", + "sha2 0.10.6", + "signature", ] [[package]] name = "libc" -version = "0.2.124" +version = "0.2.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" + +[[package]] +name = "once_cell" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -374,31 +606,229 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "osmosis-std-derive" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4d482a16be198ee04e0f94e10dd9b8d02332dcf33bc5ea4b255e7e25eedc5df" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pfc-dust-collector" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9debbb83cefcd272c751c07f984f75f70be4195e87dddbdb87247bd969d51436" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable", + "cw-ownable-derive", + "pfc-dust-collector-derive", + "pfc-whitelist", + "pfc-whitelist-derive", + "schemars", + "serde", +] + +[[package]] +name = "pfc-dust-collector-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d72f8e98168a562a454569e5cc70070c4408d1a8f7828efce41fd496e52fb36d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pfc-fee-split" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659bc95805fb3ab45d6db2eda7ee71bfe5fa952e4705146e0ce3d8113daaab57" +dependencies = [ + "cosmwasm-std", + "cw20 1.1.2", + "schemars", + "serde", +] + +[[package]] +name = "pfc-fee-split" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d362330ac0f385ff8d58ae80075dabaaeddc148a5156e041c36ee743fe96a3a8" +dependencies = [ + "cosmwasm-std", + "cw20 1.1.2", + "schemars", + "serde", +] + +[[package]] +name = "pfc-steak" +version = "3.0.20" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw20 1.1.2", + "cw20-base 1.1.2", + "schemars", + "serde", +] + +[[package]] +name = "pfc-steak-hub" +version = "3.0.20" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base 1.1.2", + "funds-distributor-api", + "pfc-fee-split 0.2.5", + "pfc-steak", + "serde", +] + +[[package]] +name = "pfc-steak-hub-tf" +version = "3.0.20" +dependencies = [ + "cosmwasm-std", + "cw-item-set", + "cw-ownable", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "osmosis-std-derive", + "pfc-dust-collector", + "pfc-fee-split 1.5.0", + "pfc-steak", + "prost", + "prost-types", + "protobuf", + "schemars", + "serde", +] + +[[package]] +name = "pfc-steak-token" +version = "3.0.20" +dependencies = [ + "cosmwasm-std", + "cw20 0.13.4", + "cw20-base 0.13.4", +] + +[[package]] +name = "pfc-whitelist" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217870c932a72a63c52c9126792a5bd29773b1318b65635d900091c8f54d3a5c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "pfc-whitelist-derive", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "pfc-whitelist-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8cd39f1b0792d2a383d072795fc88ee82bc671374cac0a1ec229ff8abe5da1" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "pkcs8" -version = "0.8.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", - "zeroize", ] [[package]] name = "proc-macro2" -version = "1.0.37" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost", +] + +[[package]] +name = "protobuf" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +checksum = "3018844a02746180074f621e847703737d27d89d7f0721a7a4da317f88b16385" dependencies = [ - "unicode-xid", + "bytes", + "once_cell", + "protobuf-support", + "thiserror", +] + +[[package]] +name = "protobuf-support" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf96d872914fcda2b66d66ea3fff2be7c66865d31c7bb2790cff32c0e714880" +dependencies = [ + "thiserror", ] [[package]] name = "quote" -version = "1.0.18" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -414,22 +844,21 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.6", ] [[package]] name = "rfc6979" -version = "0.1.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "crypto-bigint", "hmac", - "zeroize", + "subtle", ] [[package]] @@ -440,9 +869,9 @@ checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "schemars" -version = "0.8.8" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b5a3c80cea1ab61f4260238409510e814e38b4b563c06044edf91e7dc070e3" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -452,22 +881,23 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.8" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.55", ] [[package]] name = "sec1" -version = "0.2.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ + "base16ct", "der", "generic-array", "pkcs8", @@ -475,44 +905,50 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + [[package]] name = "serde" -version = "1.0.136" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] name = "serde_derive_internals" -version = "0.25.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -532,28 +968,39 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "signature" -version = "1.3.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", - "rand_core 0.6.3", + "digest 0.10.7", + "rand_core 0.6.4", ] [[package]] name = "spki" -version = "0.5.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", @@ -565,37 +1012,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "steak" -version = "2.0.0" -dependencies = [ - "cosmwasm-std", - "cw20", - "schemars", - "serde", -] - -[[package]] -name = "steak-hub" -version = "2.0.0" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "cw20", - "cw20-base", - "serde", - "steak", -] - -[[package]] -name = "steak-token" -version = "2.0.0" -dependencies = [ - "cosmwasm-std", - "cw20", - "cw20-base", -] - [[package]] name = "subtle" version = "2.4.1" @@ -604,33 +1020,44 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.91" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.55", ] [[package]] @@ -640,22 +1067,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] -name = "uint" -version = "0.9.3" +name = "unicode-ident" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "version_check" @@ -677,6 +1092,6 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "zeroize" -version = "1.4.3" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index c2f449b0..fda51ba4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,9 @@ [workspace] -members = ["contracts/*", "packages/*"] +members = ["contracts/token", "contracts/hub", "contracts/hub-tf", + "packages/steak"] +resolver = '1' -[profile.release.package.steak] +[profile.release.package.pfc-steak] opt-level = 3 debug = false debug-assertions = false @@ -9,6 +11,12 @@ codegen-units = 1 incremental = false [profile.release] +opt-level = 3 +debug = false rpath = false lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false overflow-checks = true diff --git a/README.md b/README.md index ac96c10d..376ff53e 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ Terra liquid staking derivative. Of the community, by the community, for the community. -A previous version ([v1.0.0-rc0](https://github.com/st4k3h0us3/steak-contracts/releases/tag/v1.0.0-rc0)) of Steak was audited by [SCV Security](https://twitter.com/TerraSCV) ([link](https://github.com/SCV-Security/PublicReports/blob/main/CW/St4k3h0us3/St4k3h0us3%20-%20Steak%20Contracts%20Audit%20Review%20-%20%20v1.0.pdf)). - +Previous versions of Steak have been audited: +* [v1.0.0-rc0](https://github.com/st4k3h0us3/steak-contracts/releases/tag/v1.0.0-rc0) by [SCV Security](https://twitter.com/TerraSCV) ([link](https://github.com/SCV-Security/PublicReports/blob/main/CW/St4k3h0us3/St4k3h0us3%20-%20Steak%20Contracts%20Audit%20Review%20-%20%20v1.0.pdf)). +* [v3.0.3-cargo](https://github.com/PFC-developer/steak-contracts/tree/v3.0.3-cargo) by [SCV](https://github.com/SCV-Security/PublicReports/blob/a2d955cac6398f78a3cd067a04bda147ec7ba5c3/CW%2FPFC%2FPFC%20-%20Steak%20%26%20Fee%20Split%20-%20Audit%20Report%20v1.0.pdf) ## Contracts | Contract | Description | @@ -15,12 +16,12 @@ A previous version ([v1.0.0-rc0](https://github.com/st4k3h0us3/steak-contracts/r ### Mainnet (phoenix-1) -| Contract | Address | -| ------------------- | --------- | -| Steak Hub | [`tbd`]() | -| Steak Token | [`tbd`]() | -| STEAK-LUNA Pair | [`tbd`]() | -| STEAK-LUNA LP Token | [`tbd`]() | +| Contract | Address | +| ------------------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Steak Hub | [`terra12e4v50xl33fnwkzltz9vu565snlmx65vdrk8e2644km09myewr8q538psc`](https://finder.terra.money/mainnet/address/terra12e4v50xl33fnwkzltz9vu565snlmx65vdrk8e2644km09myewr8q538psc) | +| Steak Token | [`terra1xumzh893lfa7ak5qvpwmnle5m5xp47t3suwwa9s0ydqa8d8s5faqn6x7al`](https://finder.terra.money/mainnet/address/terra1xumzh893lfa7ak5qvpwmnle5m5xp47t3suwwa9s0ydqa8d8s5faqn6x7al) | +| STEAK-LUNA Pair | [`terra1jynmf6gteg4rd03ztldan5j2dp78su4tc3hfvkve8dl068c2yppsk5uszc`](https://finder.terra.money/mainnet/address/terra1jynmf6gteg4rd03ztldan5j2dp78su4tc3hfvkve8dl068c2yppsk5uszc) | +| STEAK-LUNA LP Token | [`tbd`]() | ### Testnet (pisco-1) diff --git a/build_arm.sh b/build_arm.sh new file mode 100755 index 00000000..346acbc7 --- /dev/null +++ b/build_arm.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +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.12.13 diff --git a/build_x86.sh b/build_x86.sh new file mode 100755 index 00000000..c699b58d --- /dev/null +++ b/build_x86.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +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.15.1 diff --git a/contracts/hub-tf/Cargo.toml b/contracts/hub-tf/Cargo.toml new file mode 100644 index 00000000..ff977d2f --- /dev/null +++ b/contracts/hub-tf/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "pfc-steak-hub-tf" +version = "3.0.20" +authors = ["larry ", "PFC "] +edition = "2018" +license = "GPL-3.0-or-later" +repository = "https://github.com/pfc-developer/steak-contracts" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] + +[dependencies] +#cosmwasm-std = { version = "1.2.1", features=["iterator","stargate","cosmwasm_1_1","staking"]} +cosmwasm-std = { version = "1.5.8", features = ["iterator", "stargate", "staking"] } + +cw2 = "1.1.2" +#cw20 = "1.0.0" +#cw20-base = { version = "1.0.0", features = ["library"] } +cw-storage-plus = "1.2.0" +cw-ownable = "0.5.1" +cw-item-set = "0.7.1" +prost = { version = "0.12.3", default-features = false, features = ["prost-derive"] } +prost-types = { version = "0.12.3", default-features = false } +schemars = "0.8.21" +pfc-steak = { path = "../../packages/steak" } +serde = { version = "1.0.210", default-features = false, features = ["derive"] } +pfc-fee-split = "1.5.0" +pfc-dust-collector = "0.1.0" +#{ path="../../packages/pfc-dust-collector" } +osmosis-std-derive = "0.15.3" +[dev-dependencies] +protobuf = { version = "3.6.0", features = ["with-bytes"] } \ No newline at end of file diff --git a/contracts/hub-tf/README.md b/contracts/hub-tf/README.md new file mode 100644 index 00000000..babc90be --- /dev/null +++ b/contracts/hub-tf/README.md @@ -0,0 +1,45 @@ +# Steak Hub + +Steak Hub contract manages the bonding/unbonding of Luna, minting/burning of Steak, and reinvestment of staking rewards. + +## Overview + +### Exchange rate + +Unlike [Lido's stETH](https://github.com/lidofinance/lido-dao/tree/master/contracts/0.4.24), the Steak token does not rebase; instead, the exchange rate between Luna and Steak increases (i.e. each Steak becomes worth more Luna) as staking rewards are harvested, and reduces if validators are slashed. + +The exchange rate, as defined by the amount of `uluna` redeemable per `usteak`, is calculated as + +```plain +exchange_rate = total_uluna_staked / total_usteak_supply +``` + +### Unlocked coins + +Unlocked coin refers to coins held by the Steak Hub contract (referred to as "the contract" hereafter) that can be reinvested. The contract tracks the amounts of unlocked coins using a `Vec` variable stored under the `unlocked_coins` key. + +Each time the Hub contract delegates to or undelegates from a validator, the claimable staking rewards are automatically transferred to the contract. The amounts of coins transferred are recorded in the `coin_received` event. When handling the response, the contract parses this event and updates the `unlocked_coins` variable accordingly. + +When harvesting, the contract needs to swap Terra stablecoins into Luna. the contract offers all unlocked coins that have exchange rates defined against Luna to be swapped, and deduct them from `unlocked_coins` accordingly. When handling the response, the contract parses the `swap` event and increments the unlocked Luna amount. + +### Unbonding + +Cosmos chains, by default, has a limit of 7 undelegations at a time per validator-delegator pair. In order to support unbonding requests from many users, the contract needs to bundle unbonding requests together and submit them in batches. + +![illustration-of-unbonding-queue](./unbonding-queue.png) + +For mainnet, the contract will submit a batch every 3 days, such that there are at most 7 undelegations at a time with each validator. This 3 day interval is defined by the `epoch_period` parameter. + +During the 3 day period, the contract accepts unbonding requests from users and store them in an `IndexedMap` data structure under the `unbond_requests` key, and the aggregated properties of the pending batch under the `pending_batch` key. Each user's share in the batch is proportional to the amount of Steak tokens the user requests to burn. + +At the end of the 3 day period, anyone can invoke the `ExecuteMsg::SubmitUnbond` function to submit the pending batch to be unbonded. The contract calculates the amount of Luna to unbond based on the Luna/Steak exchange rate at the time, burns the Steak tokens, and initiates undelegations with the validators. + +At the end of the following 21 day unbonding period, the user can invoke the `ExecuteMsg::WithdrawUnbonded` function. The contract pulls all of the user's unclaimed unbonding requests, and refunds appropriate amounts of Luna based on the each request's share in that batch, to the user. + +## Reference + +Similar projects: + +* [Lido - stLUNA](https://github.com/lidofinance/lido-terra-contracts) +* [Stader - LunaX](https://github.com/stader-labs/stader-liquid-token) +* [Staking derivatives (dSCRT)](https://github.com/Cashmaney/SecretStaking) \ No newline at end of file diff --git a/contracts/hub-tf/src/contract.rs b/contracts/hub-tf/src/contract.rs new file mode 100644 index 00000000..b29f7438 --- /dev/null +++ b/contracts/hub-tf/src/contract.rs @@ -0,0 +1,228 @@ +use std::convert::TryInto; + +use cosmwasm_std::{ + Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, StdResult, entry_point, + to_json_binary, +}; +use cw2::{ContractVersion, get_contract_version, set_contract_version}; +use pfc_steak::{ + hub::{CallbackMsg, MigrateMsg, QueryMsg}, + hub_tf::{ExecuteMsg, InstantiateMsg, TokenFactoryType}, +}; + +use crate::{execute, queries, state::State}; + +//use crate::helpers::{ unwrap_reply}; + +/// Contract name that is used for migration. +pub const CONTRACT_NAME: &str = "steak-hub-tf"; +/// Contract version that is used for migration. +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const REPLY_INSTANTIATE_TOKEN: u64 = 1; +pub const REPLY_REGISTER_RECEIVED_COINS: u64 = 2; +pub const SPECIAL_SEND_MESSAGE_TO_TRANSFER: &str = "PFC_TRANSFER_NOT_SEND"; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + execute::instantiate(deps, env, msg) +} + +#[entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + let api = deps.api; + match msg { + ExecuteMsg::Bond { + receiver, + exec_msg, + } => execute::bond( + deps, + env, + receiver.map(|s| api.addr_validate(&s)).transpose()?.unwrap_or(info.sender), + info.funds, + exec_msg, + true, + ), + ExecuteMsg::Unbond { + receiver, + } => execute::queue_unbond( + deps, + env, + receiver.map(|s| api.addr_validate(&s)).transpose()?.unwrap_or(info.sender), + info.funds, + ), + ExecuteMsg::WithdrawUnbonded { + receiver, + } => execute::withdraw_unbonded( + deps, + env, + info.sender.clone(), + receiver.map(|s| api.addr_validate(&s)).transpose()?.unwrap_or(info.sender), + ), + ExecuteMsg::WithdrawUnbondedAdmin { + address, + } => execute::withdraw_unbonded_admin(deps, env, info.sender, api.addr_validate(&address)?), + ExecuteMsg::AddValidator { + validator, + } => execute::add_validator(deps, info.sender, validator), + ExecuteMsg::RemoveValidator { + validator, + } => execute::remove_validator(deps, env, info.sender, validator), + ExecuteMsg::RemoveValidatorEx { + validator, + } => execute::remove_validator_ex(deps, env, info.sender, validator), + ExecuteMsg::Redelegate { + validator_from, + validator_to, + } => execute::redelegate(deps, env, info.sender, validator_from, validator_to), + + ExecuteMsg::TransferOwnership { + new_owner, + } => execute::transfer_ownership(deps, info.sender, new_owner), + ExecuteMsg::AcceptOwnership {} => execute::accept_ownership(deps, info.sender), + ExecuteMsg::Harvest {} => execute::harvest(deps, env), + ExecuteMsg::Rebalance { + minimum, + } => execute::rebalance(deps, env, minimum), + ExecuteMsg::Reconcile {} => execute::reconcile(deps, env), + ExecuteMsg::SubmitBatch {} => execute::submit_batch(deps, env), + ExecuteMsg::TransferFeeAccount { + fee_account_type, + new_fee_account, + } => execute::transfer_fee_account(deps, info.sender, fee_account_type, new_fee_account), + ExecuteMsg::UpdateFee { + new_fee, + } => execute::update_fee(deps, info.sender, new_fee), + ExecuteMsg::Callback(callback_msg) => callback(deps, env, info, callback_msg), + ExecuteMsg::PauseValidator { + validator, + } => execute::pause_validator(deps, env, info.sender, validator), + ExecuteMsg::UnPauseValidator { + validator, + } => execute::unpause_validator(deps, env, info.sender, validator), + ExecuteMsg::SetUnbondPeriod { + unbond_period, + } => execute::set_unbond_period(deps, env, info.sender, unbond_period), + + ExecuteMsg::SetDustCollector { + dust_collector, + } => execute::set_dust_collector(deps, env, info.sender, dust_collector), + ExecuteMsg::CollectDust { + max_tokens, + } => { + let max_tokens_usize_r = max_tokens.try_into(); + if let Ok(max_tokens_usize) = max_tokens_usize_r { + execute::collect_dust(deps, env, max_tokens_usize) + } else { + Err(StdError::generic_err("max_tokens too large")) + } + }, + ExecuteMsg::ReturnDenom {} => { + execute::bond(deps, env, info.sender, info.funds, None, false) + }, + ExecuteMsg::SetBaseDenom { + new_denom, + } => execute::set_base_denom(deps, info.sender, new_denom), + ExecuteMsg::ChangeTokenFactory { + token_factory_type, + } => execute::change_token_factory(deps, info.sender, &token_factory_type), + } +} + +fn callback( + deps: DepsMut, + env: Env, + info: MessageInfo, + callback_msg: CallbackMsg, +) -> StdResult { + if env.contract.address != info.sender { + return Err(StdError::generic_err("callbacks can only be invoked by the contract itself")); + } + + match callback_msg { + CallbackMsg::Reinvest {} => execute::reinvest(deps, env), + } +} + +#[entry_point] +pub fn reply(_deps: DepsMut, _env: Env, reply: Reply) -> StdResult { + match reply.id { + REPLY_REGISTER_RECEIVED_COINS => Ok(Response::default()), + _ => Err(StdError::generic_err(format!( + "invalid reply id: {} {:?}", + reply.id, reply.result + ))), + } +} + +#[entry_point] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_json_binary(&queries::config(deps)?), + QueryMsg::State {} => to_json_binary(&queries::state(deps, env)?), + QueryMsg::PendingBatch {} => to_json_binary(&queries::pending_batch(deps)?), + QueryMsg::PreviousBatch(id) => to_json_binary(&queries::previous_batch(deps, id)?), + QueryMsg::PreviousBatches { + start_after, + limit, + } => to_json_binary(&queries::previous_batches(deps, start_after, limit)?), + QueryMsg::UnbondRequestsByBatch { + id, + start_after, + limit, + } => to_json_binary(&queries::unbond_requests_by_batch(deps, id, start_after, limit)?), + QueryMsg::UnbondRequestsByUser { + user, + start_after, + limit, + } => to_json_binary(&queries::unbond_requests_by_user(deps, user, start_after, limit)?), + QueryMsg::Unreconciled {} => to_json_binary(&queries::previous_batches_unreconciled(deps)?), + } +} + +#[entry_point] +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { + let contract_version = get_contract_version(deps.storage).unwrap_or_else(|_| ContractVersion { + contract: "steak-hub-tf".to_string(), + version: "0".to_string(), + }); + match contract_version.contract.as_ref() { + #[allow(clippy::single_match)] + "steak-hub-tf" => match contract_version.version.as_ref() { + #[allow(clippy::single_match)] + "0" => {}, + "3.0.1" | "3.0.2" => { + let state = State::default(); + let kuji = state.kuji_token_factory.load(deps.storage)?; + if kuji { + state.token_factory_type.save(deps.storage, &TokenFactoryType::Kujira)? + } else { + state.token_factory_type.save(deps.storage, &TokenFactoryType::CosmWasm)? + } + }, + _ => {}, + }, + _ => { + return Err(StdError::generic_err("contract name is not the same. aborting {}")); + }, + } + /* + let state = State::default(); + + state.max_fee_rate.save(deps.storage,&Decimal::from_ratio(10u32,100u32))?; + state.fee_rate.save(deps.storage,&Decimal::from_ratio(10u32,100u32))?; + + */ + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(Response::new() + .add_attribute("previous_contract_name", &contract_version.contract) + .add_attribute("previous_contract_version", &contract_version.version) + .add_attribute("new_contract_name", CONTRACT_NAME) + .add_attribute("new_contract_version", CONTRACT_VERSION)) +} diff --git a/contracts/hub-tf/src/execute.rs b/contracts/hub-tf/src/execute.rs new file mode 100644 index 00000000..7b247499 --- /dev/null +++ b/contracts/hub-tf/src/execute.rs @@ -0,0 +1,1184 @@ +use std::{collections::HashSet, str::FromStr}; + +use cosmwasm_std::{ + Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, DistributionMsg, Env, Event, Order, ReplyOn, + Response, StdError, StdResult, SubMsg, Uint128, WasmMsg, to_json_binary, +}; +use pfc_steak::{ + DecimalCheckedOps, + hub::{Batch, CallbackMsg, FeeType, PendingBatch, UnbondRequest}, + hub_tf::{ExecuteMsg, InstantiateMsg, TokenFactoryType}, +}; + +//use crate::token_factory::denom::{MsgBurn, MsgCreateDenom, MsgMint}; +use crate::types::{Coins, Delegation, Redelegation}; +use crate::{ + contract::{REPLY_REGISTER_RECEIVED_COINS, SPECIAL_SEND_MESSAGE_TO_TRANSFER}, + helpers::{ + get_denom_balance, parse_received_fund, query_all_delegations, query_delegation, + query_delegations, + }, + injective, kujira, + math::{ + compute_mint_amount, compute_redelegations_for_rebalancing, + compute_redelegations_for_removal, compute_unbond_amount, compute_undelegations, + reconcile_batches, + }, + osmosis, + state::{State, VALIDATORS, VALIDATORS_ACTIVE, previous_batches, unbond_requests}, + token_factory, +}; + +//-------------------------------------------------------------------------------------------------- +// Instantiation +//-------------------------------------------------------------------------------------------------- + +pub fn instantiate(deps: DepsMut, env: Env, msg: InstantiateMsg) -> StdResult { + let state = State::default(); + + if msg.max_fee_amount > Decimal::from_str("1.00")? { + return Err(StdError::generic_err("Max fee can not exceed 1/100%")); + } + + if msg.fee_amount > msg.max_fee_amount { + return Err(StdError::generic_err("fee can not exceed max fee")); + } + let fee_type = FeeType::from_str(&msg.fee_account_type) + .map_err(|_| StdError::generic_err("Invalid Fee type: Wallet or FeeSplit only"))?; + + state.owner.save(deps.storage, &deps.api.addr_validate(&msg.owner)?)?; + state.epoch_period.save(deps.storage, &msg.epoch_period)?; + state.unbond_period.save(deps.storage, &msg.unbond_period)?; + state.unlocked_coins.save(deps.storage, &vec![])?; + state.prev_denom.save(deps.storage, &Uint128::zero())?; + state.denom.save(deps.storage, &msg.denom)?; + state.max_fee_rate.save(deps.storage, &msg.max_fee_amount)?; + state.fee_rate.save(deps.storage, &msg.fee_amount)?; + state.fee_account_type.save(deps.storage, &fee_type)?; + + state.fee_account.save(deps.storage, &deps.api.addr_validate(&msg.fee_account)?)?; + + state.pending_batch.save(deps.storage, &PendingBatch { + id: 1, + usteak_to_burn: Uint128::zero(), + est_unbond_start_time: env.block.time.seconds() + msg.epoch_period, + })?; + + for v in msg.validators { + VALIDATORS.insert(deps.storage, &v)?; + VALIDATORS_ACTIVE.insert(deps.storage, &v)?; + } + let token_factory_type = TokenFactoryType::from_str(&msg.token_factory).map_err(|_| { + StdError::generic_err("Invalid Token Factory type: CosmWasm, Kujira, or Injective only") + })?; + + state.token_factory_type.save(deps.storage, &token_factory_type)?; + + let steak_denom = format!("factory/{0}/{1}", env.contract.address, msg.steak_denom); + let steak_denom_msg = msg.steak_denom; + state.steak_denom.save(deps.storage, &steak_denom)?; + state.steak_minted.save(deps.storage, &Uint128::zero())?; + + if let Some(dust) = msg.dust_collector { + state.dust_collector.save(deps.storage, &Some(deps.api.addr_validate(&dust)?))? + } else { + state.dust_collector.save(deps.storage, &None)? + } + let c = match state.token_factory_type.load(deps.storage)? { + TokenFactoryType::CosmWasm => { + >::into( + token_factory::denom::MsgCreateDenom { + sender: env.contract.address.to_string(), + subdenom: steak_denom_msg, + }, + ) + }, + TokenFactoryType::Kujira => >::into( + kujira::denom::MsgCreateDenom { + sender: env.contract.address.to_string(), + subdenom: steak_denom_msg, + }, + ), + TokenFactoryType::Injective => >::into( + injective::denom::MsgCreateDenom { + sender: env.contract.address.to_string(), + subdenom: steak_denom_msg, + }, + ), + TokenFactoryType::Osmosis => >::into( + osmosis::denom::MsgCreateDenom { + sender: env.contract.address.to_string(), + subdenom: steak_denom_msg, + }, + ), + }; + + Ok(Response::new().add_message(c)) +} + +//-------------------------------------------------------------------------------------------------- +// Bonding and harvesting logics +//-------------------------------------------------------------------------------------------------- + +/// bond tokens (XXX) to validators, returning bXXXX +/// +/// To save gas for users, now we simply delegate all deposited Luna to the validator with the +/// smallest amount of delegation. If delegations become severely unbalance as a result of this +/// (e.g. when a single user makes a very big deposit), anyone can invoke `ExecuteMsg::Rebalance` +/// to balance the delegations. +pub fn bond( + deps: DepsMut, + env: Env, + receiver: Addr, + funds: Vec, + bond_msg: Option, + mint_steak: bool, +) -> StdResult { + let state = State::default(); + let denom = state.denom.load(deps.storage)?; + let amount_to_bond = parse_received_fund(&funds, &denom)?; + let steak_minted = state.steak_minted.load(deps.storage)?; + let steak_denom = state.steak_denom.load(deps.storage)?; + let token_factory_type = state.token_factory_type.load(deps.storage)?; + let mut validators: Vec = Default::default(); + for v in VALIDATORS_ACTIVE.items(deps.storage, None, None, Order::Ascending) { + validators.push(v?); + } + + // Query the current delegations made to validators, and find the validator with the smallest + // delegated amount through a linear search + // The code for linear search is a bit uglier than using `sort_by` but cheaper: O(n) vs O(n * + // log(n)) + let delegations_all = query_all_delegations(&deps.querier, &env.contract.address)?; + let delegations = query_delegations(&deps.querier, &validators, &env.contract.address, &denom)?; + + let mut validator = &delegations[0].validator; + let mut amount = delegations[0].amount; + for d in &delegations[1..] { + if d.amount < amount { + validator = &d.validator; + amount = d.amount; + } + } + let new_delegation = Delegation { + validator: validator.clone(), + amount: amount_to_bond.u128(), + denom: denom.clone(), + }; + let delegate_submsg = SubMsg { + msg: new_delegation.to_cosmos_msg(), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never, + }; + + if mint_steak { + // Query the current supply of Steak and compute the amount to mint + // let usteak_supply = steak_minted; + let usteak_to_mint = compute_mint_amount(steak_minted, amount_to_bond, &delegations_all); + state.steak_minted.save(deps.storage, &(steak_minted + usteak_to_mint))?; + // TODO deal with multiple token returns + state.prev_denom.save( + deps.storage, + &get_denom_balance(&deps.querier, env.contract.address.clone(), denom.clone())?, + )?; + + let mint_msg = match token_factory_type { + TokenFactoryType::CosmWasm => >::into( + token_factory::denom::MsgMint { + sender: env.contract.address.to_string(), + amount: Some(token_factory::denom::Coin { + denom: steak_denom.clone(), + amount: usteak_to_mint.to_string(), + }), + }, + ), + TokenFactoryType::Kujira => { + >::into(kujira::denom::MsgMint { + sender: env.contract.address.to_string(), + recipient: env.contract.address.to_string(), + amount: Some(kujira::denom::Coin { + denom: steak_denom.clone(), + amount: usteak_to_mint.to_string(), + }), + }) + }, + TokenFactoryType::Injective => { + >::into(injective::denom::MsgMint { + sender: env.contract.address.to_string(), + amount: Some(injective::denom::Coin { + denom: steak_denom.clone(), + amount: usteak_to_mint.to_string(), + }), + }) + }, + TokenFactoryType::Osmosis => { + >::into(osmosis::denom::MsgMint { + sender: env.contract.address.to_string(), + amount: Some(osmosis::denom::Coin { + denom: steak_denom.clone(), + amount: usteak_to_mint.to_string(), + }), + mint_to_address: env.contract.address.to_string(), + }) + }, + }; + + let contract_info = deps.querier.query_wasm_contract_info(receiver.to_string()); + + // send the uSteak, optionally calling a smart contract + let send_transfer_msg: CosmosMsg = match contract_info { + Ok(_) => { + if let Some(exec_msg) = bond_msg { + if exec_msg == SPECIAL_SEND_MESSAGE_TO_TRANSFER { + // this is for backwards compatibility only + CosmosMsg::Bank(BankMsg::Send { + to_address: receiver.to_string(), + amount: vec![Coin { + denom: steak_denom, + amount: usteak_to_mint, + }], + }) + } else { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: receiver.to_string(), + msg: to_json_binary(&exec_msg)?, + funds: vec![Coin { + denom: steak_denom, + amount: usteak_to_mint, + }], + }) + } + } else { + CosmosMsg::Bank(BankMsg::Send { + to_address: receiver.to_string(), + amount: vec![Coin { + denom: steak_denom, + amount: usteak_to_mint, + }], + }) + } + }, + Err(_) => CosmosMsg::Bank(BankMsg::Send { + to_address: receiver.to_string(), + amount: vec![Coin { + denom: steak_denom, + amount: usteak_to_mint, + }], + }), + }; + + let event = Event::new("steakhub/bonded") + .add_attribute("time", env.block.time.seconds().to_string()) + .add_attribute("height", env.block.height.to_string()) + .add_attribute("steak_receiver", receiver) + .add_attribute("denom_bonded", denom) + .add_attribute("denom_amount", amount_to_bond) + .add_attribute("usteak_minted", usteak_to_mint); + + Ok(Response::new() + .add_submessage(delegate_submsg) + .add_messages(vec![mint_msg, send_transfer_msg]) + // .add_message(send_msg) + .add_event(event) + .add_attribute("action", "steakhub/bond")) + } else { + let event = Event::new("steakhub/bonded-nomint") + .add_attribute("time", env.block.time.seconds().to_string()) + .add_attribute("height", env.block.height.to_string()) + .add_attribute("from", receiver) + .add_attribute("denom_bonded", denom) + .add_attribute("denom_amount", amount_to_bond); + + Ok(Response::new() + .add_submessage(delegate_submsg) + .add_event(event) + .add_attribute("action", "steakhub/bond-nomint")) + } +} + +pub fn harvest(deps: DepsMut, env: Env) -> StdResult { + let state = State::default(); + let denom = state.denom.load(deps.storage)?; + state.prev_denom.save( + deps.storage, + &get_denom_balance(&deps.querier, env.contract.address.clone(), denom)?, + )?; + + let withdraw_submsgs = deps + .querier + .query_all_delegations(&env.contract.address)? + .into_iter() + .map(|d| SubMsg { + id: REPLY_REGISTER_RECEIVED_COINS, + msg: CosmosMsg::Distribution(DistributionMsg::WithdrawDelegatorReward { + validator: d.validator, + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }) + .collect::>(); + + let callback_msg = CallbackMsg::Reinvest {}.into_cosmos_msg(&env.contract.address)?; + + Ok(Response::new() + .add_submessages(withdraw_submsgs) + .add_message(callback_msg) + .add_attribute("action", "steakhub/harvest")) +} + +/// NOTE: +/// 1. When delegation Native denom here, we don't need to use a `SubMsg` to handle the received +/// coins, because we have already withdrawn all claimable staking rewards previously in the same +/// atomic execution. +/// 2. Same as with `bond`, in the latest implementation we only delegate staking rewards with the +/// validator that has the smallest delegation amount. +pub fn reinvest(deps: DepsMut, env: Env) -> StdResult { + let state = State::default(); + let denom = state.denom.load(deps.storage)?; + let fee = state.fee_rate.load(deps.storage)?; + + let mut validators: Vec = Default::default(); + for v in VALIDATORS_ACTIVE.items(deps.storage, None, None, Order::Ascending) { + validators.push(v?); + } + let prev_coin = state.prev_denom.load(deps.storage)?; + let current_coin = + get_denom_balance(&deps.querier, env.contract.address.clone(), denom.clone())?; + + if current_coin <= prev_coin { + return Err(StdError::generic_err("no rewards")); + } + let amount_to_bond = current_coin.saturating_sub(prev_coin); + let mut unlocked_coins = state.unlocked_coins.load(deps.storage)?; + + /* + + if unlocked_coins.is_empty() { + return Err(StdError::generic_err("no rewards")); + } + let amount_to_bond = unlocked_coins + .iter() + .find(|coin| coin.denom == denom) + .ok_or_else(|| StdError::generic_err("no native amount available to be bonded"))? + .amount; + */ + let delegations = query_delegations(&deps.querier, &validators, &env.contract.address, &denom)?; + let mut validator = &delegations[0].validator; + let mut amount = delegations[0].amount; + for d in &delegations[1..] { + if d.amount < amount { + validator = &d.validator; + amount = d.amount; + } + } + let fee_amount = if fee.is_zero() { + Uint128::zero() + } else { + fee.checked_mul_uint(amount_to_bond)? + }; + let amount_to_bond_minus_fees = amount_to_bond.saturating_sub(fee_amount); + + let new_delegation = Delegation::new(validator, amount_to_bond_minus_fees.u128(), &denom); + + unlocked_coins.retain(|coin| coin.denom != denom); + state.unlocked_coins.save(deps.storage, &unlocked_coins)?; + + let event = Event::new("steakhub/harvested") + .add_attribute("time", env.block.time.seconds().to_string()) + .add_attribute("height", env.block.height.to_string()) + .add_attribute("denom", &denom) + .add_attribute("fees_deducted", fee_amount) + .add_attribute("denom_bonded", amount_to_bond_minus_fees); + + if fee_amount > Uint128::zero() { + let fee_account = state.fee_account.load(deps.storage)?; + let fee_type = state.fee_account_type.load(deps.storage)?; + + let send_msgs = match fee_type { + FeeType::Wallet => vec![CosmosMsg::Bank(BankMsg::Send { + to_address: fee_account.to_string(), + amount: vec![Coin::new(fee_amount.into(), &denom)], + })], + FeeType::FeeSplit => { + let msg = pfc_fee_split::fee_split_msg::ExecuteMsg::Deposit { + flush: false, + }; + + vec![msg.into_cosmos_msg(fee_account, vec![Coin::new(fee_amount.into(), &denom)])?] + }, + }; + Ok(Response::new() + .add_message(new_delegation.to_cosmos_msg()) + .add_messages(send_msgs) + .add_event(event) + .add_attribute("action", "steakhub/reinvest")) + } else { + Ok(Response::new() + .add_message(new_delegation.to_cosmos_msg()) + .add_event(event) + .add_attribute("action", "steakhub/reinvest")) + } +} + +/// NOTE: a `SubMsgResponse` may contain multiple coin-receiving events, must handle them +/// individually +pub fn register_received_coins( + deps: DepsMut, + env: Env, + mut events: Vec, +) -> StdResult { + events.retain(|event| event.ty == "coin_received"); + if events.is_empty() { + return Ok(Response::new()); + } + + let mut received_coins = Coins(vec![]); + for event in &events { + received_coins.add_many(&parse_coin_receiving_event(&env, event)?)?; + } + + let state = State::default(); + state.unlocked_coins.update(deps.storage, |coins| -> StdResult<_> { + let mut coins = Coins(coins); + coins.add_many(&received_coins)?; + Ok(coins.0) + })?; + + Ok(Response::new().add_attribute("action", "steakhub/register_received_coins")) +} + +fn parse_coin_receiving_event(env: &Env, event: &Event) -> StdResult { + let receiver = &event + .attributes + .iter() + .find(|attr| attr.key == "receiver") + .ok_or_else(|| StdError::generic_err("cannot find `receiver` attribute"))? + .value; + + let amount_str = &event + .attributes + .iter() + .find(|attr| attr.key == "amount") + .ok_or_else(|| StdError::generic_err("cannot find `amount` attribute"))? + .value; + + let amount = if *receiver == env.contract.address { + Coins::from_str(amount_str)? + } else { + Coins(vec![]) + }; + + Ok(amount) +} + +//-------------------------------------------------------------------------------------------------- +// Unbonding logics +//-------------------------------------------------------------------------------------------------- + +pub fn queue_unbond( + deps: DepsMut, + env: Env, + receiver: Addr, + funds: Vec, +) -> StdResult { + let state = State::default(); + let steak_denom = state.steak_denom.load(deps.storage)?; + + let usteak_to_burn = funds + .iter() + .filter(|p| p.denom == steak_denom) + .map(|steak_funds| steak_funds.amount) + .sum::(); + if funds.len() != 1 || usteak_to_burn.is_zero() { + return Err(StdError::generic_err(format!( + "you can only send {} tokens to unbond", + steak_denom + ))); + } + let mut pending_batch = state.pending_batch.load(deps.storage)?; + pending_batch.usteak_to_burn += usteak_to_burn; + state.pending_batch.save(deps.storage, &pending_batch)?; + + unbond_requests().update( + deps.storage, + (pending_batch.id, receiver.as_ref()), + |x| -> StdResult<_> { + let mut request = x.unwrap_or_else(|| UnbondRequest { + id: pending_batch.id, + user: receiver.clone(), + shares: Uint128::zero(), + }); + request.shares += usteak_to_burn; + Ok(request) + }, + )?; + + let mut msgs: Vec = vec![]; + if env.block.time.seconds() >= pending_batch.est_unbond_start_time { + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.into(), + msg: to_json_binary(&ExecuteMsg::SubmitBatch {})?, + funds: vec![], + })); + } + + let event = Event::new("steakhub/unbond_queued") + .add_attribute("time", env.block.time.seconds().to_string()) + .add_attribute("height", env.block.height.to_string()) + .add_attribute("id", pending_batch.id.to_string()) + .add_attribute("receiver", receiver) + .add_attribute("usteak_to_burn", usteak_to_burn); + + Ok(Response::new() + .add_messages(msgs) + .add_event(event) + .add_attribute("action", "steakhub/queue_unbond")) +} + +pub fn submit_batch(deps: DepsMut, env: Env) -> StdResult { + let state = State::default(); + let denom = state.denom.load(deps.storage)?; + let steak_denom = state.steak_denom.load(deps.storage)?; + let token_factory_type = state.token_factory_type.load(deps.storage)?; + let usteak_supply = state.steak_minted.load(deps.storage)?; + let mut validators: Vec = Default::default(); + for v in VALIDATORS.items(deps.storage, None, None, Order::Ascending) { + validators.push(v?); + } + + let unbond_period = state.unbond_period.load(deps.storage)?; + let pending_batch = state.pending_batch.load(deps.storage)?; + + let current_time = env.block.time.seconds(); + if current_time < pending_batch.est_unbond_start_time { + return Err(StdError::generic_err(format!( + "batch can only be submitted for unbonding after {}", + pending_batch.est_unbond_start_time + ))); + } + let mut validators_active: HashSet = Default::default(); + for v in VALIDATORS_ACTIVE.items(deps.storage, None, None, Order::Ascending) { + validators_active.insert(v?); + } + for v in validators.iter() { + validators_active.remove(v); + } + // let active_validator_list = Vec::from_iter(validators_active); + + // for unbonding we still need to look at + // TODO verify denom + let delegations = query_all_delegations(&deps.querier, &env.contract.address)?; + // let delegations_active = + // query_delegations(&deps.querier, &active_validator_list, &env.contract.address, &denom)?; + // let usteak_supply = query_cw20_total_supply(&deps.querier, &steak_token)?; + + let amount_to_unbond = compute_unbond_amount( + usteak_supply, + pending_batch.usteak_to_burn, + &delegations, + // &delegations_active, + ); + + let new_undelegations = compute_undelegations(amount_to_unbond, &delegations, &denom); + + // NOTE: Regarding the `amount_unclaimed` value + // + // If validators misbehave and get slashed during the unbonding period, the contract can receive + // LESS Luna than `amount_to_unbond` when unbonding finishes! + // + // In this case, users who invokes `withdraw_unbonded` will have their txs failed as the + // contract does not have enough Luna balance. + // + // I don't have a solution for this... other than to manually fund contract with the slashed + // amount. + previous_batches().save(deps.storage, pending_batch.id, &Batch { + id: pending_batch.id, + reconciled: false, + total_shares: pending_batch.usteak_to_burn, + amount_unclaimed: amount_to_unbond, + est_unbond_end_time: current_time + unbond_period, + })?; + + let epoch_period = state.epoch_period.load(deps.storage)?; + state.pending_batch.save(deps.storage, &PendingBatch { + id: pending_batch.id + 1, + usteak_to_burn: Uint128::zero(), + est_unbond_start_time: current_time + epoch_period, + })?; + state.prev_denom.save( + deps.storage, + &get_denom_balance(&deps.querier, env.contract.address.clone(), denom)?, + )?; + + let undelegate_submsgs = new_undelegations + .iter() + .map(|d| SubMsg { + id: REPLY_REGISTER_RECEIVED_COINS, + msg: d.to_cosmos_msg(), + gas_limit: None, + reply_on: ReplyOn::Never, + }) + .collect::>(); + + let mut msgs = vec![]; + if !pending_batch.usteak_to_burn.is_zero() { + let burn_msg = match token_factory_type { + TokenFactoryType::Kujira => { + >::into(kujira::denom::MsgBurn { + sender: env.contract.address.to_string(), + amount: Some(kujira::denom::Coin { + denom: steak_denom, + amount: pending_batch.usteak_to_burn.to_string(), + }), + }) + }, + TokenFactoryType::CosmWasm => >::into( + token_factory::denom::MsgBurn { + sender: env.contract.address.to_string(), + amount: Some(token_factory::denom::Coin { + denom: steak_denom, + amount: pending_batch.usteak_to_burn.to_string(), + }), + }, + ), + TokenFactoryType::Injective => { + >::into(injective::denom::MsgBurn { + sender: env.contract.address.to_string(), + amount: Some(injective::denom::Coin { + denom: steak_denom, + amount: pending_batch.usteak_to_burn.to_string(), + }), + }) + }, + TokenFactoryType::Osmosis => { + >::into(osmosis::denom::MsgBurn { + sender: env.contract.address.to_string(), + amount: Some(osmosis::denom::Coin { + denom: steak_denom, + amount: pending_batch.usteak_to_burn.to_string(), + }), + burn_from_address: env.contract.address.to_string(), + }) + }, + }; + // yes.. this will fail if supply is less than the amount to burn. this is intentional. + state.steak_minted.save(deps.storage, &(usteak_supply - pending_batch.usteak_to_burn))?; + msgs.push(burn_msg); + } + let event = Event::new("steakhub/unbond_submitted") + .add_attribute("time", env.block.time.seconds().to_string()) + .add_attribute("height", env.block.height.to_string()) + .add_attribute("id", pending_batch.id.to_string()) + .add_attribute("native_unbonded", amount_to_unbond) + .add_attribute("usteak_burned", pending_batch.usteak_to_burn); + + Ok(Response::new() + .add_submessages(undelegate_submsgs) + .add_messages(msgs) + .add_event(event) + .add_attribute("action", "steakhub/unbond")) +} + +pub fn reconcile(deps: DepsMut, env: Env) -> StdResult { + let state = State::default(); + let current_time = env.block.time.seconds(); + + // Load batches that have not been reconciled + let all_batches = previous_batches() + .idx + .reconciled + .prefix("false".to_string()) + .range(deps.storage, None, None, Order::Ascending) + .map(|item| { + let (_, v) = item?; + Ok(v) + }) + .collect::>>()?; + + let mut batches = all_batches + .into_iter() + .filter(|b| current_time > b.est_unbond_end_time) + .collect::>(); + + let native_expected_received: Uint128 = batches.iter().map(|b| b.amount_unclaimed).sum(); + let denom = state.denom.load(deps.storage)?; + let unlocked_coins = state.unlocked_coins.load(deps.storage)?; + + let native_expected_unlocked = Coins(unlocked_coins).find(&denom).amount; + + let native_expected = native_expected_received + native_expected_unlocked; + let native_actual = deps.querier.query_balance(&env.contract.address, &denom)?.amount; + + let native_to_deduct = + native_expected.checked_sub(native_actual).unwrap_or_else(|_| Uint128::zero()); + if !native_to_deduct.is_zero() { + reconcile_batches(&mut batches, native_expected - native_actual); + } + + for batch in batches.iter_mut() { + batch.reconciled = true; + previous_batches().save(deps.storage, batch.id, batch)?; + } + + let ids = batches.iter().map(|b| b.id.to_string()).collect::>().join(","); + + let event = Event::new("steakhub/reconciled") + .add_attribute("ids", ids) + .add_attribute("native_deducted", native_to_deduct.to_string()); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/reconcile")) +} + +pub fn withdraw_unbonded_admin( + deps: DepsMut, + env: Env, + user: Addr, + receiver: Addr, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &user)?; + + withdraw_unbonded(deps, env, receiver.clone(), receiver) +} + +pub fn withdraw_unbonded( + deps: DepsMut, + env: Env, + user: Addr, + receiver: Addr, +) -> StdResult { + let state = State::default(); + let denom = state.denom.load(deps.storage)?; + let current_time = env.block.time.seconds(); + + // NOTE: If the user has too many unclaimed requests, this may not fit in the WASM memory... + // However, this is practically never going to happen. Who would create hundreds of unbonding + // requests and never claim them? + let requests = unbond_requests() + .idx + .user + .prefix(user.to_string()) + .range(deps.storage, None, None, Order::Ascending) + .map(|item| { + let (_, v) = item?; + Ok(v) + }) + .collect::>>()?; + + // NOTE: Native in the following batches are withdrawn it the batch: + // - is a _previous_ batch, not a _pending_ batch + // - is reconciled + // - has finished unbonding + // If not sure whether the batches have been reconciled, the user should first invoke + // `ExecuteMsg::Reconcile` before withdrawing. + let mut total_native_to_refund = Uint128::zero(); + let mut ids: Vec = vec![]; + for request in &requests { + if let Ok(mut batch) = previous_batches().load(deps.storage, request.id) { + if batch.reconciled && batch.est_unbond_end_time < current_time { + let native_to_refund = + batch.amount_unclaimed.multiply_ratio(request.shares, batch.total_shares); + + ids.push(request.id.to_string()); + + total_native_to_refund += native_to_refund; + batch.total_shares -= request.shares; + batch.amount_unclaimed -= native_to_refund; + + if batch.total_shares.is_zero() { + previous_batches().remove(deps.storage, request.id)?; + } else { + previous_batches().save(deps.storage, batch.id, &batch)?; + } + unbond_requests().remove(deps.storage, (request.id, user.as_ref()))?; + } + } + } + + if total_native_to_refund.is_zero() { + return Err(StdError::generic_err("withdrawable amount is zero")); + } + + let refund_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: receiver.clone().into(), + amount: vec![Coin::new(total_native_to_refund.u128(), denom)], + }); + + let event = Event::new("steakhub/unbonded_withdrawn") + .add_attribute("time", env.block.time.seconds().to_string()) + .add_attribute("height", env.block.height.to_string()) + .add_attribute("ids", ids.join(",")) + .add_attribute("user", user) + .add_attribute("receiver", receiver) + .add_attribute("amount_refunded", total_native_to_refund); + + Ok(Response::new() + .add_message(refund_msg) + .add_event(event) + .add_attribute("action", "steakhub/withdraw_unbonded")) +} + +//-------------------------------------------------------------------------------------------------- +// Ownership and management logics +//-------------------------------------------------------------------------------------------------- + +pub fn rebalance(deps: DepsMut, env: Env, minimum: Uint128) -> StdResult { + let state = State::default(); + let denom = state.denom.load(deps.storage)?; + + let delegations = query_all_delegations(&deps.querier, &env.contract.address)?; + + let mut validators_active: Vec = Default::default(); + for v in VALIDATORS_ACTIVE.items(deps.storage, None, None, Order::Ascending) { + validators_active.push(v?); + } + + let new_redelegations = + compute_redelegations_for_rebalancing(validators_active, &delegations, minimum); + + state + .prev_denom + .save(deps.storage, &get_denom_balance(&deps.querier, env.contract.address, denom)?)?; + + let redelegate_submsgs = new_redelegations + .iter() + .map(|rd| SubMsg { + id: REPLY_REGISTER_RECEIVED_COINS, + msg: rd.to_cosmos_msg(), + gas_limit: None, + reply_on: ReplyOn::Never, + }) + .collect::>(); + + let amount: u128 = new_redelegations.iter().map(|rd| rd.amount).sum(); + + let event = Event::new("steakhub/rebalanced").add_attribute("amount_moved", amount.to_string()); + + Ok(Response::new() + .add_submessages(redelegate_submsgs) + .add_event(event) + .add_attribute("action", "steakhub/rebalance")) +} + +pub fn add_validator(deps: DepsMut, sender: Addr, validator: String) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + if VALIDATORS.contains(deps.storage, &validator) { + return Err(StdError::generic_err("validator is already whitelisted")); + } + VALIDATORS.insert(deps.storage, &validator)?; + VALIDATORS_ACTIVE.insert(deps.storage, &validator)?; + + let event = Event::new("steakhub/validator_added").add_attribute("validator", validator); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/add_validator")) +} + +pub fn remove_validator( + deps: DepsMut, + env: Env, + sender: Addr, + validator: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + let denom = state.denom.load(deps.storage)?; + + if !VALIDATORS.contains(deps.storage, &validator) { + return Err(StdError::generic_err("validator is not already whitelisted")); + } + VALIDATORS.remove(deps.storage, &validator)?; + VALIDATORS_ACTIVE.insert(deps.storage, &validator)?; + let mut validators: Vec = Default::default(); + for v in VALIDATORS.items(deps.storage, None, None, Order::Ascending) { + validators.push(v?); + } + + let delegations = query_delegations(&deps.querier, &validators, &env.contract.address, &denom)?; + let delegation_to_remove = + query_delegation(&deps.querier, &validator, &env.contract.address, &denom)?; + let new_redelegations = + compute_redelegations_for_removal(&delegation_to_remove, &delegations, &denom); + + state + .prev_denom + .save(deps.storage, &get_denom_balance(&deps.querier, env.contract.address, denom)?)?; + + let redelegate_submsgs = new_redelegations + .iter() + .map(|d| SubMsg { + id: REPLY_REGISTER_RECEIVED_COINS, + msg: d.to_cosmos_msg(), + gas_limit: None, + reply_on: ReplyOn::Never, + }) + .collect::>(); + + let event = Event::new("steak/validator_removed").add_attribute("validator", validator); + + Ok(Response::new() + .add_submessages(redelegate_submsgs) + .add_event(event) + .add_attribute("action", "steakhub/remove_validator")) +} + +pub fn remove_validator_ex( + deps: DepsMut, + _env: Env, + sender: Addr, + validator: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + + if !VALIDATORS.contains(deps.storage, &validator) { + return Err(StdError::generic_err("validator is not already whitelisted")); + } + VALIDATORS.remove(deps.storage, &validator)?; + + let event = Event::new("steak/validator_removed_ex").add_attribute("validator", validator); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/remove_validator_ex")) +} + +pub fn pause_validator( + deps: DepsMut, + _env: Env, + sender: Addr, + validator: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + + if !VALIDATORS_ACTIVE.contains(deps.storage, &validator) { + return Err(StdError::generic_err("validator is not already whitelisted")); + } + VALIDATORS_ACTIVE.remove(deps.storage, &validator)?; + + let event = Event::new("steak/pause_validator").add_attribute("validator", validator); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/pause_validator")) +} + +pub fn unpause_validator( + deps: DepsMut, + _env: Env, + sender: Addr, + validator: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + VALIDATORS_ACTIVE.insert(deps.storage, &validator)?; + + let event = Event::new("steak/unpause_validator").add_attribute("validator", validator); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/unpause_validator")) +} + +pub fn set_unbond_period( + deps: DepsMut, + _env: Env, + sender: Addr, + unbond_period: u64, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + state.unbond_period.save(deps.storage, &unbond_period)?; + let event = Event::new("steak/set_unbond_period") + .add_attribute("unbond_period", format!("{}", unbond_period)); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/set_unbond_period")) +} + +pub fn transfer_ownership(deps: DepsMut, sender: Addr, new_owner: String) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + state.new_owner.save(deps.storage, &deps.api.addr_validate(&new_owner)?)?; + + Ok(Response::new().add_attribute("action", "steakhub/transfer_ownership")) +} + +pub fn accept_ownership(deps: DepsMut, sender: Addr) -> StdResult { + let state = State::default(); + + let previous_owner = state.owner.load(deps.storage)?; + let new_owner = state.new_owner.load(deps.storage)?; + + if sender != new_owner { + return Err(StdError::generic_err("unauthorized: sender is not new owner")); + } + + state.owner.save(deps.storage, &sender)?; + state.new_owner.remove(deps.storage); + + let event = Event::new("steakhub/ownership_transferred") + .add_attribute("new_owner", new_owner) + .add_attribute("previous_owner", previous_owner); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/transfer_ownership")) +} + +pub fn transfer_fee_account( + deps: DepsMut, + sender: Addr, + fee_account_type: String, + new_fee_account: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + let fee_type = FeeType::from_str(&fee_account_type) + .map_err(|_| StdError::generic_err("Invalid Fee type: Wallet or FeeSplit only"))?; + + state.fee_account_type.save(deps.storage, &fee_type)?; + + state.fee_account.save(deps.storage, &deps.api.addr_validate(&new_fee_account)?)?; + + Ok(Response::new().add_attribute("action", "steakhub/transfer_fee_account")) +} + +pub fn change_denom(deps: DepsMut, sender: Addr, new_denom: String) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + state.denom.save(deps.storage, &new_denom)?; + + Ok(Response::new().add_attribute("action", "steakhub/change_denom")) +} + +pub fn update_fee(deps: DepsMut, sender: Addr, new_fee: Decimal) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + if new_fee > state.max_fee_rate.load(deps.storage)? { + return Err(StdError::generic_err("refusing to set fee above maximum set")); + } + state.fee_rate.save(deps.storage, &new_fee)?; + + Ok(Response::new().add_attribute("action", "steakhub/update_fee")) +} + +pub fn set_dust_collector( + deps: DepsMut, + _env: Env, + sender: Addr, + dust_collector: Option, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + if let Some(ref dust_addr) = dust_collector { + state.dust_collector.save(deps.storage, &Some(deps.api.addr_validate(dust_addr)?))?; + } else { + state.dust_collector.save(deps.storage, &None)?; + }; + + let event = Event::new("steak/set_dust_collector") + .add_attribute("dust_collector", dust_collector.unwrap_or("-cleared-".into())); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/set_dust_collector")) +} + +pub fn collect_dust(deps: DepsMut, env: Env, max_tokens: usize) -> StdResult { + let state = State::default(); + let denom = state.denom.load(deps.storage)?; + let steak_denom = state.steak_denom.load(deps.storage)?; + let balances = deps.querier.query_all_balances(env.contract.address)?; + let balances_filtered = balances + .into_iter() + .filter(|p| !(p.denom == denom || p.denom == steak_denom)) + .take(max_tokens) + .collect::>(); + if balances_filtered.is_empty() { + return Ok(Response::new().add_attribute("dust", "no-dust")); + } + if let Some(dust_addr) = state.dust_collector.load(deps.storage)? { + let balances_count = balances_filtered.len(); + let msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: dust_addr.to_string(), + funds: balances_filtered, + msg: to_json_binary(&pfc_dust_collector::dust_collector::ExecuteMsg::DustReceived {})?, + }); + Ok(Response::new() + .add_attribute("dust", format!("sent {} tokens", balances_count)) + .add_message(msg)) + } else { + Ok(Response::new().add_attribute("dust", "dust-collector-called")) + } +} + +pub fn redelegate( + deps: DepsMut, + env: Env, + sender: Addr, + validator_from: String, + validator_to: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + let denom = state.denom.load(deps.storage)?; + + let delegation = + query_delegation(&deps.querier, &validator_from, &env.contract.address, &denom)?; + + let redelegation_msg = SubMsg { + id: REPLY_REGISTER_RECEIVED_COINS, + msg: Redelegation::new(&validator_from, &validator_to, delegation.amount, &denom) + .to_cosmos_msg(), + gas_limit: None, + reply_on: ReplyOn::Never, + }; + + state + .prev_denom + .save(deps.storage, &get_denom_balance(&deps.querier, env.contract.address, denom)?)?; + + let event = Event::new("steak/redelegate") + .add_attribute("validator_from", validator_from) + .add_attribute("validator_to", validator_to) + .add_attribute("amount", delegation.amount.to_string()); + + Ok(Response::new() + .add_submessage(redelegation_msg) + .add_event(event) + .add_attribute("action", "steakhub/redelegate")) +} +pub fn set_base_denom(deps: DepsMut, sender: Addr, new_denom: String) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + state.denom.save(deps.storage, &new_denom)?; + + let event = Event::new("steak/set_base_denom").add_attribute("base_denom", new_denom); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/set_base_denom")) +} + +pub fn change_token_factory( + deps: DepsMut, + sender: Addr, + token_factory_type: &str, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + + let tf = TokenFactoryType::from_str(token_factory_type) + .map_err(|_| StdError::generic_err("Invalid Token Factory type"))?; + + state.token_factory_type.save(deps.storage, &tf)?; + let event = Event::new("steak/change_token_factory") + .add_attribute("token_factory_type", token_factory_type); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/change_token_factory")) +} diff --git a/contracts/hub-tf/src/helpers.rs b/contracts/hub-tf/src/helpers.rs new file mode 100644 index 00000000..4c5e72e1 --- /dev/null +++ b/contracts/hub-tf/src/helpers.rs @@ -0,0 +1,118 @@ +use std::str::FromStr; + +use cosmwasm_std::{ + Addr, BalanceResponse, BankQuery, Coin, QuerierWrapper, QueryRequest, StdError, StdResult, + Uint128, +}; + +use crate::types::Delegation; +/* +/// Unwrap a `Reply` object to extract the response +pub(crate) fn unwrap_reply(reply: Reply) -> StdResult { + reply.result.into_result().map_err(StdError::generic_err) +} +*/ + +/// Query the amounts of Luna a staker is delegating to a specific validator +pub(crate) fn query_delegation( + querier: &QuerierWrapper, + validator: &str, + delegator_addr: &Addr, + denom: &str, +) -> StdResult { + Ok(Delegation { + validator: validator.to_string(), + amount: querier + .query_delegation(delegator_addr, validator)? + .map(|fd| fd.amount.amount.u128()) + .unwrap_or(0), + denom: denom.into(), + }) +} +pub(crate) fn query_all_delegations( + querier: &QuerierWrapper, + delegator_addr: &Addr, + // _denom: &str, +) -> StdResult> { + Ok(querier + .query_all_delegations(delegator_addr)? + .into_iter() + .map(|std_delegation| Delegation { + validator: std_delegation.validator.to_string(), + amount: std_delegation.amount.amount.u128(), + denom: std_delegation.amount.denom, + }) + .collect()) +} + +/// Query the amounts of Luna a staker is delegating to each of the validators specified +pub(crate) fn query_delegations( + querier: &QuerierWrapper, + validators: &[String], + delegator_addr: &Addr, + denom: &str, +) -> StdResult> { + validators + .iter() + .map(|validator| query_delegation(querier, validator, delegator_addr, denom)) + .collect() +} + +/// `cosmwasm_std::Coin` does not implement `FromStr`, so we have do it ourselves +/// +/// Parsing the string with regex doesn't work, because the resulting binary would be too big for +/// including the `regex` library. Example: +/// https://github.com/PFC-Validator/terra-rust/blob/v1.1.8/terra-rust-api/src/client/core_types.rs#L34-L55 +/// +/// We opt for a dirtier solution. Enumerate characters in the string, and break before the first +/// character that is not a number. Split the string at that index. +/// +/// This assumes the denom never starts with a number, which is true on Terra. +pub(crate) fn parse_coin(s: &str) -> StdResult { + for (i, c) in s.chars().enumerate() { + if c.is_alphabetic() { + let amount = Uint128::from_str(&s[..i])?; + let denom = &s[i..]; + return Ok(Coin::new(amount.u128(), denom)); + } + } + + Err(StdError::generic_err(format!("failed to parse coin: {}", s))) +} + +/// Find the amount of a denom sent along a message, assert it is non-zero, and no other denom were +/// sent together +pub(crate) fn parse_received_fund(funds: &[Coin], denom: &str) -> StdResult { + if funds.len() != 1 { + return Err(StdError::generic_err(format!( + "must deposit exactly one coin; received {}", + funds.len() + ))); + } + + let fund = &funds[0]; + if fund.denom != denom { + return Err(StdError::generic_err(format!( + "expected {} deposit, received {}", + denom, fund.denom + ))); + } + + if fund.amount.is_zero() { + return Err(StdError::generic_err("deposit amount must be non-zero")); + } + + Ok(fund.amount) +} + +pub fn get_denom_balance( + querier: &QuerierWrapper, + account_addr: Addr, + denom: String, +) -> StdResult { + let balance: BalanceResponse = querier.query(&QueryRequest::Bank(BankQuery::Balance { + address: account_addr.to_string(), + denom, + }))?; + Ok(balance.amount.amount) +} diff --git a/contracts/hub-tf/src/injective/denom.rs b/contracts/hub-tf/src/injective/denom.rs new file mode 100644 index 00000000..ea713567 --- /dev/null +++ b/contracts/hub-tf/src/injective/denom.rs @@ -0,0 +1,143 @@ +// source: https://github.com/White-Whale-Defi-Platform/white-whale-core/blob/feat/tokenfactory-lp/packages/pool-network/src/denom.rs + +use std::convert::{TryFrom, TryInto}; + +use osmosis_std_derive::CosmwasmExt; + +// see https://github.com/notional-labs/wasmd/blob/v0.30.0-sdk469.4/proto/cosmwasm/tokenfactory/v1beta1/tx.proto + +/// Coin defines a token with a denomination and an amount. +/// +/// NOTE: The amount field is an Int which implements the custom method +/// signatures required by gogoproto. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmos.base.v1beta1.Coin")] +pub struct Coin { + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub amount: ::prost::alloc::string::String, +} + +/// Create a Denom +/// +/// MsgCreateDenom defines the message structure for the CreateDenom gRPC service +/// method. It allows an account to create a new denom. It requires a sender +/// address and a sub denomination. The (sender_address, sub_denomination) tuple +/// must be unique and cannot be re-used. +/// +/// The resulting denom created is defined as +/// . The resulting denom's admin is +/// originally set to be the creator, but this can be changed later. The token +/// denom does not indicate the current admin. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgCreateDenom")] +pub struct MsgCreateDenom { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + /// subdenom can be up to 44 "alphanumeric" characters long. + #[prost(string, tag = "2")] + pub subdenom: ::prost::alloc::string::String, +} + +/// MsgCreateDenomResponse is the return value of MsgCreateDenom +/// It returns the full string of the newly created denom +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgCreateDenomResponse")] +pub struct MsgCreateDenomResponse { + #[prost(string, tag = "1")] + pub new_token_denom: ::prost::alloc::string::String, +} + +/// MsgMint is the sdk.Msg type for allowing an admin account to mint +/// more of a token. For now, we only support minting to the sender account +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgMint")] +pub struct MsgMint { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgMintResponse")] +pub struct MsgMintResponse {} + +/// MsgBurn is the sdk.Msg type for allowing an admin account to burn +/// a token. For now, we only support burning from the sender account. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgBurn")] +pub struct MsgBurn { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgBurnResponse")] +pub struct MsgBurnResponse {} diff --git a/contracts/hub-tf/src/injective/mod.rs b/contracts/hub-tf/src/injective/mod.rs new file mode 100644 index 00000000..80617f64 --- /dev/null +++ b/contracts/hub-tf/src/injective/mod.rs @@ -0,0 +1,2 @@ +// +pub mod denom; diff --git a/contracts/hub-tf/src/kujira/denom.rs b/contracts/hub-tf/src/kujira/denom.rs new file mode 100644 index 00000000..1ffddeb9 --- /dev/null +++ b/contracts/hub-tf/src/kujira/denom.rs @@ -0,0 +1,145 @@ +// source: https://github.com/White-Whale-Defi-Platform/white-whale-core/blob/feat/tokenfactory-lp/packages/pool-network/src/denom.rs + +use std::convert::{TryFrom, TryInto}; + +use osmosis_std_derive::CosmwasmExt; + +// see https://github.com/notional-labs/wasmd/blob/v0.30.0-sdk469.4/proto/cosmwasm/tokenfactory/v1beta1/tx.proto + +/// Coin defines a token with a denomination and an amount. +/// +/// NOTE: The amount field is an Int which implements the custom method +/// signatures required by gogoproto. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmos.base.v1beta1.Coin")] +pub struct Coin { + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub amount: ::prost::alloc::string::String, +} + +/// Create a Denom +/// +/// MsgCreateDenom defines the message structure for the CreateDenom gRPC service +/// method. It allows an account to create a new denom. It requires a sender +/// address and a sub denomination. The (sender_address, sub_denomination) tuple +/// must be unique and cannot be re-used. +/// +/// The resulting denom created is defined as +/// . The resulting denom's admin is +/// originally set to be the creator, but this can be changed later. The token +/// denom does not indicate the current admin. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/kujira.denom.MsgCreateDenom")] +pub struct MsgCreateDenom { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + /// subdenom can be up to 44 "alphanumeric" characters long. + #[prost(string, tag = "2")] + pub subdenom: ::prost::alloc::string::String, +} + +/// MsgCreateDenomResponse is the return value of MsgCreateDenom +/// It returns the full string of the newly created denom +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/kujira.denom.MsgCreateDenomResponse")] +pub struct MsgCreateDenomResponse { + #[prost(string, tag = "1")] + pub new_token_denom: ::prost::alloc::string::String, +} + +/// MsgMint is the sdk.Msg type for allowing an admin account to mint +/// more of a token. For now, we only support minting to the sender account +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/kujira.denom.MsgMint")] +pub struct MsgMint { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, + #[prost(string, tag = "3")] + pub recipient: ::prost::alloc::string::String, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/kujira.denom.MsgMintResponse")] +pub struct MsgMintResponse {} + +/// MsgBurn is the sdk.Msg type for allowing an admin account to burn +/// a token. For now, we only support burning from the sender account. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/kujira.denom.MsgBurn")] +pub struct MsgBurn { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/kujira.denom.MsgBurnResponse")] +pub struct MsgBurnResponse {} diff --git a/contracts/hub-tf/src/kujira/mod.rs b/contracts/hub-tf/src/kujira/mod.rs new file mode 100644 index 00000000..80617f64 --- /dev/null +++ b/contracts/hub-tf/src/kujira/mod.rs @@ -0,0 +1,2 @@ +// +pub mod denom; diff --git a/contracts/hub-tf/src/lib.rs b/contracts/hub-tf/src/lib.rs new file mode 100644 index 00000000..74e5fc4a --- /dev/null +++ b/contracts/hub-tf/src/lib.rs @@ -0,0 +1,16 @@ +//#[cfg(not(feature = "library"))] +pub mod contract; + +pub mod execute; +pub mod helpers; +pub mod injective; +pub mod kujira; +pub mod math; +mod migrations; +pub mod osmosis; +pub mod queries; +pub mod state; +#[cfg(test)] +mod testing; +pub mod token_factory; +pub mod types; diff --git a/contracts/hub-tf/src/math.rs b/contracts/hub-tf/src/math.rs new file mode 100644 index 00000000..1e28b972 --- /dev/null +++ b/contracts/hub-tf/src/math.rs @@ -0,0 +1,295 @@ +use std::{cmp, cmp::Ordering, collections::HashMap}; + +use cosmwasm_std::Uint128; +use pfc_steak::hub::Batch; + +use crate::types::{Delegation, Redelegation, Undelegation}; + +//-------------------------------------------------------------------------------------------------- +// Minting/burning logics +//-------------------------------------------------------------------------------------------------- + +/// Compute the amount of Steak token to mint for a specific Luna stake amount. If current total +/// staked amount is zero, we use 1 usteak = 1 native; otherwise, we calculate base on the current +/// native per ustake ratio. +pub(crate) fn compute_mint_amount( + usteak_supply: Uint128, + native_to_bond: Uint128, + all_delegations: &[Delegation], + // inactive_delegations: &[Delegation], +) -> Uint128 { + let native_bonded: u128 = all_delegations.iter().map(|d| d.amount).sum(); + // let native_bonded_inactive: u128 = inactive_delegations.iter().map(|d| d.amount).sum(); + // let native_bonded = native_bonded_c + native_bonded_inactive; + if native_bonded == 0 { + native_to_bond + } else { + usteak_supply.multiply_ratio(native_to_bond, native_bonded) + } +} + +/// Compute the amount of `native` to unbond for a specific `usteak` burn amount +/// +/// There is no way `usteak` total supply is zero when the user is senting a non-zero amount of +/// `usteak` to burn, so we don't need to handle division-by-zero here +pub(crate) fn compute_unbond_amount( + usteak_supply: Uint128, + usteak_to_burn: Uint128, + all_delegations: &[Delegation], + //active_delegations: &[Delegation], +) -> Uint128 { + let native_bonded: u128 = all_delegations.iter().map(|d| d.amount).sum(); + // let native_bonded_a: u128 = active_delegations.iter().map(|d| d.amount).sum(); + // let native_bonded = native_bonded_c + native_bonded_a; + Uint128::new(native_bonded).multiply_ratio(usteak_to_burn, usteak_supply) +} + +//-------------------------------------------------------------------------------------------------- +// Delegation logics +//-------------------------------------------------------------------------------------------------- + +/// Given the current delegations made to validators, and a specific amount of `native` to unstake, +/// compute the undelegations to make such that the delegated amount to each validator is as even +/// as possible. +/// +/// This function is based on Lido's implementation: +/// https://github.com/lidofinance/lido-terra-contracts/blob/v1.0.2/contracts/lido_terra_validators_registry/src/common.rs#L55-102 +pub(crate) fn compute_undelegations( + native_to_unbond: Uint128, + current_delegations: &[Delegation], + denom: &str, +) -> Vec { + let native_staked: u128 = current_delegations.iter().map(|d| d.amount).sum(); + let validator_count = current_delegations.len() as u128; + + let native_to_distribute = native_staked - native_to_unbond.u128(); + let native_per_validator = native_to_distribute / validator_count; + let remainder = native_to_distribute % validator_count; + + let mut new_undelegations: Vec = vec![]; + let mut native_available = native_to_unbond.u128(); + for (i, d) in current_delegations.iter().enumerate() { + let remainder_for_validator: u128 = u128::from((i + 1) as u128 <= remainder); + let native_for_validator = native_per_validator + remainder_for_validator; + + let mut native_to_undelegate = if d.amount < native_for_validator { + 0 + } else { + d.amount - native_for_validator + }; + + native_to_undelegate = cmp::min(native_to_undelegate, native_available); + native_available -= native_to_undelegate; + + if native_to_undelegate > 0 { + new_undelegations.push(Undelegation::new(&d.validator, native_to_undelegate, denom)); + } + + if native_available == 0 { + break; + } + } + + new_undelegations +} + +/// Given a validator who is to be removed from the whitelist, and current delegations made to other +/// validators, compute the new delegations to make such that the delegated amount to each validator +// is as even as possible. +/// +/// This function is based on Lido's implementation: +/// https://github.com/lidofinance/lido-terra-contracts/blob/v1.0.2/contracts/lido_terra_validators_registry/src/common.rs#L19-L53 +pub(crate) fn compute_redelegations_for_removal( + delegation_to_remove: &Delegation, + current_delegations: &[Delegation], + denom: &str, +) -> Vec { + let native_staked: u128 = current_delegations.iter().map(|d| d.amount).sum(); + let validator_count = current_delegations.len() as u128; + + let native_to_distribute = native_staked + delegation_to_remove.amount; + let native_per_validator = native_to_distribute / validator_count; + let remainder = native_to_distribute % validator_count; + + let mut new_redelegations: Vec = vec![]; + let mut native_available = delegation_to_remove.amount; + for (i, d) in current_delegations.iter().enumerate() { + let remainder_for_validator: u128 = u128::from((i + 1) as u128 <= remainder); + let native_for_validator = native_per_validator + remainder_for_validator; + + let mut native_to_redelegate = if d.amount > native_for_validator { + 0 + } else { + native_for_validator - d.amount + }; + + native_to_redelegate = cmp::min(native_to_redelegate, native_available); + native_available -= native_to_redelegate; + + if native_to_redelegate > 0 { + new_redelegations.push(Redelegation::new( + &delegation_to_remove.validator, + &d.validator, + native_to_redelegate, + denom, + )); + } + + if native_available == 0 { + break; + } + } + + new_redelegations +} + +/// Compute redelegation moves that will make each validator's delegation the targeted amount +/// (hopefully this sentence makes sense) +/// +/// This algorithm does not guarantee the minimal number of moves, but is the best I can some up +/// with... +pub(crate) fn compute_redelegations_for_rebalancing( + validators_active: Vec, + current_delegations: &[Delegation], + min_difference: Uint128, +) -> Vec { + let native_staked: u128 = current_delegations.iter().map(|d| d.amount).sum(); + let validator_count = validators_active.len() as u128; + + let native_per_validator = native_staked / validator_count; + let remainder = native_staked % validator_count; + + // If a validator's current delegated amount is greater than the target amount, native will be + // redelegated _from_ them. They will be put in `src_validators` vector + // If a validator's current delegated amount is smaller than the target amount, native will be + // redelegated _to_ them. They will be put in `dst_validators` vector + let mut src_delegations: Vec = vec![]; + let mut dst_delegations: Vec = vec![]; + for (i, d) in current_delegations.iter().enumerate() { + let remainder_for_validator: u128 = u128::from((i + 1) as u128 <= remainder); + let native_for_validator = native_per_validator + remainder_for_validator; + // eprintln!("{} amount ={} native={} min={}", d.validator, d.amount, native_for_validator, + // min_difference); + match d.amount.cmp(&native_for_validator) { + Ordering::Greater => { + if d.amount - native_for_validator > min_difference.u128() { + src_delegations.push(Delegation::new( + &d.validator, + d.amount - native_for_validator, + &d.denom, + )); + } + }, + Ordering::Less => { + if validators_active.contains(&d.validator) + && native_for_validator - d.amount > min_difference.u128() + { + dst_delegations.push(Delegation::new( + &d.validator, + native_for_validator - d.amount, + &d.denom, + )); + } + }, + Ordering::Equal => (), + } + } + + let mut new_redelegations: Vec = vec![]; + while !src_delegations.is_empty() && !dst_delegations.is_empty() { + let src_delegation = src_delegations[0].clone(); + let dst_delegation = dst_delegations[0].clone(); + let native_to_redelegate = cmp::min(src_delegation.amount, dst_delegation.amount); + + if src_delegation.amount == native_to_redelegate { + src_delegations.remove(0); + } else { + src_delegations[0].amount -= native_to_redelegate; + } + + if dst_delegation.amount == native_to_redelegate { + dst_delegations.remove(0); + } else { + dst_delegations[0].amount -= native_to_redelegate; + } + new_redelegations.push(Redelegation::new( + &src_delegation.validator, + &dst_delegation.validator, + native_to_redelegate, + &src_delegation.denom, + )); + } + // eprintln!("new redelegations ={:?}", new_redelegations); + + new_redelegations +} + +//-------------------------------------------------------------------------------------------------- +// Batch logics +//-------------------------------------------------------------------------------------------------- + +/// If the received native amount after the unbonding period is less than expected, e.g. due to +/// rounding error or the validator(s) being slashed, then deduct the difference in amount evenly +/// from each unreconciled batch. +/// +/// The idea of "reconciling" is based on Stader's implementation: +/// https://github.com/stader-labs/stader-liquid-token/blob/v0.2.1/contracts/staking/src/contract.rs#L968-L1048 +pub(crate) fn reconcile_batches(batches: &mut [Batch], native_to_deduct: Uint128) { + let batch_count = batches.len() as u128; + let native_per_batch = native_to_deduct.u128() / batch_count; + let remainder = native_to_deduct.u128() % batch_count; + //let mut remaining_underflow = Uint128::zero(); + let mut underflows: HashMap = HashMap::default(); + + // distribute the underflows uniformly accross non-underflowing batches + for (i, batch) in batches.iter_mut().enumerate() { + let remainder_for_batch: u128 = u128::from((i + 1) as u128 <= remainder); + let native_for_batch = Uint128::new(native_per_batch + remainder_for_batch); + + if batch.amount_unclaimed < native_for_batch && batch_count > 1 { + // remaining_underflow += native_for_batch - batch.amount_unclaimed; + underflows.insert(i, native_for_batch - batch.amount_unclaimed); + } + batch.amount_unclaimed = batch.amount_unclaimed.saturating_sub(native_for_batch); + + batch.reconciled = true; + } + if !underflows.is_empty() { + let batch_count: u128 = batch_count - (underflows.len() as u128); + let to_deduct: Uint128 = underflows.iter().map(|v| v.1).sum(); + let native_per_batch = to_deduct.u128() / batch_count; + let remainder = to_deduct.u128() % batch_count; + let mut remaining_underflow = Uint128::zero(); + // the remaining underflow will be applied by oldest batch first. + for (i, batch) in batches.iter_mut().enumerate() { + if !batch.amount_unclaimed.is_zero() { + let remainder_for_batch: u128 = u128::from((i + 1) as u128 <= remainder); + let native_for_batch = Uint128::new(native_per_batch + remainder_for_batch); + if batch.amount_unclaimed < native_for_batch && batch_count > 1 { + remaining_underflow += native_for_batch - batch.amount_unclaimed; + } + batch.amount_unclaimed = batch.amount_unclaimed.saturating_sub(native_for_batch); + } + } + + if !remaining_underflow.is_zero() { + // the remaining underflow will be applied by oldest batch first. + for batch in batches.iter_mut() { + //} .enumerate() { + if !batch.amount_unclaimed.is_zero() && !remaining_underflow.is_zero() { + if batch.amount_unclaimed >= remaining_underflow { + batch.amount_unclaimed -= remaining_underflow; + remaining_underflow = Uint128::zero() + } else { + remaining_underflow -= batch.amount_unclaimed; + batch.amount_unclaimed = Uint128::zero(); + } + } + } + + if !remaining_underflow.is_zero() { + // no way to reconcile right now, need to top up some funds. + } + } + } +} diff --git a/contracts/hub-tf/src/migrations.rs b/contracts/hub-tf/src/migrations.rs new file mode 100644 index 00000000..a28ddad2 --- /dev/null +++ b/contracts/hub-tf/src/migrations.rs @@ -0,0 +1 @@ +// no migrations yet diff --git a/contracts/hub-tf/src/osmosis/denom.rs b/contracts/hub-tf/src/osmosis/denom.rs new file mode 100644 index 00000000..e7a78a5f --- /dev/null +++ b/contracts/hub-tf/src/osmosis/denom.rs @@ -0,0 +1,147 @@ +// source: https://github.com/White-Whale-Defi-Platform/white-whale-core/blob/feat/tokenfactory-lp/packages/pool-network/src/denom.rs + +use std::convert::{TryFrom, TryInto}; + +use osmosis_std_derive::CosmwasmExt; + +// see https://github.com/notional-labs/wasmd/blob/v0.30.0-sdk469.4/proto/cosmwasm/tokenfactory/v1beta1/tx.proto + +/// Coin defines a token with a denomination and an amount. +/// +/// NOTE: The amount field is an Int which implements the custom method +/// signatures required by gogoproto. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmos.base.v1beta1.Coin")] +pub struct Coin { + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub amount: ::prost::alloc::string::String, +} + +/// Create a Denom +/// +/// MsgCreateDenom defines the message structure for the CreateDenom gRPC service +/// method. It allows an account to create a new denom. It requires a sender +/// address and a sub denomination. The (sender_address, sub_denomination) tuple +/// must be unique and cannot be re-used. +/// +/// The resulting denom created is defined as +/// . The resulting denom's admin is +/// originally set to be the creator, but this can be changed later. The token +/// denom does not indicate the current admin. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgCreateDenom")] +pub struct MsgCreateDenom { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + /// subdenom can be up to 44 "alphanumeric" characters long. + #[prost(string, tag = "2")] + pub subdenom: ::prost::alloc::string::String, +} + +/// MsgCreateDenomResponse is the return value of MsgCreateDenom +/// It returns the full string of the newly created denom +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgCreateDenomResponse")] +pub struct MsgCreateDenomResponse { + #[prost(string, tag = "1")] + pub new_token_denom: ::prost::alloc::string::String, +} + +/// MsgMint is the sdk.Msg type for allowing an admin account to mint +/// more of a token. For now, we only support minting to the sender account +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgMint")] +pub struct MsgMint { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, + #[prost(string, tag = "3")] + pub mint_to_address: ::prost::alloc::string::String, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgMintResponse")] +pub struct MsgMintResponse {} + +/// MsgBurn is the sdk.Msg type for allowing an admin account to burn +/// a token. For now, we only support burning from the sender account. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgBurn")] +pub struct MsgBurn { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, + #[prost(string, tag = "3")] + pub burn_from_address: ::prost::alloc::string::String, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/osmosis.tokenfactory.v1beta1.MsgBurnResponse")] +pub struct MsgBurnResponse {} diff --git a/contracts/hub-tf/src/osmosis/mod.rs b/contracts/hub-tf/src/osmosis/mod.rs new file mode 100644 index 00000000..80617f64 --- /dev/null +++ b/contracts/hub-tf/src/osmosis/mod.rs @@ -0,0 +1,2 @@ +// +pub mod denom; diff --git a/contracts/hub-tf/src/queries.rs b/contracts/hub-tf/src/queries.rs new file mode 100644 index 00000000..47059124 --- /dev/null +++ b/contracts/hub-tf/src/queries.rs @@ -0,0 +1,197 @@ +use std::{ + collections::{BTreeSet, HashSet}, + iter::FromIterator, +}; + +use cosmwasm_std::{Decimal, Deps, Env, Order, StdResult, Uint128}; +use cw_storage_plus::Bound; +use pfc_steak::hub::{ + Batch, ConfigResponse, PendingBatch, StateResponse, UnbondRequestsByBatchResponseItem, + UnbondRequestsByUserResponseItem, +}; + +use crate::{ + helpers::query_delegations, + state, + state::{State, VALIDATORS, VALIDATORS_ACTIVE}, +}; + +const MAX_LIMIT: u32 = 30; +const DEFAULT_LIMIT: u32 = 10; + +pub fn config(deps: Deps) -> StdResult { + let state = State::default(); + let mut validators: BTreeSet = BTreeSet::new(); + for res in VALIDATORS.items(deps.storage, None, None, Order::Ascending) { + validators.insert(res?); + } + + let mut validators_active: BTreeSet = BTreeSet::new(); + for res in VALIDATORS_ACTIVE.items(deps.storage, None, None, Order::Ascending) { + let validator = res?; + validators.remove(&validator); + validators_active.insert(validator); + } + + let validator_active_vec: Vec = Vec::from_iter(validators_active); + let paused_validators: Vec = Vec::from_iter(validators); + + Ok(ConfigResponse { + owner: state.owner.load(deps.storage)?.into(), + new_owner: state.new_owner.may_load(deps.storage)?.map(|addr| addr.into()), + steak_token: state.steak_denom.load(deps.storage)?, + epoch_period: state.epoch_period.load(deps.storage)?, + unbond_period: state.unbond_period.load(deps.storage)?, + denom: state.denom.load(deps.storage)?, + fee_type: state.fee_account_type.load(deps.storage)?.to_string(), + fee_account: state.fee_account.load(deps.storage)?.to_string(), + fee_rate: state.fee_rate.load(deps.storage)?, + max_fee_rate: state.max_fee_rate.load(deps.storage)?, + validators: validator_active_vec, + paused_validators, + dust_collector: state.dust_collector.load(deps.storage)?.map(|a| a.to_string()), + token_factory: Some(state.token_factory_type.load(deps.storage)?.to_string()), + }) +} + +pub fn state(deps: Deps, env: Env) -> StdResult { + let state = State::default(); + let denom = state.denom.load(deps.storage)?; + let total_usteak = state.steak_minted.load(deps.storage)?; // query_cw20_total_supply(&deps.querier, &steak_token)?; + let mut validators: HashSet = Default::default(); + for res in VALIDATORS.items(deps.storage, None, None, Order::Ascending) { + validators.insert(res?); + } + let mut validators_active: HashSet = Default::default(); + for res in VALIDATORS_ACTIVE.items(deps.storage, None, None, Order::Ascending) { + validators_active.insert(res?); + } + validators.extend(validators_active); + let validator_vec: Vec = Vec::from_iter(validators); + let delegations = + query_delegations(&deps.querier, &validator_vec, &env.contract.address, &denom)?; + let total_native: u128 = delegations.iter().map(|d| d.amount).sum(); + + let exchange_rate = if total_usteak.is_zero() { + Decimal::one() + } else { + Decimal::from_ratio(total_native, total_usteak) + }; + + Ok(StateResponse { + total_usteak, + total_native: Uint128::new(total_native), + exchange_rate, + unlocked_coins: state.unlocked_coins.load(deps.storage)?, + }) +} + +pub fn pending_batch(deps: Deps) -> StdResult { + let state = State::default(); + + state.pending_batch.load(deps.storage) +} + +pub fn previous_batch(deps: Deps, id: u64) -> StdResult { + state::previous_batches().load(deps.storage, id) +} + +pub fn previous_batches( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult> { + let start = start_after.map(Bound::exclusive); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + state::previous_batches() + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| { + let (_, v) = item?; + Ok(v) + }) + .collect() +} +pub fn previous_batches_unreconciled(deps: Deps) -> StdResult> { + state::previous_batches() + .idx + .reconciled + .prefix("false".into()) + .range(deps.storage, None, None, Order::Ascending) + .map(|item| { + let (_, v) = item?; + Ok(v) + }) + .collect() +} + +pub fn unbond_requests_by_batch( + deps: Deps, + id: u64, + start_after: Option, + limit: Option, +) -> StdResult> { + //let addr: Addr; + let addr_clone; + let start = match start_after { + None => None, + Some(addr_str) => { + deps.api.addr_validate(&addr_str)?; + addr_clone = addr_str; + Some(Bound::exclusive(addr_clone.as_str())) + }, + }; + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + state::unbond_requests() + .prefix(id) + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| { + let (_, v) = item?; + Ok(v.into()) + }) + .collect() +} + +pub fn unbond_requests_by_user( + deps: Deps, + user: String, + start_after: Option, + limit: Option, +) -> StdResult> { + let user_addr = deps.api.addr_validate(&user)?; + + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + if let Some(start_id) = start_after { + state::unbond_requests() + .idx + .user + .prefix(user_addr.to_string()) + .range(deps.storage, None, None, Order::Ascending) + .filter(|r| { + let x = r.as_ref().unwrap(); + + x.1.id > start_id + }) + .take(limit) + .map(|item| { + let (_, v) = item?; + Ok(v.into()) + }) + .collect() + } else { + state::unbond_requests() + .idx + .user + .prefix(user_addr.to_string()) + .range(deps.storage, None, None, Order::Ascending) + .take(limit) + .map(|item| { + let (_, v) = item?; + Ok(v.into()) + }) + .collect() + } +} diff --git a/contracts/hub-tf/src/state.rs b/contracts/hub-tf/src/state.rs new file mode 100644 index 00000000..258008d1 --- /dev/null +++ b/contracts/hub-tf/src/state.rs @@ -0,0 +1,160 @@ +use cosmwasm_std::{Addr, Coin, Decimal, StdError, StdResult, Storage, Uint128}; +use cw_item_set::Set; +use cw_storage_plus::{Index, IndexList, IndexedMap, Item, MultiIndex}; +use pfc_steak::{ + hub::{Batch, FeeType, PendingBatch, UnbondRequest}, + hub_tf::TokenFactoryType, +}; + +pub(crate) const BATCH_KEY_V101: &str = "previous_batches_101"; +pub(crate) const BATCH_KEY_OWNER_V101: &str = "previous_batches_owner_101"; +pub(crate) const UNBOND_KEY_V101: &str = "unbond_101"; +pub(crate) const UNBOND_KEY_USER_V101: &str = "unbond_owner_101"; + +/// Validators who currently have delegations. This can be different due to being +/// paused/re-delegations +pub(crate) const VALIDATORS: Set<&str> = Set::new("validators_001", "validator_counter_001"); +/// Validators where we send delegations to. +pub(crate) const VALIDATORS_ACTIVE: Set<&str> = + Set::new("validators_active_001", "validator_active_counter_001"); + +// pub(crate) const BATCH_KEY_RECONCILED_V101: &str = "previous_batches__reconciled_101"; + +pub(crate) struct State<'a> { + /// Account who can call certain privileged functions + pub owner: Item<'a, Addr>, + /// Pending ownership transfer, awaiting acceptance by the new owner + pub new_owner: Item<'a, Addr>, + pub fee_account_type: Item<'a, FeeType>, + /// Account to send fees to + pub fee_account: Item<'a, Addr>, + /// Current fee rate + pub fee_rate: Item<'a, Decimal>, + /// Maximum fee rate + pub max_fee_rate: Item<'a, Decimal>, + /// denom to accept + pub denom: Item<'a, String>, + /// denom to mint. + pub steak_denom: Item<'a, String>, + /// coins minted + pub steak_minted: Item<'a, Uint128>, + /// How often the unbonding queue is to be executed + pub epoch_period: Item<'a, u64>, + /// The staking module's unbonding time, in seconds + pub unbond_period: Item<'a, u64>, + // Validators who will receive the delegations + // pub validators: Item<'a, Vec>, + /// Coins that can be reinvested + pub unlocked_coins: Item<'a, Vec>, + /// The current batch of un-bonding requests queued to be executed + pub pending_batch: Item<'a, PendingBatch>, + + /// Previous batches that have started unbonding but not yet finished + // pub previous_batches: IndexedMap<'a, u64, Batch, PreviousBatchesIndexes<'a>>, + /// Users' shares in un-bonding batches + //pub unbond_requests: IndexedMap<'a, (u64, &'a Addr), UnbondRequest, + // UnbondRequestsIndexes<'a>>, pub validators_active: Item<'a, Vec>, + /// coins in 'denom' held before reinvest was called. + pub prev_denom: Item<'a, Uint128>, + + /// Kuji version of Token-factory + /// @deprecated + pub kuji_token_factory: Item<'a, bool>, + /// Dust Collector contract + pub dust_collector: Item<'a, Option>, + /// INJ version of Token-factory + pub token_factory_type: Item<'a, TokenFactoryType>, +} + +impl Default for State<'static> { + fn default() -> Self { + Self { + owner: Item::new("owner"), + new_owner: Item::new("new_owner"), + fee_account: Item::new("fee_account"), + fee_rate: Item::new("fee_rate"), + max_fee_rate: Item::new("max_fee_rate"), + denom: Item::new("denom"), + steak_denom: Item::new("steak_denom"), + steak_minted: Item::new("steak_minted"), + //steak_token: Item::new("steak_token"), + epoch_period: Item::new("epoch_period"), + unbond_period: Item::new("unbond_period"), + // validators: Item::new("validators"), + unlocked_coins: Item::new("unlocked_coins"), + pending_batch: Item::new("pending_batch"), + // previous_batches: IndexedMap::new(BATCH_KEY_V101, pb_indexes), + // unbond_requests: IndexedMap::new("unbond_requests", ubr_indexes), + // validators_active: Item::new("validators_active"), + prev_denom: Item::new("prev_denom"), + fee_account_type: Item::new("fee_account_type"), + kuji_token_factory: Item::new("kuji_token_factory"), + dust_collector: Item::new("dust_collector"), + token_factory_type: Item::new("token_factory_type"), + } + } +} + +impl State<'_> { + pub fn assert_owner(&self, storage: &dyn Storage, sender: &Addr) -> StdResult<()> { + let owner = self.owner.load(storage)?; + if *sender == owner { + Ok(()) + } else { + Err(StdError::generic_err("unauthorized: sender is not owner")) + } + } +} + +pub fn previous_batches_reconciled_idx(_pk: &[u8], d: &Batch) -> String { + if d.reconciled { + String::from("true") + } else { + String::from("false") + } +} + +pub fn previous_batches<'a>() -> IndexedMap<'a, u64, Batch, PreviousBatchesIndexes<'a>> { + IndexedMap::new(BATCH_KEY_V101, PreviousBatchesIndexes { + reconciled: MultiIndex::new( + previous_batches_reconciled_idx, + BATCH_KEY_V101, + BATCH_KEY_OWNER_V101, + ), + }) +} + +pub fn unbond_requests_user_idx(_pk: &[u8], d: &UnbondRequest) -> String { + d.user.to_string() +} + +pub fn unbond_requests<'a>() +-> IndexedMap<'a, (u64, &'a str), UnbondRequest, UnbondRequestsIndexes<'a>> { + IndexedMap::new(UNBOND_KEY_V101, UnbondRequestsIndexes { + user: MultiIndex::new(unbond_requests_user_idx, UNBOND_KEY_V101, UNBOND_KEY_USER_V101), + }) +} + +pub struct PreviousBatchesIndexes<'a> { + // pk goes to second tuple element + pub reconciled: MultiIndex<'a, String, Batch, u64>, +} + +impl IndexList for PreviousBatchesIndexes<'_> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.reconciled]; + Box::new(v.into_iter()) + } +} + +pub struct UnbondRequestsIndexes<'a> { + // pk goes to second tuple element + pub user: MultiIndex<'a, String, UnbondRequest, (u64, Addr)>, +} + +impl IndexList for UnbondRequestsIndexes<'_> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.user]; + Box::new(v.into_iter()) + } +} diff --git a/contracts/hub-tf/src/testing/custom_querier.rs b/contracts/hub-tf/src/testing/custom_querier.rs new file mode 100644 index 00000000..5929ac13 --- /dev/null +++ b/contracts/hub-tf/src/testing/custom_querier.rs @@ -0,0 +1,66 @@ +use cosmwasm_std::{ + Addr, Coin, Empty, FullDelegation, Querier, QuerierResult, QueryRequest, SystemError, + WasmQuery, from_json, + testing::{BankQuerier, MOCK_CONTRACT_ADDR, StakingQuerier}, +}; + +use super::helpers::err_unsupported_query; +use crate::types::Delegation; + +#[derive(Default)] +pub(super) struct CustomQuerier { + pub bank_querier: BankQuerier, + pub staking_querier: StakingQuerier, +} + +impl Querier for CustomQuerier { + fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { + let request: QueryRequest<_> = match from_json(bin_request) { + Ok(v) => v, + Err(e) => { + return Err(SystemError::InvalidRequest { + error: format!("Parsing query request: {}", e), + request: bin_request.into(), + }) + .into(); + }, + }; + self.handle_query(&request) + } +} + +impl CustomQuerier { + pub fn set_bank_balances(&mut self, balances: &[Coin]) { + self.bank_querier = BankQuerier::new(&[(MOCK_CONTRACT_ADDR, balances)]); + } + + pub fn set_staking_delegations(&mut self, delegations: &[Delegation]) { + let fds = delegations + .iter() + .map(|d| FullDelegation { + delegator: Addr::unchecked(MOCK_CONTRACT_ADDR), + validator: d.validator.clone(), + amount: Coin::new(d.amount, "uluna"), + can_redelegate: Coin::new(0, "uluna"), + accumulated_rewards: vec![], + }) + .collect::>(); + + self.staking_querier = StakingQuerier::new("uluna", &[], &fds); + } + + pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { + match request { + QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: _, + msg, + }) => err_unsupported_query(msg), + + QueryRequest::Bank(query) => self.bank_querier.query(query), + + QueryRequest::Staking(query) => self.staking_querier.query(query), + + _ => err_unsupported_query(request), + } + } +} diff --git a/contracts/hub-tf/src/testing/helpers.rs b/contracts/hub-tf/src/testing/helpers.rs new file mode 100644 index 00000000..60d1c2b7 --- /dev/null +++ b/contracts/hub-tf/src/testing/helpers.rs @@ -0,0 +1,46 @@ +use cosmwasm_std::{ + Addr, BlockInfo, ContractInfo, Deps, Env, OwnedDeps, QuerierResult, SystemError, SystemResult, + Timestamp, from_json, + testing::{MOCK_CONTRACT_ADDR, MockApi, MockStorage, mock_env}, +}; +use pfc_steak::hub::QueryMsg; +use serde::de::DeserializeOwned; + +use super::custom_querier::CustomQuerier; +use crate::contract::query; + +pub(super) fn err_unsupported_query(request: T) -> QuerierResult { + SystemResult::Err(SystemError::InvalidRequest { + error: format!("[mock] unsupported query: {:?}", request), + request: Default::default(), + }) +} + +pub(super) fn mock_dependencies() -> OwnedDeps { + OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: CustomQuerier::default(), + custom_query_type: Default::default(), + } +} + +pub(super) fn mock_env_at_timestamp(timestamp: u64) -> Env { + Env { + block: BlockInfo { + height: 12_345, + time: Timestamp::from_seconds(timestamp), + chain_id: "cosmos-testnet-14002".to_string(), + }, + contract: ContractInfo { + address: Addr::unchecked(MOCK_CONTRACT_ADDR), + }, + transaction: None, + } +} + +pub(super) fn query_helper(deps: Deps, msg: QueryMsg) -> T { + let bin = query(deps, mock_env(), msg).unwrap(); + //eprintln!("Query Response {:?}",&bin); + from_json(bin).unwrap() +} diff --git a/contracts/hub-tf/src/testing/mod.rs b/contracts/hub-tf/src/testing/mod.rs new file mode 100644 index 00000000..a059f3e3 --- /dev/null +++ b/contracts/hub-tf/src/testing/mod.rs @@ -0,0 +1,4 @@ +mod custom_querier; + +mod helpers; +mod tests; diff --git a/contracts/hub-tf/src/testing/tests.rs b/contracts/hub-tf/src/testing/tests.rs new file mode 100644 index 00000000..4d356131 --- /dev/null +++ b/contracts/hub-tf/src/testing/tests.rs @@ -0,0 +1,1880 @@ +use std::str::FromStr; + +use cosmwasm_std::{ + Addr, BankMsg, Coin, CosmosMsg, Decimal, DistributionMsg, Order, OwnedDeps, ReplyOn, StdError, + StdResult, SubMsg, Uint128, WasmMsg, + testing::{MOCK_CONTRACT_ADDR, MockApi, MockStorage, mock_env, mock_info}, + to_json_binary, +}; +use pfc_steak::{ + hub::{ + Batch, CallbackMsg, ConfigResponse, PendingBatch, QueryMsg, StateResponse, UnbondRequest, + UnbondRequestsByBatchResponseItem, UnbondRequestsByUserResponseItem, + }, + hub_tf::{ExecuteMsg, InstantiateMsg}, +}; + +use super::{ + custom_querier::CustomQuerier, + helpers::{mock_dependencies, mock_env_at_timestamp, query_helper}, +}; +use crate::{ + contract::{REPLY_REGISTER_RECEIVED_COINS, execute, instantiate}, + helpers::{parse_coin, parse_received_fund}, + math::{ + compute_redelegations_for_rebalancing, compute_redelegations_for_removal, + compute_undelegations, + }, + state::{State, VALIDATORS, previous_batches, unbond_requests}, + token_factory::{ + denom, + denom::{MsgBurn, MsgCreateDenom, MsgMint}, + }, + types::{Coins, Delegation, Redelegation, Undelegation}, +}; + +//-------------------------------------------------------------------------------------------------- +// Test setup +//-------------------------------------------------------------------------------------------------- + +fn setup_test() -> OwnedDeps { + let mut deps = mock_dependencies(); + + let res = instantiate( + deps.as_mut(), + mock_env_at_timestamp(10000), + mock_info("deployer", &[]), + InstantiateMsg { + owner: "larry".to_string(), + denom: "uxyz".to_string(), + steak_denom: "boneXYZ".to_string(), + fee_account_type: "Wallet".to_string(), + fee_account: "the_fee_man".to_string(), + fee_amount: Decimal::from_ratio(10_u128, 100_u128), //10% + max_fee_amount: Decimal::from_ratio(20_u128, 100_u128), //20% + + epoch_period: 259200, // 3 * 24 * 60 * 60 = 3 days + unbond_period: 1814400, // 21 * 24 * 60 * 60 = 21 days + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string()], + token_factory: "CosmWasm".to_string(), + dust_collector: Some("dusty_1".to_string()), + }, + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + let c = >::into(MsgCreateDenom { + sender: "cosmos2contract".to_string(), + subdenom: "boneXYZ".to_string(), + }); + assert_eq!(res.messages[0], SubMsg::new(c)); + + deps +} + +fn setup_test_fee_split() -> OwnedDeps { + let mut deps = mock_dependencies(); + + let res = instantiate( + deps.as_mut(), + mock_env_at_timestamp(10000), + mock_info("deployer", &[]), + InstantiateMsg { + owner: "larry".to_string(), + denom: "uxyz".to_string(), + steak_denom: "sXYZ".to_string(), + fee_account_type: "FeeSplit".to_string(), + fee_account: "fee_split_contract".to_string(), + fee_amount: Decimal::from_ratio(10_u128, 100_u128), //10% + max_fee_amount: Decimal::from_ratio(20_u128, 100_u128), //20% + epoch_period: 259200, // 3 * 24 * 60 * 60 = 3 days + unbond_period: 1814400, // 21 * 24 * 60 * 60 = 21 days + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string()], + + token_factory: "CosmWasm".to_string(), + dust_collector: Some("dusty_2".to_string()), + }, + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + let c = >::into(MsgCreateDenom { + sender: "cosmos2contract".to_string(), + subdenom: "sXYZ".to_string(), + }); + assert_eq!(res.messages[0], SubMsg::new(c)); + + deps +} + +//-------------------------------------------------------------------------------------------------- +// Execution +//-------------------------------------------------------------------------------------------------- + +#[test] +fn proper_instantiation() { + let deps = setup_test(); + + let res: ConfigResponse = query_helper(deps.as_ref(), QueryMsg::Config {}); + assert_eq!(res, ConfigResponse { + owner: "larry".to_string(), + new_owner: None, + steak_token: "factory/cosmos2contract/boneXYZ".to_string(), + epoch_period: 259200, + unbond_period: 1814400, + denom: "uxyz".to_string(), + fee_type: "Wallet".to_string(), + fee_account: "the_fee_man".to_string(), + fee_rate: Decimal::from_ratio(10_u128, 100_u128), + max_fee_rate: Decimal::from_ratio(20_u128, 100_u128), + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string(),], + paused_validators: vec![], + dust_collector: Some("dusty_1".to_string()), + token_factory: Some("CosmWasm".to_string()) + }); + + let res: StateResponse = query_helper(deps.as_ref(), QueryMsg::State {}); + assert_eq!(res, StateResponse { + total_usteak: Uint128::zero(), + total_native: Uint128::zero(), + exchange_rate: Decimal::one(), + unlocked_coins: vec![], + },); + + let res: PendingBatch = query_helper(deps.as_ref(), QueryMsg::PendingBatch {}); + assert_eq!(res, PendingBatch { + id: 1, + usteak_to_burn: Uint128::zero(), + est_unbond_start_time: 269200, // 10,000 + 259,200 + },); + let deps_fee_split = setup_test_fee_split(); + + let res_fee_split: ConfigResponse = query_helper(deps_fee_split.as_ref(), QueryMsg::Config {}); + assert_eq!(res_fee_split, ConfigResponse { + owner: "larry".to_string(), + new_owner: None, + steak_token: "factory/cosmos2contract/sXYZ".to_string(), + epoch_period: 259200, + unbond_period: 1814400, + denom: "uxyz".to_string(), + fee_type: "FeeSplit".to_string(), + fee_account: "fee_split_contract".to_string(), + fee_rate: Decimal::from_ratio(10_u128, 100_u128), + max_fee_rate: Decimal::from_ratio(20_u128, 100_u128), + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string(),], + paused_validators: vec![], + dust_collector: Some("dusty_2".to_string()), + token_factory: Some("CosmWasm".to_string()) + }); +} + +#[test] +fn bonding() { + let mut deps = setup_test(); + + // Bond when no delegation has been made + // In this case, the full deposit simply goes to the first validator + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("user_1", &[Coin::new(1_000_000, "uxyz")]), + ExecuteMsg::Bond { + receiver: None, + exec_msg: None, + }, + ) + .unwrap(); + + // 3 messages. (switched to 3 so we can 'send' instead of 'transfer' minted tokens, so contract + // will know about it 1 - delegate + // 2 - mint token (to ourselves) + // 3 - send/transfer it + assert_eq!(res.messages.len(), 3); + assert_eq!(res.messages[0], SubMsg { + msg: Delegation::new("alice", 1_000_000, "uxyz").to_cosmos_msg(), + gas_limit: None, + id: REPLY_REGISTER_RECEIVED_COINS, + reply_on: ReplyOn::Never, + }); + let mint_msg = >::into(MsgMint { + sender: "cosmos2contract".to_string(), + amount: Some(denom::Coin { + denom: "factory/cosmos2contract/boneXYZ".to_string(), + amount: Uint128::new(1_000_000).to_string(), + }), + }); + assert_eq!(res.messages[1], SubMsg { + id: 0, + msg: mint_msg, + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + assert_eq!(res.messages[2], SubMsg { + id: 0, + msg: CosmosMsg::Bank(BankMsg::Send { + to_address: "user_1".to_string(), + amount: vec![Coin { + denom: "factory/cosmos2contract/boneXYZ".to_string(), + amount: Uint128::new(1_000_000) + }], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + // Bond when there are existing delegations, and Luna:Steak exchange rate is >1 + // Previously user 1 delegated 1,000,000 uluna. We assume we have accumulated 2.5% yield at + // 1025000 staked + deps.querier.set_staking_delegations(&[ + Delegation::new("alice", 341667, "uxyz"), + Delegation::new("bob", 341667, "uxyz"), + Delegation::new("charlie", 341666, "uxyz"), + ]); + + // Charlie has the smallest amount of delegation, so the full deposit goes to him + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("user_2", &[Coin::new(12345, "uxyz")]), + ExecuteMsg::Bond { + receiver: Some("user_3".to_string()), + exec_msg: None, + }, + ) + .unwrap(); + + assert_eq!(res.messages.len(), 3); + assert_eq!(res.messages[0], SubMsg { + msg: Delegation::new("charlie", 12345, "uxyz").to_cosmos_msg(), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never + }); + let mint_msg = >::into(MsgMint { + sender: "cosmos2contract".to_string(), + amount: Some(denom::Coin { + denom: "factory/cosmos2contract/boneXYZ".to_string(), + amount: Uint128::new(12_043).to_string(), + }), + }); + assert_eq!(res.messages[1], SubMsg { + id: 0, + msg: mint_msg, + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + assert_eq!(res.messages[2], SubMsg { + id: 0, + msg: CosmosMsg::Bank(BankMsg::Send { + to_address: "user_3".to_string(), + + amount: vec![Coin { + denom: "factory/cosmos2contract/boneXYZ".to_string(), + amount: Uint128::new(12_043) + }], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + // Check the state after bonding + deps.querier.set_staking_delegations(&[ + Delegation::new("alice", 341667, "uxyz"), + Delegation::new("bob", 341667, "uxyz"), + Delegation::new("charlie", 354011, "uxyz"), + ]); + + let res: StateResponse = query_helper(deps.as_ref(), QueryMsg::State {}); + assert_eq!(res, StateResponse { + total_usteak: Uint128::new(1012043), + total_native: Uint128::new(1037345), + exchange_rate: Decimal::from_ratio(1037345u128, 1012043u128), + unlocked_coins: vec![], + }); +} + +#[test] +fn harvesting() { + let mut deps = setup_test(); + + // Assume users have bonded a total of 1,000,000 uluna and minted the same amount of usteak + deps.querier.set_staking_delegations(&[ + Delegation::new("alice", 341667, "uxyz"), + Delegation::new("bob", 341667, "uxyz"), + Delegation::new("charlie", 341666, "uxyz"), + ]); + + let res = execute(deps.as_mut(), mock_env(), mock_info("worker", &[]), ExecuteMsg::Harvest {}) + .unwrap(); + + assert_eq!(res.messages.len(), 4); + assert_eq!(res.messages[0], SubMsg { + msg: CosmosMsg::Distribution(DistributionMsg::WithdrawDelegatorReward { + validator: "alice".to_string(), + }), + gas_limit: None, + id: REPLY_REGISTER_RECEIVED_COINS, + reply_on: ReplyOn::Never, + }); + assert_eq!(res.messages[1], SubMsg { + msg: CosmosMsg::Distribution(DistributionMsg::WithdrawDelegatorReward { + validator: "bob".to_string(), + }), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never + }); + assert_eq!(res.messages[2], SubMsg { + msg: CosmosMsg::Distribution(DistributionMsg::WithdrawDelegatorReward { + validator: "charlie".to_string(), + }), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never + }); + assert_eq!(res.messages[3], SubMsg { + id: 0, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: MOCK_CONTRACT_ADDR.to_string(), + msg: to_json_binary(&ExecuteMsg::Callback(CallbackMsg::Reinvest {})).unwrap(), + funds: vec![], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); +} + +#[test] +fn reinvesting() { + let mut deps = setup_test(); + let state = State::default(); + + deps.querier.set_staking_delegations(&[ + Delegation::new("alice", 333334, "uxyz"), + Delegation::new("bob", 333333, "uxyz"), + Delegation::new("charlie", 333333, "uxyz"), + ]); + state.prev_denom.save(deps.as_mut().storage, &Uint128::zero()).unwrap(); + deps.querier.set_bank_balances(&[Coin::new(234u128, "uxyz")]); + + // After the swaps, `unlocked_coins` should contain only uxyz and unknown denoms + state + .unlocked_coins + .save(deps.as_mut().storage, &vec![ + Coin::new(234, "uxyz"), + Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ), + ]) + .unwrap(); + + // Bob has the smallest amount of delegations, so all proceeds go to him + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(MOCK_CONTRACT_ADDR, &[]), + ExecuteMsg::Callback(CallbackMsg::Reinvest {}), + ) + .unwrap(); + + assert_eq!(res.messages.len(), 2); + assert_eq!(res.messages[0], SubMsg { + id: 0, + msg: Delegation::new("bob", 234 - 23, "uxyz").to_cosmos_msg(), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + let send_msg = BankMsg::Send { + to_address: "the_fee_man".into(), + amount: vec![Coin::new(23u128, "uxyz")], + }; + assert_eq!(res.messages[1], SubMsg { + id: 0, + msg: CosmosMsg::Bank(send_msg), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + // Storage should have been updated + let unlocked_coins = state.unlocked_coins.load(deps.as_ref().storage).unwrap(); + assert_eq!(unlocked_coins, vec![Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + )],); +} + +#[test] +fn reinvesting_fee_split() { + let mut deps = setup_test_fee_split(); + let state = State::default(); + + deps.querier.set_staking_delegations(&[ + Delegation::new("alice", 333334, "uxyz"), + Delegation::new("bob", 333333, "uxyz"), + Delegation::new("charlie", 333333, "uxyz"), + ]); + state.prev_denom.save(deps.as_mut().storage, &Uint128::zero()).unwrap(); + deps.querier.set_bank_balances(&[Coin::new(234u128, "uxyz")]); + + // After the swaps, `unlocked_coins` should contain only uxyz and unknown denoms + state + .unlocked_coins + .save(deps.as_mut().storage, &vec![ + Coin::new(234, "uxyz"), + Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ), + ]) + .unwrap(); + + // Bob has the smallest amount of delegations, so all proceeds go to him + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(MOCK_CONTRACT_ADDR, &[]), + ExecuteMsg::Callback(CallbackMsg::Reinvest {}), + ) + .unwrap(); + + assert_eq!(res.messages.len(), 2); + assert_eq!(res.messages[0], SubMsg { + id: 0, + msg: Delegation::new("bob", 234 - 23, "uxyz").to_cosmos_msg(), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + let send_msg = pfc_fee_split::fee_split_msg::ExecuteMsg::Deposit { + flush: false, + }; + + assert_eq!(res.messages[1], SubMsg { + id: 0, + msg: send_msg + .into_cosmos_msg("fee_split_contract", vec![Coin::new(23u128, "uxyz")]) + .unwrap(), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + // Storage should have been updated + let unlocked_coins = state.unlocked_coins.load(deps.as_ref().storage).unwrap(); + assert_eq!(unlocked_coins, vec![Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + )],); +} + +#[test] +fn queuing_unbond() { + let mut deps = setup_test(); + let state = State::default(); + + // Only Steak token is accepted for unbonding requests + let err = execute( + deps.as_mut(), + mock_env(), + mock_info("random_token", &[Coin { + denom: "asldkj".into(), + amount: Uint128::new(69_420), + }]), + ExecuteMsg::Unbond { + receiver: None, + }, + ) + .unwrap_err(); + + assert_eq!( + err, + StdError::generic_err("you can only send factory/cosmos2contract/boneXYZ tokens to unbond") + ); + + // User 1 creates an unbonding request before `est_unbond_start_time` is reached. The unbond + // request is saved, but not the pending batch is not submitted for unbonding + let res = execute( + deps.as_mut(), + mock_env_at_timestamp(12345), // est_unbond_start_time = 269200 + mock_info("steak_token", &[Coin { + denom: "factory/cosmos2contract/boneXYZ".into(), + amount: Uint128::new(23_456), + }]), + ExecuteMsg::Unbond { + receiver: Some("user_1".to_string()), + }, + ) + .unwrap(); + + assert_eq!(res.messages.len(), 0); + + // User 2 creates an unbonding request after `est_unbond_start_time` is reached. The unbond + // request is saved, and the pending is automatically submitted for unbonding + let res = execute( + deps.as_mut(), + mock_env_at_timestamp(269201), // est_unbond_start_time = 269200 + mock_info("user_3", &[Coin { + denom: "factory/cosmos2contract/boneXYZ".into(), + amount: Uint128::new(69_420), + }]), + ExecuteMsg::Unbond { + receiver: None, + }, + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + assert_eq!(res.messages[0], SubMsg { + id: 0, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: MOCK_CONTRACT_ADDR.to_string(), + msg: to_json_binary(&ExecuteMsg::SubmitBatch {}).unwrap(), + funds: vec![], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + // The users' unbonding requests should have been saved + let ubr1 = unbond_requests() + .load(deps.as_ref().storage, (1u64, Addr::unchecked("user_1").as_ref())) + .unwrap(); + let ubr2 = unbond_requests() + .load(deps.as_ref().storage, (1u64, Addr::unchecked("user_3").as_ref())) + .unwrap(); + + assert_eq!(ubr1, UnbondRequest { + id: 1, + user: Addr::unchecked("user_1"), + shares: Uint128::new(23456), + }); + assert_eq!(ubr2, UnbondRequest { + id: 1, + user: Addr::unchecked("user_3"), + shares: Uint128::new(69420), + }); + + // Pending batch should have been updated + let pending_batch = state.pending_batch.load(deps.as_ref().storage).unwrap(); + assert_eq!(pending_batch, PendingBatch { + id: 1, + usteak_to_burn: Uint128::new(92876), // 23,456 + 69,420 + est_unbond_start_time: 269200, + }); +} + +#[test] +fn submitting_batch() { + let mut deps = setup_test(); + let state = State::default(); + + // uluna bonded: 1,037,345 + // usteak supply: 1,012,043 + // uluna per ustake: 1.025 + deps.querier.set_staking_delegations(&[ + Delegation::new("alice", 345_782, "uxyz"), + Delegation::new("bob", 345_782, "uxyz"), + Delegation::new("charlie", 345_781, "uxyz"), + ]); + state.steak_minted.save(deps.as_mut().storage, &Uint128::new(1_012_043)).unwrap(); + + // We continue from the contract state at the end of the last test + let unbond_reqs = vec![ + UnbondRequest { + id: 1, + user: Addr::unchecked("user_1"), + shares: Uint128::new(23456), + }, + UnbondRequest { + id: 1, + user: Addr::unchecked("user_3"), + shares: Uint128::new(69420), + }, + ]; + + for unbond_request in &unbond_reqs { + unbond_requests() + .save( + deps.as_mut().storage, + (unbond_request.id, Addr::unchecked(unbond_request.user.clone()).as_str()), + unbond_request, + ) + .unwrap(); + } + + state + .pending_batch + .save(deps.as_mut().storage, &PendingBatch { + id: 1, + usteak_to_burn: Uint128::new(92876), // 23,456 + 69,420 + est_unbond_start_time: 269200, + }) + .unwrap(); + + // Anyone can invoke `submit_batch`. Here we continue from the previous test and assume it is + // invoked automatically as user 2 submits the unbonding request + // + // usteak to burn: 23,456 + 69,420 = 92,876 + // uluna to unbond: 1,037,345 * 92,876 / 1,012,043 = 95,197 + // + // Target: (1,037,345 - 95,197) / 3 = 314,049 + // Remainer: 1 + // Alice: 345,782 - (314,049 + 1) = 31,732 + // Bob: 345,782 - (314,049 + 0) = 31,733 + // Charlie: 345,781 - (314,049 + 0) = 31,732 + let res = execute( + deps.as_mut(), + mock_env_at_timestamp(269_201), + mock_info(MOCK_CONTRACT_ADDR, &[]), + ExecuteMsg::SubmitBatch {}, + ) + .unwrap(); + + assert_eq!(res.messages.len(), 4); + assert_eq!(res.messages[0], SubMsg { + msg: Undelegation::new("alice", 31732, "uxyz").to_cosmos_msg(), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never + }); + assert_eq!(res.messages[1], SubMsg { + msg: Undelegation::new("bob", 31733, "uxyz").to_cosmos_msg(), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never + }); + assert_eq!(res.messages[2], SubMsg { + msg: Undelegation::new("charlie", 31732, "uxyz").to_cosmos_msg(), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never + }); + + let burn_msg = >::into(MsgBurn { + sender: "cosmos2contract".to_string(), + amount: Some(denom::Coin { + denom: "factory/cosmos2contract/boneXYZ".to_string(), + amount: Uint128::new(92_876).to_string(), + }), + }); + assert_eq!(res.messages[3], SubMsg { + id: 0, + msg: burn_msg, + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + // A new pending batch should have been created + let pending_batch = state.pending_batch.load(deps.as_ref().storage).unwrap(); + assert_eq!(pending_batch, PendingBatch { + id: 2, + usteak_to_burn: Uint128::zero(), + est_unbond_start_time: 528401, // 269,201 + 259,200 + }); + + // Previous batch should have been updated + let previous_batch = previous_batches().load(deps.as_ref().storage, 1u64).unwrap(); + assert_eq!(previous_batch, Batch { + id: 1, + reconciled: false, + total_shares: Uint128::new(92876), + amount_unclaimed: Uint128::new(95197), + est_unbond_end_time: 2083601, // 269,201 + 1,814,400 + }); +} + +#[test] +fn reconciling() { + let mut deps = setup_test(); + let state = State::default(); + + let previous_batch_list = vec![ + Batch { + id: 1, + reconciled: true, + total_shares: Uint128::new(92876), + amount_unclaimed: Uint128::new(95197), // 1.025 Luna per Steak + est_unbond_end_time: 10000, + }, + Batch { + id: 2, + reconciled: false, + total_shares: Uint128::new(1345), + amount_unclaimed: Uint128::new(1385), // 1.030 Luna per Steak + est_unbond_end_time: 20000, + }, + Batch { + id: 3, + reconciled: false, + total_shares: Uint128::new(1456), + amount_unclaimed: Uint128::new(1506), // 1.035 Luna per Steak + est_unbond_end_time: 30000, + }, + Batch { + id: 4, + reconciled: false, + total_shares: Uint128::new(1567), + amount_unclaimed: Uint128::new(1629), // 1.040 Luna per Steak + est_unbond_end_time: 40000, // not yet finished unbonding, ignored + }, + ]; + + for previous_batch in &previous_batch_list { + previous_batches().save(deps.as_mut().storage, previous_batch.id, previous_batch).unwrap(); + } + + state + .unlocked_coins + .save(deps.as_mut().storage, &vec![ + Coin::new(10000, "uxyz"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ), + ]) + .unwrap(); + + deps.querier.set_bank_balances(&[ + Coin::new(12345, "uxyz"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B"), + ]); + + execute( + deps.as_mut(), + mock_env_at_timestamp(35000), + mock_info("worker", &[]), + ExecuteMsg::Reconcile {}, + ) + .unwrap(); + + // Expected received: batch 2 + batch 3 = 1385 + 1506 = 2891 + // Expected unlocked: 10000 + // Expected: 12891 + // Actual: 12345 + // Shortfall: 12891 - 12345 = 456 + // + // uluna per batch: 546 / 2 = 273 + // remainder: 0 + // batch 2: 1385 - 273 = 1112 + // batch 3: 1506 - 273 = 1233 + let batch = previous_batches().load(deps.as_ref().storage, 2u64).unwrap(); + assert_eq!(batch, Batch { + id: 2, + reconciled: true, + total_shares: Uint128::new(1345), + amount_unclaimed: Uint128::new(1112), // 1385 - 273 + est_unbond_end_time: 20000, + }); + + let batch = previous_batches().load(deps.as_ref().storage, 3u64).unwrap(); + assert_eq!(batch, Batch { + id: 3, + reconciled: true, + total_shares: Uint128::new(1456), + amount_unclaimed: Uint128::new(1233), // 1506 - 273 + est_unbond_end_time: 30000, + }); + + // Batches 1 and 4 should not have changed + let batch = previous_batches().load(deps.as_ref().storage, 1u64).unwrap(); + assert_eq!(batch, previous_batch_list[0]); + + let batch = previous_batches().load(deps.as_ref().storage, 4u64).unwrap(); + assert_eq!(batch, previous_batch_list[3]); +} + +#[test] +fn withdrawing_unbonded() { + let mut deps = setup_test(); + let state = State::default(); + + // We simulate a most general case: + // - batches 1 and 2 have finished unbonding + // - batch 3 have been submitted for unbonding but have not finished + // - batch 4 is still pending + let unbond_reqs = vec![ + UnbondRequest { + id: 1, + user: Addr::unchecked("user_1"), + shares: Uint128::new(23456), + }, + UnbondRequest { + id: 1, + user: Addr::unchecked("user_3"), + shares: Uint128::new(69420), + }, + UnbondRequest { + id: 2, + user: Addr::unchecked("user_1"), + shares: Uint128::new(34567), + }, + UnbondRequest { + id: 3, + user: Addr::unchecked("user_1"), + shares: Uint128::new(45678), + }, + UnbondRequest { + id: 4, + user: Addr::unchecked("user_1"), + shares: Uint128::new(56789), + }, + ]; + + for unbond_request in &unbond_reqs { + unbond_requests() + .save( + deps.as_mut().storage, + (unbond_request.id, Addr::unchecked(unbond_request.user.clone()).as_str()), + unbond_request, + ) + .unwrap(); + } + + let previous_batch_list = vec![ + Batch { + id: 1, + reconciled: true, + total_shares: Uint128::new(92876), + amount_unclaimed: Uint128::new(95197), // 1.025 Luna per Steak + est_unbond_end_time: 10000, + }, + Batch { + id: 2, + reconciled: true, + total_shares: Uint128::new(34567), + amount_unclaimed: Uint128::new(35604), // 1.030 Luna per Steak + est_unbond_end_time: 20000, + }, + Batch { + id: 3, + reconciled: false, // finished unbonding, but not reconciled; ignored + total_shares: Uint128::new(45678), + amount_unclaimed: Uint128::new(47276), // 1.035 Luna per Steak + est_unbond_end_time: 20000, + }, + Batch { + id: 4, + reconciled: true, + total_shares: Uint128::new(56789), + amount_unclaimed: Uint128::new(59060), // 1.040 Luna per Steak + est_unbond_end_time: 30000, /* reconciled, but not yet finished unbonding; + * ignored */ + }, + ]; + + for previous_batch in &previous_batch_list { + previous_batches().save(deps.as_mut().storage, previous_batch.id, previous_batch).unwrap(); + } + + state + .pending_batch + .save(deps.as_mut().storage, &PendingBatch { + id: 4, + usteak_to_burn: Uint128::new(56789), + est_unbond_start_time: 100000, + }) + .unwrap(); + + // Attempt to withdraw before any batch has completed unbonding. Should error + let err = execute( + deps.as_mut(), + mock_env_at_timestamp(5000), + mock_info("user_1", &[]), + ExecuteMsg::WithdrawUnbonded { + receiver: None, + }, + ) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("withdrawable amount is zero")); + + // Attempt to withdraw once batches 1 and 2 have finished unbonding, but 3 has not yet + // + // Withdrawable from batch 1: 95,197 * 23,456 / 92,876 = 24,042 + // Withdrawable from batch 2: 35,604 + // Total withdrawable: 24,042 + 35,604 = 59,646 + // + // Batch 1 should be updated: + // Total shares: 92,876 - 23,456 = 69,420 + // Unclaimed uluna: 95,197 - 24,042 = 71,155 + // + // Batch 2 is completely withdrawn, should be purged from storage + let res = execute( + deps.as_mut(), + mock_env_at_timestamp(25000), + mock_info("user_1", &[]), + ExecuteMsg::WithdrawUnbonded { + receiver: None, + }, + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + assert_eq!(res.messages[0], SubMsg { + id: 0, + msg: CosmosMsg::Bank(BankMsg::Send { + to_address: "user_1".to_string(), + amount: vec![Coin::new(59646, "uxyz")], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + // Previous batches should have been updated + let batch = previous_batches().load(deps.as_ref().storage, 1u64).unwrap(); + assert_eq!(batch, Batch { + id: 1, + reconciled: true, + total_shares: Uint128::new(69420), + amount_unclaimed: Uint128::new(71155), + est_unbond_end_time: 10000, + }); + + let err = previous_batches().load(deps.as_ref().storage, 2u64).unwrap_err(); + match err { + StdError::NotFound { + .. + } => {}, + _ => { + panic!("Should have been not found") + }, + }; + + // User 1's unbond requests in batches 1 and 2 should have been deleted + let err1 = unbond_requests() + .load(deps.as_ref().storage, (1u64, Addr::unchecked("user_1").as_str())) + .unwrap_err(); + let err2 = unbond_requests() + .load(deps.as_ref().storage, (1u64, Addr::unchecked("user_1").as_str())) + .unwrap_err(); + + match err1 { + StdError::NotFound { + .. + } => {}, + _ => { + panic!("Should have been not found") + }, + }; + + match err2 { + StdError::NotFound { + .. + } => {}, + _ => { + panic!("Should have been not found") + }, + }; + + // User 3 attempt to withdraw; also specifying a receiver + let res = execute( + deps.as_mut(), + mock_env_at_timestamp(25000), + mock_info("user_3", &[]), + ExecuteMsg::WithdrawUnbonded { + receiver: Some("user_2".to_string()), + }, + ) + .unwrap(); + + assert_eq!(res.messages.len(), 1); + assert_eq!(res.messages[0], SubMsg { + id: 0, + msg: CosmosMsg::Bank(BankMsg::Send { + to_address: "user_2".to_string(), + amount: vec![Coin::new(71155, "uxyz")], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + // Batch 1 and user 2's unbonding request should have been purged from storage + let err = previous_batches().load(deps.as_ref().storage, 1u64).unwrap_err(); + match err { + StdError::NotFound { + .. + } => {}, + _ => { + panic!("Should have been not found") + }, + }; + + let err = unbond_requests() + .load(deps.as_ref().storage, (1u64, Addr::unchecked("user_3").as_str())) + .unwrap_err(); + + match err { + StdError::NotFound { + .. + } => {}, + _ => { + panic!("Should have been not found") + }, + }; +} + +#[test] +fn adding_validator() { + let mut deps = setup_test(); + //let state = State::default(); + + let err = + execute(deps.as_mut(), mock_env(), mock_info("jake", &[]), ExecuteMsg::AddValidator { + validator: "dave".to_string(), + }) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("unauthorized: sender is not owner")); + + let err = + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::AddValidator { + validator: "alice".to_string(), + }) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("validator is already whitelisted")); + + let res = + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::AddValidator { + validator: "dave".to_string(), + }) + .unwrap(); + + assert_eq!(res.messages.len(), 0); + + assert!(VALIDATORS.contains(deps.as_ref().storage, "alice")); + assert!(VALIDATORS.contains(deps.as_ref().storage, "bob")); + assert!(VALIDATORS.contains(deps.as_ref().storage, "charlie")); + assert!(VALIDATORS.contains(deps.as_ref().storage, "dave")); + assert_eq!(VALIDATORS.count(deps.as_ref().storage).unwrap(), 4); +} + +#[test] +fn removing_validator() { + let mut deps = setup_test(); + //let state = State::default(); + + deps.querier.set_staking_delegations(&[ + Delegation::new("alice", 341667, "uxyz"), + Delegation::new("bob", 341667, "uxyz"), + Delegation::new("charlie", 341666, "uxyz"), + ]); + + let err = + execute(deps.as_mut(), mock_env(), mock_info("jake", &[]), ExecuteMsg::RemoveValidator { + validator: "charlie".to_string(), + }) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("unauthorized: sender is not owner")); + + let err = + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::RemoveValidator { + validator: "dave".to_string(), + }) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("validator is not already whitelisted")); + + // Target: (341667 + 341667 + 341666) / 2 = 512500 + // Remainder: 0 + // Alice: 512500 + 0 - 341667 = 170833 + // Bob: 512500 + 0 - 341667 = 170833 + let res = + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::RemoveValidator { + validator: "charlie".to_string(), + }) + .unwrap(); + + assert_eq!(res.messages.len(), 2); + assert_eq!(res.messages[0], SubMsg { + msg: Redelegation::new("charlie", "alice", 170833, "uxyz").to_cosmos_msg(), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never + },); + assert_eq!(res.messages[1], SubMsg { + msg: Redelegation::new("charlie", "bob", 170833, "uxyz").to_cosmos_msg(), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never + },); + + assert!(VALIDATORS.contains(deps.as_ref().storage, "alice")); + assert!(VALIDATORS.contains(deps.as_ref().storage, "bob")); + assert_eq!(VALIDATORS.count(deps.as_ref().storage).unwrap(), 2); +} + +#[test] +fn transferring_ownership() { + let mut deps = setup_test(); + let state = State::default(); + + let err = + execute(deps.as_mut(), mock_env(), mock_info("jake", &[]), ExecuteMsg::TransferOwnership { + new_owner: "jake".to_string(), + }) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("unauthorized: sender is not owner")); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("larry", &[]), + ExecuteMsg::TransferOwnership { + new_owner: "jake".to_string(), + }, + ) + .unwrap(); + + assert_eq!(res.messages.len(), 0); + + let owner = state.owner.load(deps.as_ref().storage).unwrap(); + assert_eq!(owner, Addr::unchecked("larry")); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info("pumpkin", &[]), + ExecuteMsg::AcceptOwnership {}, + ) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("unauthorized: sender is not new owner")); + + let res = + execute(deps.as_mut(), mock_env(), mock_info("jake", &[]), ExecuteMsg::AcceptOwnership {}) + .unwrap(); + + assert_eq!(res.messages.len(), 0); + + let owner = state.owner.load(deps.as_ref().storage).unwrap(); + assert_eq!(owner, Addr::unchecked("jake")); +} + +#[test] +fn splitting_fees() { + let mut deps = setup_test(); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info("jake", &[]), + ExecuteMsg::TransferFeeAccount { + fee_account_type: "Wallet".to_string(), + new_fee_account: "charlie".to_string(), + }, + ) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("unauthorized: sender is not owner")); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info("larry", &[]), + ExecuteMsg::TransferFeeAccount { + fee_account_type: "xxxx".to_string(), + new_fee_account: "charlie".to_string(), + }, + ) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("Invalid Fee type: Wallet or FeeSplit only")); + + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::TransferFeeAccount { + fee_account_type: "Wallet".to_string(), + new_fee_account: "charlie".to_string(), + }) + .unwrap(); + let res: ConfigResponse = query_helper(deps.as_ref(), QueryMsg::Config {}); + assert_eq!(res, ConfigResponse { + owner: "larry".to_string(), + new_owner: None, + steak_token: "factory/cosmos2contract/boneXYZ".to_string(), + epoch_period: 259200, + unbond_period: 1814400, + denom: "uxyz".to_string(), + fee_type: "Wallet".to_string(), + fee_account: "charlie".to_string(), + fee_rate: Decimal::from_ratio(10_u128, 100_u128), + max_fee_rate: Decimal::from_ratio(20_u128, 100_u128), + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string(),], + paused_validators: vec![], + dust_collector: Some("dusty_1".to_string()), + token_factory: Some("CosmWasm".to_string()) + }); + + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::TransferFeeAccount { + fee_account_type: "FeeSplit".to_string(), + new_fee_account: "contract".to_string(), + }) + .unwrap(); + let res: ConfigResponse = query_helper(deps.as_ref(), QueryMsg::Config {}); + assert_eq!(res, ConfigResponse { + owner: "larry".to_string(), + new_owner: None, + steak_token: "factory/cosmos2contract/boneXYZ".to_string(), + epoch_period: 259200, + unbond_period: 1814400, + denom: "uxyz".to_string(), + fee_type: "FeeSplit".to_string(), + fee_account: "contract".to_string(), + fee_rate: Decimal::from_ratio(10_u128, 100_u128), + max_fee_rate: Decimal::from_ratio(20_u128, 100_u128), + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string(),], + paused_validators: vec![], + dust_collector: Some("dusty_1".to_string()), + token_factory: Some("CosmWasm".to_string()) + }); +} +//-------------------------------------------------------------------------------------------------- +// Queries +//-------------------------------------------------------------------------------------------------- + +#[test] +fn querying_previous_batches() { + let mut deps = mock_dependencies(); + + let batches = vec![ + Batch { + id: 1, + reconciled: false, + total_shares: Uint128::new(123), + amount_unclaimed: Uint128::new(678), + est_unbond_end_time: 10000, + }, + Batch { + id: 2, + reconciled: true, + total_shares: Uint128::new(234), + amount_unclaimed: Uint128::new(789), + est_unbond_end_time: 15000, + }, + Batch { + id: 3, + reconciled: false, + total_shares: Uint128::new(345), + amount_unclaimed: Uint128::new(890), + est_unbond_end_time: 20000, + }, + Batch { + id: 4, + reconciled: true, + total_shares: Uint128::new(456), + amount_unclaimed: Uint128::new(999), + est_unbond_end_time: 25000, + }, + ]; + + //let state = State::default(); + for batch in &batches { + previous_batches().save(deps.as_mut().storage, batch.id, batch).unwrap(); + } + + // Querying a single batch + let res: Batch = query_helper(deps.as_ref(), QueryMsg::PreviousBatch(1)); + assert_eq!(res, batches[0].clone()); + + let res: Batch = query_helper(deps.as_ref(), QueryMsg::PreviousBatch(2)); + assert_eq!(res, batches[1].clone()); + + // Query multiple batches + let res: Vec = query_helper(deps.as_ref(), QueryMsg::PreviousBatches { + start_after: None, + limit: None, + }); + assert_eq!(res, batches.clone()); + + let res: Vec = query_helper(deps.as_ref(), QueryMsg::PreviousBatches { + start_after: Some(1), + limit: None, + }); + assert_eq!(res, vec![batches[1].clone(), batches[2].clone(), batches[3].clone()]); + + let res: Vec = query_helper(deps.as_ref(), QueryMsg::PreviousBatches { + start_after: Some(4), + limit: None, + }); + assert_eq!(res, vec![]); + + // Query multiple batches, indexed by whether it has been reconciled + let res = previous_batches() + .idx + .reconciled + .prefix("true".into()) + .range(deps.as_ref().storage, None, None, Order::Ascending) + .map(|item| { + let (_, v) = item.unwrap(); + v + }) + .collect::>(); + + assert_eq!(res, vec![batches[1].clone(), batches[3].clone()]); + + let res = previous_batches() + .idx + .reconciled + .prefix("false".into()) + .range(deps.as_ref().storage, None, None, Order::Ascending) + .map(|item| { + let (_, v) = item.unwrap(); + v + }) + .collect::>(); + + assert_eq!(res, vec![batches[0].clone(), batches[2].clone()]); +} + +#[test] +fn querying_unbond_requests() { + let mut deps = mock_dependencies(); + //let state = State::default(); + + let unbond_reqs = vec![ + UnbondRequest { + id: 1, + user: Addr::unchecked("alice"), + shares: Uint128::new(123), + }, + UnbondRequest { + id: 1, + user: Addr::unchecked("bob"), + shares: Uint128::new(234), + }, + UnbondRequest { + id: 1, + user: Addr::unchecked("charlie"), + shares: Uint128::new(345), + }, + UnbondRequest { + id: 2, + user: Addr::unchecked("alice"), + shares: Uint128::new(456), + }, + ]; + + for unbond_request in &unbond_reqs { + unbond_requests() + .save( + deps.as_mut().storage, + (unbond_request.id, Addr::unchecked(unbond_request.user.clone()).as_str()), + unbond_request, + ) + .unwrap(); + } + + let res: Vec = + query_helper(deps.as_ref(), QueryMsg::UnbondRequestsByBatch { + id: 1, + start_after: None, + limit: None, + }); + assert_eq!(res, vec![ + unbond_reqs[0].clone().into(), + unbond_reqs[1].clone().into(), + unbond_reqs[2].clone().into(), + ]); + + let res: Vec = + query_helper(deps.as_ref(), QueryMsg::UnbondRequestsByBatch { + id: 2, + start_after: None, + limit: None, + }); + assert_eq!(res, vec![unbond_reqs[3].clone().into()]); + + let res: Vec = + query_helper(deps.as_ref(), QueryMsg::UnbondRequestsByUser { + user: "alice".to_string(), + start_after: None, + limit: None, + }); + assert_eq!(res, vec![unbond_reqs[0].clone().into(), unbond_reqs[3].clone().into(),]); + /* + for x in unbond_requests().range(deps.as_ref().storage, None, None, Order::Ascending) { + let rec = x.unwrap(); + eprintln!("Key {}/{} = {:?}", rec.0.0, rec.0.1, rec.1) + } + */ + let res: Vec = + query_helper(deps.as_ref(), QueryMsg::UnbondRequestsByUser { + user: "alice".to_string(), + start_after: Some(1), + limit: None, + }); + assert_eq!(res, vec![unbond_reqs[3].clone().into()]); +} + +//-------------------------------------------------------------------------------------------------- +// Delegations +//-------------------------------------------------------------------------------------------------- + +#[test] +fn computing_undelegations() { + let current_delegations = vec![ + Delegation::new("alice", 400, "uxyz"), + Delegation::new("bob", 300, "uxyz"), + Delegation::new("charlie", 200, "uxyz"), + ]; + + // Target: (400 + 300 + 200 - 451) / 3 = 149 + // Remainder: 2 + // Alice: 400 - (149 + 1) = 250 + // Bob: 300 - (149 + 1) = 150 + // Charlie: 200 - (149 + 0) = 51 + let new_undelegations = compute_undelegations(Uint128::new(451), ¤t_delegations, "uxyz"); + let expected = vec![ + Undelegation::new("alice", 250, "uxyz"), + Undelegation::new("bob", 150, "uxyz"), + Undelegation::new("charlie", 51, "uxyz"), + ]; + assert_eq!(new_undelegations, expected); +} + +#[test] +fn computing_redelegations_for_removal() { + let current_delegations = vec![ + Delegation::new("alice", 13000, "uxyz"), + Delegation::new("bob", 12000, "uxyz"), + Delegation::new("charlie", 11000, "uxyz"), + Delegation::new("dave", 10000, "uxyz"), + ]; + + // Suppose Dave will be removed + // uluna_per_validator = (13000 + 12000 + 11000 + 10000) / 3 = 15333 + // remainder = 1 + // to Alice: 15333 + 1 - 13000 = 2334 + // to Bob: 15333 + 0 - 12000 = 3333 + // to Charlie: 15333 + 0 - 11000 = 4333 + let expected = vec![ + Redelegation::new("dave", "alice", 2334, "uxyz"), + Redelegation::new("dave", "bob", 3333, "uxyz"), + Redelegation::new("dave", "charlie", 4333, "uxyz"), + ]; + + assert_eq!( + compute_redelegations_for_removal( + ¤t_delegations[3], + ¤t_delegations[..3], + "uxyz", + ), + expected, + ); +} + +#[test] +fn computing_redelegations_for_rebalancing() { + let current_delegations = vec![ + Delegation::new("alice", 69420, "uxyz"), + Delegation::new("bob", 1234, "uxyz"), + Delegation::new("charlie", 88888, "uxyz"), + Delegation::new("dave", 40471, "uxyz"), + Delegation::new("evan", 2345, "uxyz"), + ]; + let active_validators: Vec = vec![ + "alice".to_string(), + "bob".to_string(), + "charlie".to_string(), + "dave".to_string(), + "evan".to_string(), + ]; + // uluna_per_validator = (69420 + 88888 + 1234 + 40471 + 2345) / 4 = 40471 + // remainer = 3 + // src_delegations: + // - alice: 69420 - (40471 + 1) = 28948 + // - charlie: 88888 - (40471 + 1) = 48416 + // dst_delegations: + // - bob: (40471 + 1) - 1234 = 39238 + // - evan: (40471 + 0) - 2345 = 38126 + // + // Round 1: alice --(28948)--> bob + // src_delegations: + // - charlie: 48416 + // dst_delegations: + // - bob: 39238 - 28948 = 10290 + // - evan: 38126 + // + // Round 2: charlie --(10290)--> bob + // src_delegations: + // - charlie: 48416 - 10290 = 38126 + // dst_delegations: + // - evan: 38126 + // + // Round 3: charlie --(38126)--> evan + // Queues are emptied + let expected = vec![ + Redelegation::new("alice", "bob", 28948, "uxyz"), + Redelegation::new("charlie", "bob", 10290, "uxyz"), + Redelegation::new("charlie", "evan", 38126, "uxyz"), + ]; + + assert_eq!( + compute_redelegations_for_rebalancing( + active_validators, + ¤t_delegations, + Uint128::from(10u64) + ), + expected, + ); + + let partially_active = + vec!["alice".to_string(), "charlie".to_string(), "dave".to_string(), "evan".to_string()]; + + let partially_expected = vec![ + Redelegation::new("alice", "dave", 10118, "uxyz"), + Redelegation::new("alice", "evan", 8712, "uxyz"), + Redelegation::new("charlie", "evan", 38299, "uxyz"), + ]; + assert_eq!( + compute_redelegations_for_rebalancing( + partially_active.clone(), + ¤t_delegations, + Uint128::from(10u64) + ), + partially_expected, + ); + + let partially_expected_minimums = vec![ + Redelegation::new("alice", "evan", 18830, "uxyz"), + Redelegation::new("charlie", "evan", 29414, "uxyz"), + ]; + assert_eq!( + compute_redelegations_for_rebalancing( + partially_active, + ¤t_delegations, + Uint128::from(15_000u64) + ), + partially_expected_minimums, + ); +} + +//-------------------------------------------------------------------------------------------------- +// Coins +//-------------------------------------------------------------------------------------------------- + +#[test] +fn parsing_coin() { + let coin = parse_coin("12345uatom").unwrap(); + assert_eq!(coin, Coin::new(12345, "uatom")); + + let coin = + parse_coin("23456ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B") + .unwrap(); + assert_eq!( + coin, + Coin::new(23456, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B",) + ); + + let err = parse_coin("69420").unwrap_err(); + assert_eq!(err, StdError::generic_err("failed to parse coin: 69420")); + + let err = parse_coin("ngmi").unwrap_err(); + assert_eq!(err, StdError::generic_err("Parsing u128: cannot parse integer from empty string")); +} + +#[test] +fn parsing_coins() { + let coins = Coins::from_str("").unwrap(); + assert_eq!(coins.0, vec![]); + + let coins = Coins::from_str("12345uatom").unwrap(); + assert_eq!(coins.0, vec![Coin::new(12345, "uatom")]); + + let coins = Coins::from_str("12345uatom,23456uxyz").unwrap(); + assert_eq!(coins.0, vec![Coin::new(12345, "uatom"), Coin::new(23456, "uxyz")]); +} + +#[test] +fn adding_coins() { + let mut coins = Coins(vec![]); + + coins.add(&Coin::new(12345, "uatom")).unwrap(); + assert_eq!(coins.0, vec![Coin::new(12345, "uatom")]); + + coins.add(&Coin::new(23456, "uxyz")).unwrap(); + assert_eq!(coins.0, vec![Coin::new(12345, "uatom"), Coin::new(23456, "uxyz")]); + + coins.add_many(&Coins::from_str("76543uatom,69420uusd").unwrap()).unwrap(); + assert_eq!(coins.0, vec![ + Coin::new(88888, "uatom"), + Coin::new(23456, "uxyz"), + Coin::new(69420, "uusd"), + ]); +} + +#[test] +fn receiving_funds() { + let err = parse_received_fund(&[], "uxyz").unwrap_err(); + assert_eq!(err, StdError::generic_err("must deposit exactly one coin; received 0")); + + let err = parse_received_fund(&[Coin::new(12345, "uatom"), Coin::new(23456, "uxyz")], "uxyz") + .unwrap_err(); + assert_eq!(err, StdError::generic_err("must deposit exactly one coin; received 2")); + + let err = parse_received_fund(&[Coin::new(12345, "uatom")], "uxyz").unwrap_err(); + assert_eq!(err, StdError::generic_err("expected uxyz deposit, received uatom")); + + let err = parse_received_fund(&[Coin::new(0, "uxyz")], "uxyz").unwrap_err(); + assert_eq!(err, StdError::generic_err("deposit amount must be non-zero")); + + let amount = parse_received_fund(&[Coin::new(69420, "uxyz")], "uxyz").unwrap(); + assert_eq!(amount, Uint128::new(69420)); +} + +#[test] +fn reconciling_underflow() { + let mut deps = setup_test(); + let state = State::default(); + let previous_batch_list = vec![ + Batch { + id: 1, + reconciled: true, + total_shares: Uint128::new(92876), + amount_unclaimed: Uint128::new(95197), // 1.025 Token per Stake + est_unbond_end_time: 10000, + }, + Batch { + id: 2, + reconciled: false, + total_shares: Uint128::new(1345), + amount_unclaimed: Uint128::new(1385), // 1.030 Token per Stake + est_unbond_end_time: 20000, + }, + Batch { + id: 3, + reconciled: false, + total_shares: Uint128::new(1456), + amount_unclaimed: Uint128::new(1506), // 1.035 Token per Stake + est_unbond_end_time: 30000, + }, + Batch { + id: 4, + reconciled: false, + total_shares: Uint128::new(1), + amount_unclaimed: Uint128::new(1), + est_unbond_end_time: 30001, + }, + ]; + for previous_batch in &previous_batch_list { + previous_batches().save(deps.as_mut().storage, previous_batch.id, previous_batch).unwrap(); + } + state + .unlocked_coins + .save(deps.as_mut().storage, &vec![ + Coin::new(10000, "uatom"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ), + ]) + .unwrap(); + deps.querier.set_bank_balances(&[ + Coin::new(12345, "uatom"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B"), + ]); + execute( + deps.as_mut(), + mock_env_at_timestamp(35000), + mock_info("worker", &[]), + ExecuteMsg::Reconcile {}, + ) + .unwrap(); +} + +#[test] +fn reconciling_underflow_second() { + let mut deps = setup_test(); + let state = State::default(); + let previous_batch_list = vec![ + Batch { + id: 1, + reconciled: true, + total_shares: Uint128::new(92876), + amount_unclaimed: Uint128::new(95197), // 1.025 Token per Stake + est_unbond_end_time: 10000, + }, + Batch { + id: 2, + reconciled: false, + total_shares: Uint128::new(1345), + amount_unclaimed: Uint128::new(1385), // 1.030 Token per Stake + est_unbond_end_time: 20000, + }, + Batch { + id: 3, + reconciled: false, + total_shares: Uint128::new(176), + amount_unclaimed: Uint128::new(183), // 1.035 Token per Stake + est_unbond_end_time: 30000, + }, + Batch { + id: 4, + reconciled: false, + total_shares: Uint128::new(1), + amount_unclaimed: Uint128::new(1), + est_unbond_end_time: 30001, + }, + ]; + for previous_batch in &previous_batch_list { + previous_batches().save(deps.as_mut().storage, previous_batch.id, previous_batch).unwrap(); + } + state + .unlocked_coins + .save(deps.as_mut().storage, &vec![ + Coin::new(10000, "uatom"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ), + ]) + .unwrap(); + deps.querier.set_bank_balances(&[ + Coin::new(12345 - 1323, "uatom"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B"), + ]); + execute( + deps.as_mut(), + mock_env_at_timestamp(35000), + mock_info("worker", &[]), + ExecuteMsg::Reconcile {}, + ) + .unwrap(); +} +#[test] +fn dust_return_denom() { + let mut deps = setup_test(); + + // Bond when no delegation has been made + // In this case, the full deposit simply goes to the first validator + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("user_1", &[Coin::new(1_000_000, "uxyz")]), + ExecuteMsg::Bond { + receiver: None, + exec_msg: None, + }, + ) + .unwrap(); + + // 3 messages. (switched to 3 so we can 'send' instead of 'transfer' minted tokens, so contract + // will know about it 1 - delegate + // 2 - mint token (to ourselves) + // 3 - send/transfer it + assert_eq!(res.messages.len(), 3); + assert_eq!(res.messages[0], SubMsg { + msg: Delegation::new("alice", 1_000_000, "uxyz").to_cosmos_msg(), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never + }); + let mint_msg = >::into(MsgMint { + sender: "cosmos2contract".to_string(), + amount: Some(denom::Coin { + denom: "factory/cosmos2contract/boneXYZ".to_string(), + amount: Uint128::new(1_000_000).to_string(), + }), + }); + assert_eq!(res.messages[1], SubMsg { + id: 0, + msg: mint_msg, + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + assert_eq!(res.messages[2], SubMsg { + id: 0, + msg: CosmosMsg::Bank(BankMsg::Send { + to_address: "user_1".to_string(), + amount: vec![Coin { + denom: "factory/cosmos2contract/boneXYZ".to_string(), + amount: Uint128::new(1_000_000) + }], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + // Bond when there are existing delegations, and Luna:Steak exchange rate is >1 + // Previously user 1 delegated 1,000,000 uluna. We assume we have accumulated 2.5% yield at + // 1025000 staked + deps.querier.set_staking_delegations(&[ + Delegation::new("alice", 341667, "uxyz"), + Delegation::new("bob", 341667, "uxyz"), + Delegation::new("charlie", 341666, "uxyz"), + ]); + + // Charlie has the smallest amount of delegation, so the full deposit goes to him + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("user_2", &[Coin::new(12345, "uxyz")]), + ExecuteMsg::ReturnDenom {}, + ) + .unwrap(); + //eprintln!("{:?}", res.messages); + assert_eq!(res.messages.len(), 1); + assert_eq!(res.messages[0], SubMsg { + msg: Delegation::new("charlie", 12345, "uxyz").to_cosmos_msg(), + id: REPLY_REGISTER_RECEIVED_COINS, + gas_limit: None, + reply_on: ReplyOn::Never + }); +} +#[test] +fn test_128() { + let mut deps = mock_dependencies(); + + let user_addr = deps.api.addr_make("testing"); + + crate::state::unbond_requests() + .save(deps.as_mut().storage, (128u64, user_addr.as_str()), &UnbondRequest { + id: 128u64, + user: user_addr.clone(), + shares: Uint128::new(18084001808), + }) + .unwrap(); + + let mut_deps = deps.as_mut(); + + let elements = unbond_requests() + .range(mut_deps.storage, None, None, Order::Ascending) + .take(10) + .map(|item| { + let (_, v) = item?; + Ok(v) + }) + .collect::>>() + .unwrap(); + assert_eq!(elements.len(), 1); + let elements = unbond_requests() + .idx + .user + .prefix(user_addr.to_string()) + // .prefix(contract.to_string()) + .range(mut_deps.storage, None, None, Order::Ascending) + .take(100) + .map(|item| { + let (_, v) = item?; + Ok(v) + }) + .collect::>>() + .unwrap(); + + assert_eq!(elements.len(), 1); +} diff --git a/contracts/hub-tf/src/token_factory/denom.rs b/contracts/hub-tf/src/token_factory/denom.rs new file mode 100644 index 00000000..bcb58c46 --- /dev/null +++ b/contracts/hub-tf/src/token_factory/denom.rs @@ -0,0 +1,143 @@ +// source: https://github.com/White-Whale-Defi-Platform/white-whale-core/blob/feat/tokenfactory-lp/packages/pool-network/src/denom.rs + +use std::convert::{TryFrom, TryInto}; + +use osmosis_std_derive::CosmwasmExt; + +// see https://github.com/notional-labs/wasmd/blob/v0.30.0-sdk469.4/proto/cosmwasm/tokenfactory/v1beta1/tx.proto + +/// Coin defines a token with a denomination and an amount. +/// +/// NOTE: The amount field is an Int which implements the custom method +/// signatures required by gogoproto. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmos.base.v1beta1.Coin")] +pub struct Coin { + #[prost(string, tag = "1")] + pub denom: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub amount: ::prost::alloc::string::String, +} + +/// Create a Denom +/// +/// MsgCreateDenom defines the message structure for the CreateDenom gRPC service +/// method. It allows an account to create a new denom. It requires a sender +/// address and a sub denomination. The (sender_address, sub_denomination) tuple +/// must be unique and cannot be re-used. +/// +/// The resulting denom created is defined as +/// . The resulting denom's admin is +/// originally set to be the creator, but this can be changed later. The token +/// denom does not indicate the current admin. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmwasm.tokenfactory.v1beta1.MsgCreateDenom")] +pub struct MsgCreateDenom { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + /// subdenom can be up to 44 "alphanumeric" characters long. + #[prost(string, tag = "2")] + pub subdenom: ::prost::alloc::string::String, +} + +/// MsgCreateDenomResponse is the return value of MsgCreateDenom +/// It returns the full string of the newly created denom +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmwasm.tokenfactory.v1beta1.MsgCreateDenomResponse")] +pub struct MsgCreateDenomResponse { + #[prost(string, tag = "1")] + pub new_token_denom: ::prost::alloc::string::String, +} + +/// MsgMint is the sdk.Msg type for allowing an admin account to mint +/// more of a token. For now, we only support minting to the sender account +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmwasm.tokenfactory.v1beta1.MsgMint")] +pub struct MsgMint { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmwasm.tokenfactory.v1beta1.MsgMintResponse")] +pub struct MsgMintResponse {} + +/// MsgBurn is the sdk.Msg type for allowing an admin account to burn +/// a token. For now, we only support burning from the sender account. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmwasm.tokenfactory.v1beta1.MsgBurn")] +pub struct MsgBurn { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/cosmwasm.tokenfactory.v1beta1.MsgBurnResponse")] +pub struct MsgBurnResponse {} diff --git a/contracts/hub-tf/src/token_factory/mod.rs b/contracts/hub-tf/src/token_factory/mod.rs new file mode 100644 index 00000000..80617f64 --- /dev/null +++ b/contracts/hub-tf/src/token_factory/mod.rs @@ -0,0 +1,2 @@ +// +pub mod denom; diff --git a/contracts/hub-tf/src/types/coins.rs b/contracts/hub-tf/src/types/coins.rs new file mode 100644 index 00000000..3cd73ef7 --- /dev/null +++ b/contracts/hub-tf/src/types/coins.rs @@ -0,0 +1,55 @@ +use std::str::FromStr; + +use cosmwasm_std::{Coin, StdError, StdResult}; + +use crate::helpers::parse_coin; + +pub struct Coins(pub Vec); + +impl FromStr for Coins { + type Err = StdError; + + fn from_str(s: &str) -> Result { + if s.is_empty() { + return Ok(Self(vec![])); + } + + Ok(Self( + s.split(',') + .filter(|coin_str| !coin_str.is_empty()) // coin with zero amount may appeat as an empty string in the event log + .collect::>() + .iter() + .map(|s| parse_coin(s)) + .collect::>>()?, + )) + } +} + +impl Coins { + pub fn add(&mut self, coin_to_add: &Coin) -> StdResult<()> { + match self.0.iter_mut().find(|coin| coin.denom == coin_to_add.denom) { + Some(coin) => { + coin.amount = coin.amount.checked_add(coin_to_add.amount)?; + }, + None => { + self.0.push(coin_to_add.clone()); + }, + } + Ok(()) + } + + pub fn add_many(&mut self, coins_to_add: &Coins) -> StdResult<()> { + for coin_to_add in &coins_to_add.0 { + self.add(coin_to_add)?; + } + Ok(()) + } + + pub fn find(&self, denom: &str) -> Coin { + self.0 + .iter() + .find(|coin| coin.denom == denom) + .cloned() + .unwrap_or_else(|| Coin::new(0, denom)) + } +} diff --git a/contracts/hub-tf/src/types/keys.rs b/contracts/hub-tf/src/types/keys.rs new file mode 100644 index 00000000..25dab9a2 --- /dev/null +++ b/contracts/hub-tf/src/types/keys.rs @@ -0,0 +1,45 @@ +use std::marker::PhantomData; + +use cw_storage_plus::{Key, Prefixer, PrimaryKey}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BooleanKey { + pub wrapped: Vec, + pub data: PhantomData, +} + +impl BooleanKey { + pub fn new(val: bool) -> Self { + BooleanKey { + wrapped: if val { + vec![1] + } else { + vec![0] + }, + data: PhantomData, + } + } +} + +impl From for BooleanKey { + fn from(val: bool) -> Self { + Self::new(val) + } +} + +impl PrimaryKey<'_> for BooleanKey { + type Prefix = (); + type SubPrefix = (); + type Suffix = (); + type SuperSuffix = (); + + fn key(&self) -> Vec { + self.wrapped.key() + } +} + +impl Prefixer<'_> for BooleanKey { + fn prefix(&self) -> Vec { + self.wrapped.prefix() + } +} diff --git a/contracts/hub-tf/src/types/mod.rs b/contracts/hub-tf/src/types/mod.rs new file mode 100644 index 00000000..238b36a5 --- /dev/null +++ b/contracts/hub-tf/src/types/mod.rs @@ -0,0 +1,7 @@ +mod coins; +mod keys; +mod staking; + +pub use coins::Coins; +pub use keys::BooleanKey; +pub use staking::{Delegation, Redelegation, Undelegation}; diff --git a/contracts/hub-tf/src/types/staking.rs b/contracts/hub-tf/src/types/staking.rs new file mode 100644 index 00000000..39cb5997 --- /dev/null +++ b/contracts/hub-tf/src/types/staking.rs @@ -0,0 +1,77 @@ +use cosmwasm_std::{Coin, CosmosMsg, StakingMsg}; + +#[derive(Clone)] +#[cfg_attr(test, derive(Debug, PartialEq))] +pub struct Delegation { + pub validator: String, + pub amount: u128, + pub denom: String, +} + +impl Delegation { + pub fn new(validator: &str, amount: u128, denom: &str) -> Self { + Self { + validator: validator.to_string(), + amount, + denom: denom.to_string(), + } + } + + pub fn to_cosmos_msg(&self) -> CosmosMsg { + CosmosMsg::Staking(StakingMsg::Delegate { + validator: self.validator.clone(), + amount: Coin::new(self.amount, self.denom.to_string()), + }) + } +} + +#[cfg_attr(test, derive(Debug, PartialEq))] +pub struct Undelegation { + pub validator: String, + pub amount: u128, + pub denom: String, +} + +impl Undelegation { + pub fn new(validator: &str, amount: u128, denom: &str) -> Self { + Self { + validator: validator.to_string(), + amount, + denom: denom.to_string(), + } + } + + pub fn to_cosmos_msg(&self) -> CosmosMsg { + CosmosMsg::Staking(StakingMsg::Undelegate { + validator: self.validator.clone(), + amount: Coin::new(self.amount, self.denom.to_string()), + }) + } +} + +#[cfg_attr(test, derive(Debug, PartialEq))] +pub struct Redelegation { + pub src: String, + pub dst: String, + pub amount: u128, + pub denom: String, +} + +impl Redelegation { + pub fn new(src: &str, dst: &str, amount: u128, denom: &str) -> Self { + Self { + src: src.to_string(), + dst: dst.to_string(), + amount, + denom: denom.into(), + } + } + + pub fn to_cosmos_msg(&self) -> CosmosMsg { + CosmosMsg::Staking(StakingMsg::Redelegate { + src_validator: self.src.clone(), + dst_validator: self.dst.clone(), + amount: Coin::new(self.amount, &self.denom), + }) + } +} diff --git a/contracts/hub-tf/unbonding-queue.png b/contracts/hub-tf/unbonding-queue.png new file mode 100644 index 00000000..6f39500a Binary files /dev/null and b/contracts/hub-tf/unbonding-queue.png differ diff --git a/contracts/hub/Cargo.toml b/contracts/hub/Cargo.toml index 84415c02..58ee3eb3 100644 --- a/contracts/hub/Cargo.toml +++ b/contracts/hub/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "steak-hub" -version = "2.0.0" +name = "pfc-steak-hub" +version = "3.0.20" authors = ["larry ", "PFC "] edition = "2018" license = "GPL-3.0-or-later" @@ -10,14 +10,18 @@ repository = "https://github.com/st4k3h0us3/steak-contracts" crate-type = ["cdylib", "rlib"] [features] -backtraces = ["cosmwasm-std/backtraces"] +# backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-std = { version = "1.0", features = ["staking"] } -cw20 = "0.13" -cw20-base = { version = "0.13", features = ["library"] } +cosmwasm-std = { version = "1.5.8", features = ["staking"] } +cw2 = "1.1.2" +cw20 = "1.1.2" +cw20-base = { version = "1.1.2", features = ["library"] } cw-storage-plus = "0.13" -steak = { path = "../../packages/steak" } - -[dev-dependencies] -serde = { version = "1.0.103", default-features = false, features = ["derive"] } +pfc-steak = { path = "../../packages/steak" } +serde = { version = "1.0.210", default-features = false, features = ["derive"] } +pfc-fee-split = "0.2.5" +#pfc-dust-collector = {path = "../../packages/pfc-dust-collector"} +funds-distributor-api = { git = "https://github.com/terra-money/enterprise-contracts.git", branch = "main" } +#[dev-dependencies] +#serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/contracts/hub/src/contract.rs b/contracts/hub/src/contract.rs index 99c09430..bbf0597b 100644 --- a/contracts/hub/src/contract.rs +++ b/contracts/hub/src/contract.rs @@ -1,14 +1,30 @@ +use std::convert::TryInto; + use cosmwasm_std::{ - entry_point, from_binary, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, - StdError, StdResult, + Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, StdResult, + entry_point, from_json, to_json_binary, }; +use cw2::{ContractVersion, get_contract_version, set_contract_version}; use cw20::Cw20ReceiveMsg; +use pfc_steak::hub::{ + CallbackMsg, ExecuteMsg, FeeType, InstantiateMsg, MigrateMsg, QueryMsg, ReceiveMsg, +}; -use steak::hub::{CallbackMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, ReceiveMsg}; +use crate::{ + execute, + helpers::{get_denom_balance, unwrap_reply}, + migrations::ConfigV100, + queries, + state::State, +}; -use crate::helpers::{parse_received_fund, unwrap_reply}; -use crate::state::State; -use crate::{execute, queries}; +/// Contract name that is used for migration. +pub const CONTRACT_NAME: &str = "steak-hub"; +/// Contract version that is used for migration. +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const REPLY_INSTANTIATE_TOKEN: u64 = 1; +pub const REPLY_REGISTER_RECEIVED_COINS: u64 = 2; +pub const SPECIAL_SEND_MESSAGE_TO_TRANSFER: &str = "PFC_TRANSFER_NOT_SEND"; #[entry_point] pub fn instantiate( @@ -17,6 +33,7 @@ pub fn instantiate( _info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; execute::instantiate(deps, env, msg) } @@ -27,11 +44,24 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S ExecuteMsg::Receive(cw20_msg) => receive(deps, env, info, cw20_msg), ExecuteMsg::Bond { receiver, + exec_msg, + } => execute::bond( + deps, + env, + receiver.map(|s| api.addr_validate(&s)).transpose()?.unwrap_or(info.sender), + info.funds, + exec_msg, + false, + ), + ExecuteMsg::BondEx { + receiver, } => execute::bond( deps, env, receiver.map(|s| api.addr_validate(&s)).transpose()?.unwrap_or(info.sender), - parse_received_fund(&info.funds, "uluna")?, + info.funds, + None, + true, ), ExecuteMsg::WithdrawUnbonded { receiver, @@ -41,21 +71,66 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S info.sender.clone(), receiver.map(|s| api.addr_validate(&s)).transpose()?.unwrap_or(info.sender), ), + ExecuteMsg::WithdrawUnbondedAdmin { + address, + } => execute::withdraw_unbonded_admin(deps, env, info.sender, api.addr_validate(&address)?), ExecuteMsg::AddValidator { validator, } => execute::add_validator(deps, info.sender, validator), ExecuteMsg::RemoveValidator { validator, } => execute::remove_validator(deps, env, info.sender, validator), + ExecuteMsg::RemoveValidatorEx { + validator, + } => execute::remove_validator_ex(deps, env, info.sender, validator), + ExecuteMsg::Redelegate { + validator_from, + validator_to, + } => execute::redelegate(deps, env, info.sender, validator_from, validator_to), ExecuteMsg::TransferOwnership { new_owner, } => execute::transfer_ownership(deps, info.sender, new_owner), ExecuteMsg::AcceptOwnership {} => execute::accept_ownership(deps, info.sender), ExecuteMsg::Harvest {} => execute::harvest(deps, env), - ExecuteMsg::Rebalance {} => execute::rebalance(deps, env), + ExecuteMsg::Rebalance { + minimum, + } => execute::rebalance(deps, env, minimum), ExecuteMsg::Reconcile {} => execute::reconcile(deps, env), ExecuteMsg::SubmitBatch {} => execute::submit_batch(deps, env), + ExecuteMsg::TransferFeeAccount { + fee_account_type, + new_fee_account, + } => execute::transfer_fee_account(deps, info.sender, fee_account_type, new_fee_account), + ExecuteMsg::UpdateFee { + new_fee, + } => execute::update_fee(deps, info.sender, new_fee), ExecuteMsg::Callback(callback_msg) => callback(deps, env, info, callback_msg), + ExecuteMsg::PauseValidator { + validator, + } => execute::pause_validator(deps, env, info.sender, validator), + ExecuteMsg::UnPauseValidator { + validator, + } => execute::unpause_validator(deps, env, info.sender, validator), + ExecuteMsg::SetUnbondPeriod { + unbond_period, + } => execute::set_unbond_period(deps, env, info.sender, unbond_period), + ExecuteMsg::SetDustCollector { + dust_collector, + } => execute::set_dust_collector(deps, env, info.sender, dust_collector), + ExecuteMsg::CollectDust { + max_tokens, + } => { + let max_tokens_usize_r = max_tokens.try_into(); + if let Ok(max_tokens_usize) = max_tokens_usize_r { + execute::collect_dust(deps, env, max_tokens_usize) + } else { + Err(StdError::generic_err("max_tokens too large")) + } + }, + ExecuteMsg::ReturnDenom {} => execute::return_denom(deps, env, info.funds), + ExecuteMsg::SetBaseDenom { + new_denom, + } => execute::set_base_denom(deps, info.sender, new_denom), } } @@ -66,7 +141,7 @@ fn receive( cw20_msg: Cw20ReceiveMsg, ) -> StdResult { let api = deps.api; - match from_binary(&cw20_msg.msg)? { + match from_json(&cw20_msg.msg)? { ReceiveMsg::QueueUnbond { receiver, } => { @@ -74,9 +149,10 @@ fn receive( let steak_token = state.steak_token.load(deps.storage)?; if info.sender != steak_token { - return Err(StdError::generic_err( - format!("expecting Steak token, received {}", info.sender), - )); + return Err(StdError::generic_err(format!( + "expecting Steak token, received {}", + info.sender + ))); } execute::queue_unbond( @@ -108,7 +184,9 @@ fn callback( pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> StdResult { match reply.id { 1 => execute::register_steak_token(deps, unwrap_reply(reply)?), - 2 => execute::register_received_coins(deps, env, unwrap_reply(reply)?.events), + REPLY_REGISTER_RECEIVED_COINS => { + execute::register_received_coins(deps, env, unwrap_reply(reply)?.events) + }, id => Err(StdError::generic_err(format!("invalid reply id: {}; must be 1-2", id))), } } @@ -116,28 +194,103 @@ pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> StdResult { #[entry_point] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Config {} => to_binary(&queries::config(deps)?), - QueryMsg::State {} => to_binary(&queries::state(deps, env)?), - QueryMsg::PendingBatch {} => to_binary(&queries::pending_batch(deps)?), - QueryMsg::PreviousBatch(id) => to_binary(&queries::previous_batch(deps, id)?), + QueryMsg::Config {} => to_json_binary(&queries::config(deps)?), + QueryMsg::State {} => to_json_binary(&queries::state(deps, env)?), + QueryMsg::PendingBatch {} => to_json_binary(&queries::pending_batch(deps)?), + QueryMsg::PreviousBatch(id) => to_json_binary(&queries::previous_batch(deps, id)?), QueryMsg::PreviousBatches { start_after, limit, - } => to_binary(&queries::previous_batches(deps, start_after, limit)?), + } => to_json_binary(&queries::previous_batches(deps, start_after, limit)?), QueryMsg::UnbondRequestsByBatch { id, start_after, limit, - } => to_binary(&queries::unbond_requests_by_batch(deps, id, start_after, limit)?), + } => to_json_binary(&queries::unbond_requests_by_batch(deps, id, start_after, limit)?), QueryMsg::UnbondRequestsByUser { user, start_after, limit, - } => to_binary(&queries::unbond_requests_by_user(deps, user, start_after, limit)?), + } => to_json_binary(&queries::unbond_requests_by_user(deps, user, start_after, limit)?), + QueryMsg::Unreconciled {} => to_json_binary(&queries::previous_batches_unreconciled(deps)?), } } #[entry_point] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { - Ok(Response::new()) +pub fn migrate(deps: DepsMut, env: Env, _msg: MigrateMsg) -> StdResult { + let contract_version = match get_contract_version(deps.storage) { + Ok(version) => version, + Err(_) => ContractVersion { + contract: "steak-hub".to_string(), + version: "0".to_string(), + }, + }; + match contract_version.contract.as_ref() { + #[allow(clippy::single_match)] + "steak-hub" => match contract_version.version.as_ref() { + #[allow(clippy::single_match)] + "0" => { + let state = State::default(); + let owner = state.owner.load(deps.storage)?; + state.denom.save(deps.storage, &"uluna".to_string())?; + state.fee_account.save(deps.storage, &owner)?; + state.max_fee_rate.save(deps.storage, &Decimal::zero())?; + state.fee_rate.save(deps.storage, &Decimal::zero())?; + state.fee_account_type.save(deps.storage, &FeeType::Wallet)?; + ConfigV100::upgrade_stores(deps.storage, &deps.querier, env.contract.address)?; + state.dust_collector.save(deps.storage, &None)?; + }, + "2.1.4" => { + let state = State::default(); + ConfigV100::upgrade_stores(deps.storage, &deps.querier, env.contract.address)?; + state.fee_account_type.save(deps.storage, &FeeType::Wallet)?; + state.dust_collector.save(deps.storage, &None)?; + }, + "2.1.5" => { + ConfigV100::upgrade_stores(deps.storage, &deps.querier, env.contract.address)?; + let state = State::default(); + state.fee_account_type.save(deps.storage, &FeeType::Wallet)?; + state.dust_collector.save(deps.storage, &None)?; + }, + "2.1.6" | "2.1.7" => { + let state = State::default(); + // note: this is also done in ConfigV100::upgrade + let denom = state.denom.load(deps.storage)?; + state.prev_denom.save( + deps.storage, + &get_denom_balance(&deps.querier, env.contract.address, denom)?, + )?; + + state.fee_account_type.save(deps.storage, &FeeType::Wallet)?; + state.dust_collector.save(deps.storage, &None)?; + }, + "2.1.8" | "2.1.16" => { + let state = State::default(); + state.fee_account_type.save(deps.storage, &FeeType::Wallet)?; + state.dust_collector.save(deps.storage, &None)?; + }, + "3.0.1" => { + let state = State::default(); + state.dust_collector.save(deps.storage, &None)?; + }, + _ => {}, + }, + _ => { + return Err(StdError::generic_err("contract name is not the same. aborting {}")); + }, + } + /* + let state = State::default(); + + state.max_fee_rate.save(deps.storage,&Decimal::from_ratio(10u32,100u32))?; + state.fee_rate.save(deps.storage,&Decimal::from_ratio(10u32,100u32))?; + + */ + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(Response::new() + .add_attribute("previous_contract_name", &contract_version.contract) + .add_attribute("previous_contract_version", &contract_version.version) + .add_attribute("new_contract_name", CONTRACT_NAME) + .add_attribute("new_contract_version", CONTRACT_VERSION)) } diff --git a/contracts/hub/src/execute.rs b/contracts/hub/src/execute.rs index 482e7ac6..4b181a5c 100644 --- a/contracts/hub/src/execute.rs +++ b/contracts/hub/src/execute.rs @@ -1,22 +1,34 @@ -use std::str::FromStr; +use std::{collections::HashSet, iter::FromIterator, str::FromStr}; use cosmwasm_std::{ - to_binary, Addr, BankMsg, Coin, CosmosMsg, DepsMut, DistributionMsg, Env, Event, Order, - Response, StdError, StdResult, SubMsg, SubMsgResponse, Uint128, WasmMsg, + Addr, BankMsg, Binary, Coin, CosmosMsg, Decimal, DepsMut, DistributionMsg, Env, Event, Order, + Response, StdError, StdResult, SubMsg, SubMsgResponse, Uint128, WasmMsg, from_json, + to_json_binary, }; use cw20::{Cw20ExecuteMsg, MinterResponse}; use cw20_base::msg::InstantiateMsg as Cw20InstantiateMsg; - -use steak::hub::{Batch, CallbackMsg, ExecuteMsg, InstantiateMsg, PendingBatch, UnbondRequest}; - -use crate::helpers::{query_cw20_total_supply, query_delegation, query_delegations}; -use crate::math::{ - compute_mint_amount, compute_redelegations_for_rebalancing, compute_redelegations_for_removal, - compute_unbond_amount, compute_undelegations, reconcile_batches, +use pfc_steak::{ + DecimalCheckedOps, + hub::{ + Batch, CallbackMsg, Cw20HookMsg, ExecuteMsg, FeeType, InstantiateMsg, PendingBatch, + UnbondRequest, + }, }; -use crate::state::State; -use crate::types::{Coins, Delegation}; +use crate::{ + contract::{REPLY_INSTANTIATE_TOKEN, REPLY_REGISTER_RECEIVED_COINS}, + helpers::{ + get_denom_balance, parse_received_fund, query_all_delegations, query_cw20_total_supply, + query_delegation, query_delegations, + }, + math::{ + compute_mint_amount, compute_redelegations_for_rebalancing, + compute_redelegations_for_removal, compute_unbond_amount, compute_undelegations, + reconcile_batches, + }, + state::State, + types::{Coins, Delegation, Redelegation}, +}; //-------------------------------------------------------------------------------------------------- // Instantiation //-------------------------------------------------------------------------------------------------- @@ -24,26 +36,42 @@ use crate::types::{Coins, Delegation}; pub fn instantiate(deps: DepsMut, env: Env, msg: InstantiateMsg) -> StdResult { let state = State::default(); + if msg.max_fee_amount > Decimal::from_str("1.00")? { + return Err(StdError::generic_err("Max fee can not exceed 1/100%")); + } + + if msg.fee_amount > msg.max_fee_amount { + return Err(StdError::generic_err("fee can not exceed max fee")); + } + let fee_type = FeeType::from_str(&msg.fee_account_type) + .map_err(|_| StdError::generic_err("Invalid Fee type: Wallet or FeeSplit only"))?; + state.owner.save(deps.storage, &deps.api.addr_validate(&msg.owner)?)?; state.epoch_period.save(deps.storage, &msg.epoch_period)?; state.unbond_period.save(deps.storage, &msg.unbond_period)?; state.validators.save(deps.storage, &msg.validators)?; state.unlocked_coins.save(deps.storage, &vec![])?; - - state.pending_batch.save( - deps.storage, - &PendingBatch { - id: 1, - usteak_to_burn: Uint128::zero(), - est_unbond_start_time: env.block.time.seconds() + msg.epoch_period, - }, - )?; + state.prev_denom.save(deps.storage, &Uint128::zero())?; + state.denom.save(deps.storage, &msg.denom)?; + state.max_fee_rate.save(deps.storage, &msg.max_fee_amount)?; + state.fee_rate.save(deps.storage, &msg.fee_amount)?; + state.fee_account_type.save(deps.storage, &fee_type)?; + + state.fee_account.save(deps.storage, &deps.api.addr_validate(&msg.fee_account)?)?; + + state.pending_batch.save(deps.storage, &PendingBatch { + id: 1, + usteak_to_burn: Uint128::zero(), + est_unbond_start_time: env.block.time.seconds() + msg.epoch_period, + })?; + state.validators_active.save(deps.storage, &msg.validators)?; Ok(Response::new().add_submessage(SubMsg::reply_on_success( CosmosMsg::Wasm(WasmMsg::Instantiate { - admin: Some(msg.owner), // use the owner as admin for now; can be changed later by a `MsgUpdateAdmin` + admin: Some(msg.owner), /* use the owner as admin for now; can be changed later by a + * `MsgUpdateAdmin` */ code_id: msg.cw20_code_id, - msg: to_binary(&Cw20InstantiateMsg { + msg: to_json_binary(&Cw20InstantiateMsg { name: msg.name, symbol: msg.symbol, decimals: msg.decimals, @@ -52,12 +80,12 @@ pub fn instantiate(deps: DepsMut, env: Env, msg: InstantiateMsg) -> StdResult StdResul // Bonding and harvesting logics //-------------------------------------------------------------------------------------------------- -/// NOTE: In a previous implementation, we split up the deposited Luna over all validators, so that -/// they all have the same amount of delegation. This is however quite gas-expensive: $1.5 cost in -/// the case of 15 validators. +/// bond tokens (XXX) to validators, returning bXXXX /// /// To save gas for users, now we simply delegate all deposited Luna to the validator with the /// smallest amount of delegation. If delegations become severely unbalance as a result of this @@ -99,16 +125,23 @@ pub fn bond( deps: DepsMut, env: Env, receiver: Addr, - uluna_to_bond: Uint128, + funds: Vec, + bond_msg: Option, + mint_it: bool, ) -> StdResult { let state = State::default(); + let denom = state.denom.load(deps.storage)?; + let amount_to_bond = parse_received_fund(&funds, &denom)?; let steak_token = state.steak_token.load(deps.storage)?; - let validators = state.validators.load(deps.storage)?; + let validators = state.validators_active.load(deps.storage)?; // Query the current delegations made to validators, and find the validator with the smallest // delegated amount through a linear search - // The code for linear search is a bit uglier than using `sort_by` but cheaper: O(n) vs O(n * log(n)) - let delegations = query_delegations(&deps.querier, &validators, &env.contract.address)?; + // The code for linear search is a bit uglier than using `sort_by` but cheaper: O(n) vs O(n * + // log(n)) + let all_delegations = query_all_delegations(&deps.querier, &env.contract.address)?; + let delegations = query_delegations(&deps.querier, &validators, &env.contract.address, &denom)?; + let mut validator = &delegations[0].validator; let mut amount = delegations[0].amount; for d in &delegations[1..] { @@ -119,39 +152,118 @@ pub fn bond( } let new_delegation = Delegation { validator: validator.clone(), - amount: uluna_to_bond.u128(), + amount: amount_to_bond.u128(), + denom: denom.clone(), }; // Query the current supply of Steak and compute the amount to mint let usteak_supply = query_cw20_total_supply(&deps.querier, &steak_token)?; - let usteak_to_mint = compute_mint_amount(usteak_supply, uluna_to_bond, &delegations); + let usteak_to_mint = compute_mint_amount(usteak_supply, amount_to_bond, &all_delegations); + state.prev_denom.save( + deps.storage, + &get_denom_balance(&deps.querier, env.contract.address.clone(), denom.clone())?, + )?; - let delegate_submsg = SubMsg::reply_on_success(new_delegation.to_cosmos_msg(), 2); + let delegate_submsg = + SubMsg::reply_on_success(new_delegation.to_cosmos_msg(), REPLY_REGISTER_RECEIVED_COINS); - let mint_msg: CosmosMsg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: steak_token.into(), - msg: to_binary(&Cw20ExecuteMsg::Mint { - recipient: receiver.to_string(), - amount: usteak_to_mint, - })?, - funds: vec![], - }); + let mint_msgs: Vec = if mint_it { + vec![CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: steak_token.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Mint { + recipient: receiver.to_string(), + amount: usteak_to_mint, + })?, + funds: vec![], + })] + } else { + let contract_info = deps.querier.query_wasm_contract_info(receiver.to_string()); + + let send_transfer_msg: CosmosMsg = match contract_info { + Ok(_) => { + if let Some(exec_msg) = bond_msg { + match from_json(exec_msg)? { + Cw20HookMsg::Transfer {} => CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: steak_token.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Transfer { + recipient: receiver.to_string(), + amount: usteak_to_mint, + })?, + funds: vec![], + }), + Cw20HookMsg::Distribute {} => CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: steak_token.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Send { + contract: receiver.to_string(), + amount: usteak_to_mint, + msg: to_json_binary( + &funds_distributor_api::msg::Cw20HookMsg::Distribute {}, + )?, + })?, + funds: vec![], + }), + } + } else { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: steak_token.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Send { + contract: receiver.to_string(), + amount: usteak_to_mint, + msg: Default::default(), + })?, + funds: vec![], + }) + } + }, + Err(_) => { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: steak_token.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Transfer { + recipient: receiver.to_string(), + amount: usteak_to_mint, + // msg: Default::default(), + })?, + funds: vec![], + }) + }, + }; + vec![ + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: steak_token.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Mint { + recipient: env.contract.address.to_string(), //receiver.to_string(), + amount: usteak_to_mint, + })?, + funds: vec![], + }), + send_transfer_msg, + ] + }; let event = Event::new("steakhub/bonded") .add_attribute("time", env.block.time.seconds().to_string()) .add_attribute("height", env.block.height.to_string()) - .add_attribute("receiver", receiver) - .add_attribute("uluna_bonded", uluna_to_bond) + .add_attribute("steak_receiver", receiver) + .add_attribute("denom_bonded", denom) + .add_attribute("denom_amount", amount_to_bond) .add_attribute("usteak_minted", usteak_to_mint); Ok(Response::new() .add_submessage(delegate_submsg) - .add_message(mint_msg) + .add_messages(mint_msgs) + // .add_message(send_msg) .add_event(event) .add_attribute("action", "steakhub/bond")) } pub fn harvest(deps: DepsMut, env: Env) -> StdResult { + let state = State::default(); + let denom = state.denom.load(deps.storage)?; + state.prev_denom.save( + deps.storage, + &get_denom_balance(&deps.querier, env.contract.address.clone(), denom)?, + )?; + let withdraw_submsgs = deps .querier .query_all_delegations(&env.contract.address)? @@ -161,7 +273,7 @@ pub fn harvest(deps: DepsMut, env: Env) -> StdResult { CosmosMsg::Distribution(DistributionMsg::WithdrawDelegatorReward { validator: d.validator, }), - 2, + REPLY_REGISTER_RECEIVED_COINS, ) }) .collect::>(); @@ -175,23 +287,39 @@ pub fn harvest(deps: DepsMut, env: Env) -> StdResult { } /// NOTE: -/// 1. When delegation Luna here, we don't need to use a `SubMsg` to handle the received coins, -/// because we have already withdrawn all claimable staking rewards previously in the same atomic -/// execution. +/// 1. When delegation Native denom here, we don't need to use a `SubMsg` to handle the received +/// coins, because we have already withdrawn all claimable staking rewards previously in the same +/// atomic execution. /// 2. Same as with `bond`, in the latest implementation we only delegate staking rewards with the -/// validator that has the smallest delegation amount. +/// validator that has the smallest delegation amount. pub fn reinvest(deps: DepsMut, env: Env) -> StdResult { let state = State::default(); - let validators = state.validators.load(deps.storage)?; + let denom = state.denom.load(deps.storage)?; + let fee = state.fee_rate.load(deps.storage)?; + + let validators = state.validators_active.load(deps.storage)?; + let prev_coin = state.prev_denom.load(deps.storage)?; + let current_coin = + get_denom_balance(&deps.querier, env.contract.address.clone(), denom.clone())?; + + if current_coin <= prev_coin { + return Err(StdError::generic_err("no rewards")); + } + let amount_to_bond = current_coin.saturating_sub(prev_coin); let mut unlocked_coins = state.unlocked_coins.load(deps.storage)?; - let uluna_to_bond = unlocked_coins - .iter() - .find(|coin| coin.denom == "uluna") - .ok_or_else(|| StdError::generic_err("no uluna available to be bonded"))? - .amount; + /* - let delegations = query_delegations(&deps.querier, &validators, &env.contract.address)?; + if unlocked_coins.is_empty() { + return Err(StdError::generic_err("no rewards")); + } + let amount_to_bond = unlocked_coins + .iter() + .find(|coin| coin.denom == denom) + .ok_or_else(|| StdError::generic_err("no native amount available to be bonded"))? + .amount; + */ + let delegations = query_delegations(&deps.querier, &validators, &env.contract.address, &denom)?; let mut validator = &delegations[0].validator; let mut amount = delegations[0].amount; for d in &delegations[1..] { @@ -200,23 +328,57 @@ pub fn reinvest(deps: DepsMut, env: Env) -> StdResult { amount = d.amount; } } - let new_delegation = Delegation::new(validator, uluna_to_bond.u128()); + let fee_amount = if fee.is_zero() { + Uint128::zero() + } else { + fee.checked_mul_uint(amount_to_bond)? + }; + let amount_to_bond_minus_fees = amount_to_bond.saturating_sub(fee_amount); - unlocked_coins.retain(|coin| coin.denom != "uluna"); + let new_delegation = Delegation::new(validator, amount_to_bond_minus_fees.u128(), &denom); + + unlocked_coins.retain(|coin| coin.denom != denom); state.unlocked_coins.save(deps.storage, &unlocked_coins)?; let event = Event::new("steakhub/harvested") .add_attribute("time", env.block.time.seconds().to_string()) .add_attribute("height", env.block.height.to_string()) - .add_attribute("uluna_bonded", uluna_to_bond); - - Ok(Response::new() - .add_message(new_delegation.to_cosmos_msg()) - .add_event(event) - .add_attribute("action", "steakhub/reinvest")) + .add_attribute("denom", &denom) + .add_attribute("fees_deducted", fee_amount) + .add_attribute("denom_bonded", amount_to_bond_minus_fees); + + if fee_amount > Uint128::zero() { + let fee_account = state.fee_account.load(deps.storage)?; + let fee_type = state.fee_account_type.load(deps.storage)?; + + let send_msgs = match fee_type { + FeeType::Wallet => vec![CosmosMsg::Bank(BankMsg::Send { + to_address: fee_account.to_string(), + amount: vec![Coin::new(fee_amount.into(), &denom)], + })], + FeeType::FeeSplit => { + let msg = pfc_fee_split::fee_split_msg::ExecuteMsg::Deposit { + flush: false, + }; + + vec![msg.into_cosmos_msg(fee_account, vec![Coin::new(fee_amount.into(), &denom)])?] + }, + }; + Ok(Response::new() + .add_message(new_delegation.to_cosmos_msg()) + .add_messages(send_msgs) + .add_event(event) + .add_attribute("action", "steakhub/reinvest")) + } else { + Ok(Response::new() + .add_message(new_delegation.to_cosmos_msg()) + .add_event(event) + .add_attribute("action", "steakhub/reinvest")) + } } -/// NOTE: a `SubMsgResponse` may contain multiple coin-receiving events, must handle them individually +/// NOTE: a `SubMsgResponse` may contain multiple coin-receiving events, must handle them +/// individually pub fn register_received_coins( deps: DepsMut, env: Env, @@ -239,8 +401,7 @@ pub fn register_received_coins( Ok(coins.0) })?; - Ok(Response::new() - .add_attribute("action", "steakhub/register_received_coins")) + Ok(Response::new().add_attribute("action", "steakhub/register_received_coins")) } fn parse_coin_receiving_event(env: &Env, event: &Event) -> StdResult { @@ -301,7 +462,7 @@ pub fn queue_unbond( if env.block.time.seconds() >= pending_batch.est_unbond_start_time { msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: env.contract.address.into(), - msg: to_binary(&ExecuteMsg::SubmitBatch {})?, + msg: to_json_binary(&ExecuteMsg::SubmitBatch {})?, funds: vec![], })); } @@ -321,75 +482,92 @@ pub fn queue_unbond( pub fn submit_batch(deps: DepsMut, env: Env) -> StdResult { let state = State::default(); + let denom = state.denom.load(deps.storage)?; let steak_token = state.steak_token.load(deps.storage)?; let validators = state.validators.load(deps.storage)?; + let unbond_period = state.unbond_period.load(deps.storage)?; let pending_batch = state.pending_batch.load(deps.storage)?; let current_time = env.block.time.seconds(); if current_time < pending_batch.est_unbond_start_time { - return Err(StdError::generic_err( - format!("batch can only be submitted for unbonding after {}", pending_batch.est_unbond_start_time), - )); + return Err(StdError::generic_err(format!( + "batch can only be submitted for unbonding after {}", + pending_batch.est_unbond_start_time + ))); + } + let mut validators_active: HashSet = + HashSet::from_iter(state.validators_active.load(deps.storage)?); + for v in validators.iter() { + validators_active.remove(v); } + // let active_validator_list = Vec::from_iter(validators_active); - let delegations = query_delegations(&deps.querier, &validators, &env.contract.address)?; + // for unbonding we still need to look at + let delegations = query_all_delegations(&deps.querier, &env.contract.address)?; + // let delegations_active = + // query_delegations(&deps.querier, &active_validator_list, &env.contract.address, &denom)?; let usteak_supply = query_cw20_total_supply(&deps.querier, &steak_token)?; - let uluna_to_unbond = compute_unbond_amount(usteak_supply, pending_batch.usteak_to_burn, &delegations); - let new_undelegations = compute_undelegations(uluna_to_unbond, &delegations); + let amount_to_bond = + compute_unbond_amount(usteak_supply, pending_batch.usteak_to_burn, &delegations); + let new_undelegations = compute_undelegations(amount_to_bond, &delegations, &denom); - // NOTE: Regarding the `uluna_unclaimed` value + // NOTE: Regarding the `amount_unclaimed` value // // If validators misbehave and get slashed during the unbonding period, the contract can receive - // LESS Luna than `uluna_to_unbond` when unbonding finishes! + // LESS Luna than `amount_to_unbond` when unbonding finishes! // - // In this case, users who invokes `withdraw_unbonded` will have their txs failed as the contract - // does not have enough Luna balance. + // In this case, users who invokes `withdraw_unbonded` will have their txs failed as the + // contract does not have enough Luna balance. // - // I don't have a solution for this... other than to manually fund contract with the slashed amount. - state.previous_batches.save( - deps.storage, - pending_batch.id, - &Batch { - id: pending_batch.id, - reconciled: false, - total_shares: pending_batch.usteak_to_burn, - uluna_unclaimed: uluna_to_unbond, - est_unbond_end_time: current_time + unbond_period, - }, - )?; + // I don't have a solution for this... other than to manually fund contract with the slashed + // amount. + state.previous_batches.save(deps.storage, pending_batch.id, &Batch { + id: pending_batch.id, + reconciled: false, + total_shares: pending_batch.usteak_to_burn, + amount_unclaimed: amount_to_bond, + est_unbond_end_time: current_time + unbond_period, + })?; let epoch_period = state.epoch_period.load(deps.storage)?; - state.pending_batch.save( - deps.storage, - &PendingBatch { - id: pending_batch.id + 1, - usteak_to_burn: Uint128::zero(), - est_unbond_start_time: current_time + epoch_period, - }, - )?; + state.pending_batch.save(deps.storage, &PendingBatch { + id: pending_batch.id + 1, + usteak_to_burn: Uint128::zero(), + est_unbond_start_time: current_time + epoch_period, + })?; + state + .prev_denom + .save(deps.storage, &get_denom_balance(&deps.querier, env.contract.address, denom)?)?; let undelegate_submsgs = new_undelegations .iter() - .map(|d| SubMsg::reply_on_success(d.to_cosmos_msg(), 2)) + .map(|d| SubMsg::reply_on_success(d.to_cosmos_msg(), REPLY_REGISTER_RECEIVED_COINS)) .collect::>(); + let event = Event::new("steakhub/unbond_submitted") + .add_attribute("time", env.block.time.seconds().to_string()) + .add_attribute("height", env.block.height.to_string()) + .add_attribute("id", pending_batch.id.to_string()) + .add_attribute("native_unbonded", amount_to_bond) + .add_attribute("usteak_burned", pending_batch.usteak_to_burn); + + if pending_batch.usteak_to_burn.is_zero() { + return Ok(Response::new() + .add_submessages(undelegate_submsgs) + // .add_message(burn_msg) + .add_event(event)); + } + let burn_msg = CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: steak_token.into(), - msg: to_binary(&Cw20ExecuteMsg::Burn { + msg: to_json_binary(&Cw20ExecuteMsg::Burn { amount: pending_batch.usteak_to_burn, })?, funds: vec![], }); - let event = Event::new("steakhub/unbond_submitted") - .add_attribute("time", env.block.time.seconds().to_string()) - .add_attribute("height", env.block.height.to_string()) - .add_attribute("id", pending_batch.id.to_string()) - .add_attribute("uluna_unbonded", uluna_to_unbond) - .add_attribute("usteak_burned", pending_batch.usteak_to_burn); - Ok(Response::new() .add_submessages(undelegate_submsgs) .add_message(burn_msg) @@ -419,39 +597,46 @@ pub fn reconcile(deps: DepsMut, env: Env) -> StdResult { .filter(|b| current_time > b.est_unbond_end_time) .collect::>(); - let uluna_expected_received: Uint128 = batches - .iter() - .map(|b| b.uluna_unclaimed) - .sum(); - + let native_expected_received: Uint128 = batches.iter().map(|b| b.amount_unclaimed).sum(); + let denom = state.denom.load(deps.storage)?; let unlocked_coins = state.unlocked_coins.load(deps.storage)?; - let uluna_expected_unlocked = Coins(unlocked_coins).find("uluna").amount; - let uluna_expected = uluna_expected_received + uluna_expected_unlocked; - let uluna_actual = deps.querier.query_balance(&env.contract.address, "uluna")?.amount; + let native_expected_unlocked = Coins(unlocked_coins).find(&denom).amount; + + let native_expected = native_expected_received + native_expected_unlocked; + let native_actual = deps.querier.query_balance(&env.contract.address, &denom)?.amount; - let uluna_to_deduct = uluna_expected.checked_sub(uluna_actual).unwrap_or_else(|_| Uint128::zero()); - if !uluna_to_deduct.is_zero() { - reconcile_batches(&mut batches, uluna_expected - uluna_actual); + let native_to_deduct = + native_expected.checked_sub(native_actual).unwrap_or_else(|_| Uint128::zero()); + if !native_to_deduct.is_zero() { + reconcile_batches(&mut batches, native_expected - native_actual); } - for batch in &batches { + for batch in batches.iter_mut() { + batch.reconciled = true; state.previous_batches.save(deps.storage, batch.id, batch)?; } - let ids = batches - .iter() - .map(|b| b.id.to_string()) - .collect::>() - .join(","); + let ids = batches.iter().map(|b| b.id.to_string()).collect::>().join(","); let event = Event::new("steakhub/reconciled") .add_attribute("ids", ids) - .add_attribute("uluna_deducted", uluna_to_deduct.to_string()); + .add_attribute("native_deducted", native_to_deduct.to_string()); - Ok(Response::new() - .add_event(event) - .add_attribute("action", "steakhub/reconcile")) + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/reconcile")) +} + +pub fn withdraw_unbonded_admin( + deps: DepsMut, + env: Env, + user: Addr, + receiver: Addr, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &user)?; + + withdraw_unbonded(deps, env, receiver.clone(), receiver) } pub fn withdraw_unbonded( @@ -461,6 +646,7 @@ pub fn withdraw_unbonded( receiver: Addr, ) -> StdResult { let state = State::default(); + let denom = state.denom.load(deps.storage)?; let current_time = env.block.time.seconds(); // NOTE: If the user has too many unclaimed requests, this may not fit in the WASM memory... @@ -478,26 +664,25 @@ pub fn withdraw_unbonded( }) .collect::>>()?; - // NOTE: Luna in the following batches are withdrawn it the batch: + // NOTE: Native in the following batches are withdrawn it the batch: // - is a _previous_ batch, not a _pending_ batch // - is reconciled // - has finished unbonding - // If not sure whether the batches have been reconciled, the user should first invoke `ExecuteMsg::Reconcile` - // before withdrawing. - let mut total_uluna_to_refund = Uint128::zero(); + // If not sure whether the batches have been reconciled, the user should first invoke + // `ExecuteMsg::Reconcile` before withdrawing. + let mut total_native_to_refund = Uint128::zero(); let mut ids: Vec = vec![]; for request in &requests { if let Ok(mut batch) = state.previous_batches.load(deps.storage, request.id) { if batch.reconciled && batch.est_unbond_end_time < current_time { - let uluna_to_refund = batch - .uluna_unclaimed - .multiply_ratio(request.shares, batch.total_shares); + let native_to_refund = + batch.amount_unclaimed.multiply_ratio(request.shares, batch.total_shares); ids.push(request.id.to_string()); - total_uluna_to_refund += uluna_to_refund; + total_native_to_refund += native_to_refund; batch.total_shares -= request.shares; - batch.uluna_unclaimed -= uluna_to_refund; + batch.amount_unclaimed -= native_to_refund; if batch.total_shares.is_zero() { state.previous_batches.remove(deps.storage, request.id)?; @@ -510,13 +695,13 @@ pub fn withdraw_unbonded( } } - if total_uluna_to_refund.is_zero() { + if total_native_to_refund.is_zero() { return Err(StdError::generic_err("withdrawable amount is zero")); } let refund_msg = CosmosMsg::Bank(BankMsg::Send { to_address: receiver.clone().into(), - amount: vec![Coin::new(total_uluna_to_refund.u128(), "uluna")], + amount: vec![Coin::new(total_native_to_refund.u128(), denom)], }); let event = Event::new("steakhub/unbonded_withdrawn") @@ -525,7 +710,7 @@ pub fn withdraw_unbonded( .add_attribute("ids", ids.join(",")) .add_attribute("user", user) .add_attribute("receiver", receiver) - .add_attribute("uluna_refunded", total_uluna_to_refund); + .add_attribute("amount_refunded", total_native_to_refund); Ok(Response::new() .add_message(refund_msg) @@ -537,23 +722,29 @@ pub fn withdraw_unbonded( // Ownership and management logics //-------------------------------------------------------------------------------------------------- -pub fn rebalance(deps: DepsMut, env: Env) -> StdResult { +pub fn rebalance(deps: DepsMut, env: Env, minimum: Uint128) -> StdResult { let state = State::default(); - let validators = state.validators.load(deps.storage)?; + let denom = state.denom.load(deps.storage)?; + //let validators = state.validators.load(deps.storage)?; + let validators_active = state.validators_active.load(deps.storage)?; - let delegations = query_delegations(&deps.querier, &validators, &env.contract.address)?; + let delegations = query_all_delegations(&deps.querier, &env.contract.address)?; - let new_redelegations = compute_redelegations_for_rebalancing(&delegations); + let new_redelegations = + compute_redelegations_for_rebalancing(validators_active, &delegations, minimum); + + state + .prev_denom + .save(deps.storage, &get_denom_balance(&deps.querier, env.contract.address, denom)?)?; let redelegate_submsgs = new_redelegations .iter() - .map(|rd| SubMsg::reply_on_success(rd.to_cosmos_msg(), 2)) + .map(|rd| SubMsg::reply_on_success(rd.to_cosmos_msg(), REPLY_REGISTER_RECEIVED_COINS)) .collect::>(); let amount: u128 = new_redelegations.iter().map(|rd| rd.amount).sum(); - let event = Event::new("steakhub/rebalanced") - .add_attribute("uluna_moved", amount.to_string()); + let event = Event::new("steakhub/rebalanced").add_attribute("amount_moved", amount.to_string()); Ok(Response::new() .add_submessages(redelegate_submsgs) @@ -574,12 +765,14 @@ pub fn add_validator(deps: DepsMut, sender: Addr, validator: String) -> StdResul Ok(validators) })?; - let event = Event::new("steakhub/validator_added") - .add_attribute("validator", validator); + let mut validators_active = state.validators_active.load(deps.storage)?; + if !validators_active.contains(&validator) { + validators_active.push(validator.clone()); + } + state.validators_active.save(deps.storage, &validators_active)?; + let event = Event::new("steakhub/validator_added").add_attribute("validator", validator); - Ok(Response::new() - .add_event(event) - .add_attribute("action", "steakhub/add_validator")) + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/add_validator")) } pub fn remove_validator( @@ -591,6 +784,7 @@ pub fn remove_validator( let state = State::default(); state.assert_owner(deps.storage, &sender)?; + let denom = state.denom.load(deps.storage)?; let validators = state.validators.update(deps.storage, |mut validators| { if !validators.contains(&validator) { @@ -599,18 +793,28 @@ pub fn remove_validator( validators.retain(|v| *v != validator); Ok(validators) })?; + let mut validators_active = state.validators_active.load(deps.storage)?; + if !validators_active.contains(&validator) { + validators_active.push(validator.clone()); + } + state.validators_active.save(deps.storage, &validators_active)?; - let delegations = query_delegations(&deps.querier, &validators, &env.contract.address)?; - let delegation_to_remove = query_delegation(&deps.querier, &validator, &env.contract.address)?; - let new_redelegations = compute_redelegations_for_removal(&delegation_to_remove, &delegations); + let delegations = query_delegations(&deps.querier, &validators, &env.contract.address, &denom)?; + let delegation_to_remove = + query_delegation(&deps.querier, &validator, &env.contract.address, &denom)?; + let new_redelegations = + compute_redelegations_for_removal(&delegation_to_remove, &delegations, &denom); + + state + .prev_denom + .save(deps.storage, &get_denom_balance(&deps.querier, env.contract.address, denom)?)?; let redelegate_submsgs = new_redelegations .iter() - .map(|d| SubMsg::reply_on_success(d.to_cosmos_msg(), 2)) + .map(|d| SubMsg::reply_on_success(d.to_cosmos_msg(), REPLY_REGISTER_RECEIVED_COINS)) .collect::>(); - let event = Event::new("steak/validator_removed") - .add_attribute("validator", validator); + let event = Event::new("steak/validator_removed").add_attribute("validator", validator); Ok(Response::new() .add_submessages(redelegate_submsgs) @@ -618,14 +822,95 @@ pub fn remove_validator( .add_attribute("action", "steakhub/remove_validator")) } +pub fn remove_validator_ex( + deps: DepsMut, + _env: Env, + sender: Addr, + validator: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + + state.validators.update(deps.storage, |mut validators| { + if !validators.contains(&validator) { + return Err(StdError::generic_err("validator is not already whitelisted")); + } + validators.retain(|v| *v != validator); + Ok(validators) + })?; + + let event = Event::new("steak/validator_removed_ex").add_attribute("validator", validator); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/remove_validator_ex")) +} + +pub fn pause_validator( + deps: DepsMut, + _env: Env, + sender: Addr, + validator: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + + state.validators_active.update(deps.storage, |mut validators| { + if !validators.contains(&validator) { + return Err(StdError::generic_err("validator is not already whitelisted")); + } + validators.retain(|v| *v != validator); + Ok(validators) + })?; + + let event = Event::new("steak/pause_validator").add_attribute("validator", validator); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/pause_validator")) +} + +pub fn unpause_validator( + deps: DepsMut, + _env: Env, + sender: Addr, + validator: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + let mut validators_active = state.validators_active.load(deps.storage)?; + if !validators_active.contains(&validator) { + validators_active.push(validator.clone()); + } + state.validators_active.save(deps.storage, &validators_active)?; + + let event = Event::new("steak/unpause_validator").add_attribute("validator", validator); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/unpause_validator")) +} + +pub fn set_unbond_period( + deps: DepsMut, + _env: Env, + sender: Addr, + unbond_period: u64, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + state.unbond_period.save(deps.storage, &unbond_period)?; + let event = Event::new("steak/set_unbond_period") + .add_attribute("unbond_period", format!("{}", unbond_period)); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/set_unbond_period")) +} + pub fn transfer_ownership(deps: DepsMut, sender: Addr, new_owner: String) -> StdResult { let state = State::default(); state.assert_owner(deps.storage, &sender)?; state.new_owner.save(deps.storage, &deps.api.addr_validate(&new_owner)?)?; - Ok(Response::new() - .add_attribute("action", "steakhub/transfer_ownership")) + Ok(Response::new().add_attribute("action", "steakhub/transfer_ownership")) } pub fn accept_ownership(deps: DepsMut, sender: Addr) -> StdResult { @@ -645,7 +930,131 @@ pub fn accept_ownership(deps: DepsMut, sender: Addr) -> StdResult { .add_attribute("new_owner", new_owner) .add_attribute("previous_owner", previous_owner); + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/transfer_ownership")) +} + +pub fn transfer_fee_account( + deps: DepsMut, + sender: Addr, + fee_account_type: String, + new_fee_account: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + let fee_type = FeeType::from_str(&fee_account_type) + .map_err(|_| StdError::generic_err("Invalid Fee type: Wallet or FeeSplit only"))?; + + state.fee_account_type.save(deps.storage, &fee_type)?; + + state.fee_account.save(deps.storage, &deps.api.addr_validate(&new_fee_account)?)?; + + Ok(Response::new().add_attribute("action", "steakhub/transfer_fee_account")) +} + +pub fn change_denom(deps: DepsMut, sender: Addr, new_denom: String) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + state.denom.save(deps.storage, &new_denom)?; + + Ok(Response::new().add_attribute("action", "steakhub/change_denom")) +} + +pub fn update_fee(deps: DepsMut, sender: Addr, new_fee: Decimal) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + if new_fee > state.max_fee_rate.load(deps.storage)? { + return Err(StdError::generic_err("refusing to set fee above maximum set")); + } + state.fee_rate.save(deps.storage, &new_fee)?; + + Ok(Response::new().add_attribute("action", "steakhub/update_fee")) +} + +pub fn set_dust_collector( + deps: DepsMut, + _env: Env, + sender: Addr, + dust_collector: Option, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + if let Some(ref dust_addr) = dust_collector { + state.dust_collector.save(deps.storage, &Some(deps.api.addr_validate(dust_addr)?))?; + } else { + state.dust_collector.save(deps.storage, &None)?; + }; + let event = Event::new("steak/set_dust_collector") + .add_attribute("dust_collector", dust_collector.unwrap_or("-cleared-".into())); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/set_dust_collector")) +} + +pub fn collect_dust(deps: DepsMut, _env: Env, _max_tokens: usize) -> StdResult { + let state = State::default(); + + if let Some(_dust_addr) = state.dust_collector.load(deps.storage)? { + Ok(Response::new().add_attribute("dust", "tbd")) + } else { + Err(StdError::generic_err("No dust collector set")) + } +} +pub fn set_base_denom(deps: DepsMut, sender: Addr, new_denom: String) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + state.denom.save(deps.storage, &new_denom)?; + + let event = Event::new("steak/set_base_denom").add_attribute("base_denom", new_denom); + + Ok(Response::new().add_event(event).add_attribute("action", "steakhub/set_base_denom")) +} + +pub fn return_denom(deps: DepsMut, _env: Env, _funds: Vec) -> StdResult { + let state = State::default(); + + if let Some(_dust_addr) = state.dust_collector.load(deps.storage)? { + Ok(Response::new().add_attribute("dust", "returned")) + } else { + Err(StdError::generic_err("No dust collector set")) + } +} + +pub fn redelegate( + deps: DepsMut, + env: Env, + sender: Addr, + validator_from: String, + validator_to: String, +) -> StdResult { + let state = State::default(); + + state.assert_owner(deps.storage, &sender)?; + let denom = state.denom.load(deps.storage)?; + + let delegation = + query_delegation(&deps.querier, &validator_from, &env.contract.address, &denom)?; + + let redelegation_msg = SubMsg::reply_on_success( + Redelegation::new(&validator_from, &validator_to, delegation.amount, &denom) + .to_cosmos_msg(), + REPLY_REGISTER_RECEIVED_COINS, + ); + + state + .prev_denom + .save(deps.storage, &get_denom_balance(&deps.querier, env.contract.address, denom)?)?; + + let event = Event::new("steak/redelegate") + .add_attribute("validator_from", validator_from) + .add_attribute("validator_to", validator_to) + .add_attribute("amount", delegation.amount.to_string()); + Ok(Response::new() + .add_submessage(redelegation_msg) .add_event(event) - .add_attribute("action", "steakhub/transfer_ownership")) + .add_attribute("action", "steakhub/redelegate")) } diff --git a/contracts/hub/src/helpers.rs b/contracts/hub/src/helpers.rs index b47fb77e..9a24a2a4 100644 --- a/contracts/hub/src/helpers.rs +++ b/contracts/hub/src/helpers.rs @@ -1,7 +1,8 @@ use std::str::FromStr; use cosmwasm_std::{ - Addr, Coin, QuerierWrapper, Reply, StdError, StdResult, SubMsgResponse, Uint128, + Addr, BalanceResponse, BankQuery, Coin, QuerierWrapper, QueryRequest, Reply, StdError, + StdResult, SubMsgResponse, Uint128, }; use cw20::{Cw20QueryMsg, TokenInfoResponse}; @@ -17,7 +18,8 @@ pub(crate) fn query_cw20_total_supply( querier: &QuerierWrapper, token_addr: &Addr, ) -> StdResult { - let token_info: TokenInfoResponse = querier.query_wasm_smart(token_addr, &Cw20QueryMsg::TokenInfo {})?; + let token_info: TokenInfoResponse = + querier.query_wasm_smart(token_addr, &Cw20QueryMsg::TokenInfo {})?; Ok(token_info.total_supply) } @@ -26,10 +28,16 @@ pub(crate) fn query_delegation( querier: &QuerierWrapper, validator: &str, delegator_addr: &Addr, + denom: &str, ) -> StdResult { Ok(Delegation { validator: validator.to_string(), - amount: querier.query_delegation(delegator_addr, validator)?.map(|fd| fd.amount.amount.u128()).unwrap_or(0), + + amount: querier + .query_delegation(delegator_addr, validator)? + .map(|fd| fd.amount.amount.u128()) + .unwrap_or(0), + denom: denom.into(), }) } @@ -38,12 +46,30 @@ pub(crate) fn query_delegations( querier: &QuerierWrapper, validators: &[String], delegator_addr: &Addr, + denom: &str, ) -> StdResult> { validators .iter() - .map(|validator| query_delegation(querier, validator, delegator_addr)) + .map(|validator| query_delegation(querier, validator, delegator_addr, denom)) .collect() } +/// Query the amounts of Luna a staker is delegating to each of the validators specified +pub(crate) fn query_all_delegations( + querier: &QuerierWrapper, + delegator_addr: &Addr, +) -> StdResult> { + let res = querier + .query_all_delegations(delegator_addr)? + .into_iter() + .map(|validator| Delegation { + validator: validator.validator.clone(), + amount: validator.amount.amount.u128(), + denom: validator.amount.denom.clone(), + }) + .collect::>(); + + Ok(res) +} /// `cosmwasm_std::Coin` does not implement `FromStr`, so we have do it ourselves /// @@ -71,16 +97,18 @@ pub(crate) fn parse_coin(s: &str) -> StdResult { /// sent together pub(crate) fn parse_received_fund(funds: &[Coin], denom: &str) -> StdResult { if funds.len() != 1 { - return Err(StdError::generic_err( - format!("must deposit exactly one coin; received {}", funds.len()), - )); + return Err(StdError::generic_err(format!( + "must deposit exactly one coin; received {}", + funds.len() + ))); } let fund = &funds[0]; if fund.denom != denom { - return Err(StdError::generic_err( - format!("expected {} deposit, received {}", denom, fund.denom), - )); + return Err(StdError::generic_err(format!( + "expected {} deposit, received {}", + denom, fund.denom + ))); } if fund.amount.is_zero() { @@ -89,3 +117,15 @@ pub(crate) fn parse_received_fund(funds: &[Coin], denom: &str) -> StdResult StdResult { + let balance: BalanceResponse = querier.query(&QueryRequest::Bank(BankQuery::Balance { + address: account_addr.to_string(), + denom, + }))?; + Ok(balance.amount.amount) +} diff --git a/contracts/hub/src/lib.rs b/contracts/hub/src/lib.rs index fa160779..0cb2fe79 100644 --- a/contracts/hub/src/lib.rs +++ b/contracts/hub/src/lib.rs @@ -1,4 +1,4 @@ -#[cfg(not(feature = "library"))] +//#[cfg(not(feature = "library"))] pub mod contract; pub mod execute; @@ -8,5 +8,6 @@ pub mod queries; pub mod state; pub mod types; +mod migrations; #[cfg(test)] mod testing; diff --git a/contracts/hub/src/math.rs b/contracts/hub/src/math.rs index 83c29a5e..681dcf1a 100644 --- a/contracts/hub/src/math.rs +++ b/contracts/hub/src/math.rs @@ -1,8 +1,7 @@ -use std::{cmp, cmp::Ordering}; +use std::{cmp, cmp::Ordering, collections::HashMap}; use cosmwasm_std::Uint128; - -use steak::hub::Batch; +use pfc_steak::hub::Batch; use crate::types::{Delegation, Redelegation, Undelegation}; @@ -11,77 +10,82 @@ use crate::types::{Delegation, Redelegation, Undelegation}; //-------------------------------------------------------------------------------------------------- /// Compute the amount of Steak token to mint for a specific Luna stake amount. If current total -/// staked amount is zero, we use 1 usteak = 1 uluna; otherwise, we calculate base on the current -/// uluna per ustake ratio. +/// staked amount is zero, we use 1 usteak = 1 native; otherwise, we calculate base on the current +/// native per ustake ratio. pub(crate) fn compute_mint_amount( usteak_supply: Uint128, - uluna_to_bond: Uint128, - current_delegations: &[Delegation], + native_to_bond: Uint128, + all_delegations: &[Delegation], + // inactive_delegations: &[Delegation], ) -> Uint128 { - let uluna_bonded: u128 = current_delegations.iter().map(|d| d.amount).sum(); - if uluna_bonded == 0 { - uluna_to_bond + let native_bonded: u128 = all_delegations.iter().map(|d| d.amount).sum(); + // let native_bonded_inactive: u128 = inactive_delegations.iter().map(|d| d.amount).sum(); + // let native_bonded = native_bonded_c + native_bonded_inactive; + if native_bonded == 0 { + native_to_bond } else { - usteak_supply.multiply_ratio(uluna_to_bond, uluna_bonded) + usteak_supply.multiply_ratio(native_to_bond, native_bonded) } } -/// Compute the amount of `uluna` to unbond for a specific `usteak` burn amount +/// Compute the amount of `native` to unbond for a specific `usteak` burn amount /// -/// There is no way `usteak` total supply is zero when the user is senting a non-zero amount of `usteak` -/// to burn, so we don't need to handle division-by-zero here +/// There is no way `usteak` total supply is zero when the user is senting a non-zero amount of +/// `usteak` to burn, so we don't need to handle division-by-zero here pub(crate) fn compute_unbond_amount( usteak_supply: Uint128, usteak_to_burn: Uint128, - current_delegations: &[Delegation], + all_delegations: &[Delegation], + // active_delegations: &[Delegation], ) -> Uint128 { - let uluna_bonded: u128 = current_delegations.iter().map(|d| d.amount).sum(); - Uint128::new(uluna_bonded).multiply_ratio(usteak_to_burn, usteak_supply) + let native_bonded: u128 = all_delegations.iter().map(|d| d.amount).sum(); + // let native_bonded_a: u128 = active_delegations.iter().map(|d| d.amount).sum(); + // let native_bonded = native_bonded_c + native_bonded_a; + Uint128::new(native_bonded).multiply_ratio(usteak_to_burn, usteak_supply) } //-------------------------------------------------------------------------------------------------- // Delegation logics //-------------------------------------------------------------------------------------------------- -/// Given the current delegations made to validators, and a specific amount of `uluna` to unstake, +/// Given the current delegations made to validators, and a specific amount of `native` to unstake, /// compute the undelegations to make such that the delegated amount to each validator is as even /// as possible. /// /// This function is based on Lido's implementation: /// https://github.com/lidofinance/lido-terra-contracts/blob/v1.0.2/contracts/lido_terra_validators_registry/src/common.rs#L55-102 pub(crate) fn compute_undelegations( - uluna_to_unbond: Uint128, + native_to_unbond: Uint128, current_delegations: &[Delegation], + denom: &str, ) -> Vec { - let uluna_staked: u128 = current_delegations.iter().map(|d| d.amount).sum(); + let native_staked: u128 = current_delegations.iter().map(|d| d.amount).sum(); let validator_count = current_delegations.len() as u128; - let uluna_to_distribute = uluna_staked - uluna_to_unbond.u128(); - let uluna_per_validator = uluna_to_distribute / validator_count; - let remainder = uluna_to_distribute % validator_count; + let native_to_distribute = native_staked - native_to_unbond.u128(); + let native_per_validator = native_to_distribute / validator_count; + let remainder = native_to_distribute % validator_count; let mut new_undelegations: Vec = vec![]; - let mut uluna_available = uluna_to_unbond.u128(); + let mut native_available = native_to_unbond.u128(); for (i, d) in current_delegations.iter().enumerate() { - let remainder_for_validator: u128 = if (i + 1) as u128 <= remainder { 1 } else { 0 }; - let uluna_for_validator = uluna_per_validator + remainder_for_validator; + let remainder_for_validator: u128 = u128::from((i + 1) as u128 <= remainder); + let native_for_validator = native_per_validator + remainder_for_validator; - let mut uluna_to_undelegate = if d.amount < uluna_for_validator { + let mut native_to_undelegate = if d.amount < native_for_validator { 0 } else { - d.amount - uluna_for_validator + d.amount - native_for_validator }; - uluna_to_undelegate = std::cmp::min(uluna_to_undelegate, uluna_available); - uluna_available -= uluna_to_undelegate; + native_to_undelegate = cmp::min(native_to_undelegate, native_available); + native_available -= native_to_undelegate; - if uluna_to_undelegate > 0 { - new_undelegations.push( - Undelegation::new(&d.validator, uluna_to_undelegate), - ); + if native_to_undelegate > 0 { + new_undelegations.push(Undelegation::new(&d.validator, native_to_undelegate, denom)); } - if uluna_available == 0 { + if native_available == 0 { break; } } @@ -98,36 +102,40 @@ pub(crate) fn compute_undelegations( pub(crate) fn compute_redelegations_for_removal( delegation_to_remove: &Delegation, current_delegations: &[Delegation], + denom: &str, ) -> Vec { - let uluna_staked: u128 = current_delegations.iter().map(|d| d.amount).sum(); + let native_staked: u128 = current_delegations.iter().map(|d| d.amount).sum(); let validator_count = current_delegations.len() as u128; - let uluna_to_distribute = uluna_staked + delegation_to_remove.amount; - let uluna_per_validator = uluna_to_distribute / validator_count; - let remainder = uluna_to_distribute % validator_count; + let native_to_distribute = native_staked + delegation_to_remove.amount; + let native_per_validator = native_to_distribute / validator_count; + let remainder = native_to_distribute % validator_count; let mut new_redelegations: Vec = vec![]; - let mut uluna_available = delegation_to_remove.amount; + let mut native_available = delegation_to_remove.amount; for (i, d) in current_delegations.iter().enumerate() { - let remainder_for_validator: u128 = if (i + 1) as u128 <= remainder { 1 } else { 0 }; - let uluna_for_validator = uluna_per_validator + remainder_for_validator; + let remainder_for_validator: u128 = u128::from((i + 1) as u128 <= remainder); + let native_for_validator = native_per_validator + remainder_for_validator; - let mut uluna_to_redelegate = if d.amount > uluna_for_validator { + let mut native_to_redelegate = if d.amount > native_for_validator { 0 } else { - uluna_for_validator - d.amount + native_for_validator - d.amount }; - uluna_to_redelegate = std::cmp::min(uluna_to_redelegate, uluna_available); - uluna_available -= uluna_to_redelegate; + native_to_redelegate = cmp::min(native_to_redelegate, native_available); + native_available -= native_to_redelegate; - if uluna_to_redelegate > 0 { - new_redelegations.push( - Redelegation::new(&delegation_to_remove.validator, &d.validator, uluna_to_redelegate), - ); + if native_to_redelegate > 0 { + new_redelegations.push(Redelegation::new( + &delegation_to_remove.validator, + &d.validator, + native_to_redelegate, + denom, + )); } - if uluna_available == 0 { + if native_available == 0 { break; } } @@ -135,35 +143,53 @@ pub(crate) fn compute_redelegations_for_removal( new_redelegations } -/// Compute redelegation moves that will make each validator's delegation the targeted amount (hopefully -/// this sentence makes sense) +/// Compute redelegation moves that will make each validator's delegation the targeted amount +/// (hopefully this sentence makes sense) /// -/// This algorithm does not guarantee the minimal number of moves, but is the best I can some up with... +/// This algorithm does not guarantee the minimal number of moves, but is the best I can some up +/// with... pub(crate) fn compute_redelegations_for_rebalancing( + validators_active: Vec, current_delegations: &[Delegation], + min_difference: Uint128, ) -> Vec { - let uluna_staked: u128 = current_delegations.iter().map(|d| d.amount).sum(); - let validator_count = current_delegations.len() as u128; + let native_staked: u128 = current_delegations.iter().map(|d| d.amount).sum(); + let validator_count = validators_active.len() as u128; - let uluna_per_validator = uluna_staked / validator_count; - let remainder = uluna_staked % validator_count; + let native_per_validator = native_staked / validator_count; + let remainder = native_staked % validator_count; - // If a validator's current delegated amount is greater than the target amount, Luna will be + // If a validator's current delegated amount is greater than the target amount, native will be // redelegated _from_ them. They will be put in `src_validators` vector - // If a validator's current delegated amount is smaller than the target amount, Luna will be + // If a validator's current delegated amount is smaller than the target amount, native will be // redelegated _to_ them. They will be put in `dst_validators` vector let mut src_delegations: Vec = vec![]; let mut dst_delegations: Vec = vec![]; for (i, d) in current_delegations.iter().enumerate() { - let remainder_for_validator: u128 = if (i + 1) as u128 <= remainder { 1 } else { 0 }; - let uluna_for_validator = uluna_per_validator + remainder_for_validator; - - match d.amount.cmp(&uluna_for_validator) { + let remainder_for_validator: u128 = u128::from((i + 1) as u128 <= remainder); + let native_for_validator = native_per_validator + remainder_for_validator; + // eprintln!("{} amount ={} native={} min={}", d.validator, d.amount, native_for_validator, + // min_difference); + match d.amount.cmp(&native_for_validator) { Ordering::Greater => { - src_delegations.push(Delegation::new(&d.validator, d.amount - uluna_for_validator)); + if d.amount - native_for_validator > min_difference.u128() { + src_delegations.push(Delegation::new( + &d.validator, + d.amount - native_for_validator, + &d.denom, + )); + } }, Ordering::Less => { - dst_delegations.push(Delegation::new(&d.validator, uluna_for_validator - d.amount)); + if validators_active.contains(&d.validator) + && native_for_validator - d.amount > min_difference.u128() + { + dst_delegations.push(Delegation::new( + &d.validator, + native_for_validator - d.amount, + &d.denom, + )); + } }, Ordering::Equal => (), } @@ -173,24 +199,27 @@ pub(crate) fn compute_redelegations_for_rebalancing( while !src_delegations.is_empty() && !dst_delegations.is_empty() { let src_delegation = src_delegations[0].clone(); let dst_delegation = dst_delegations[0].clone(); - let uluna_to_redelegate = cmp::min(src_delegation.amount, dst_delegation.amount); + let native_to_redelegate = cmp::min(src_delegation.amount, dst_delegation.amount); - if src_delegation.amount == uluna_to_redelegate { + if src_delegation.amount == native_to_redelegate { src_delegations.remove(0); } else { - src_delegations[0].amount -= uluna_to_redelegate; + src_delegations[0].amount -= native_to_redelegate; } - if dst_delegation.amount == uluna_to_redelegate { + if dst_delegation.amount == native_to_redelegate { dst_delegations.remove(0); } else { - dst_delegations[0].amount -= uluna_to_redelegate; + dst_delegations[0].amount -= native_to_redelegate; } - - new_redelegations.push( - Redelegation::new(&src_delegation.validator, &dst_delegation.validator, uluna_to_redelegate), - ); + new_redelegations.push(Redelegation::new( + &src_delegation.validator, + &dst_delegation.validator, + native_to_redelegate, + &src_delegation.denom, + )); } + // eprintln!("new redelegations ={:?}", new_redelegations); new_redelegations } @@ -199,22 +228,68 @@ pub(crate) fn compute_redelegations_for_rebalancing( // Batch logics //-------------------------------------------------------------------------------------------------- -/// If the received uluna amount after the unbonding period is less than expected, e.g. due to rounding -/// error or the validator(s) being slashed, then deduct the difference in amount evenly from each -/// unreconciled batch. +/// If the received native amount after the unbonding period is less than expected, e.g. due to +/// rounding error or the validator(s) being slashed, then deduct the difference in amount evenly +/// from each unreconciled batch. /// /// The idea of "reconciling" is based on Stader's implementation: /// https://github.com/stader-labs/stader-liquid-token/blob/v0.2.1/contracts/staking/src/contract.rs#L968-L1048 -pub(crate) fn reconcile_batches(batches: &mut [Batch], uluna_to_deduct: Uint128) { +pub(crate) fn reconcile_batches(batches: &mut [Batch], native_to_deduct: Uint128) { let batch_count = batches.len() as u128; - let uluna_per_batch = uluna_to_deduct.u128() / batch_count; - let remainder = uluna_to_deduct.u128() % batch_count; + let native_per_batch = native_to_deduct.u128() / batch_count; + let remainder = native_to_deduct.u128() % batch_count; + //let mut remaining_underflow = Uint128::zero(); + let mut underflows: HashMap = HashMap::default(); + // distribute the underflows uniformly accross non-underflowing batches for (i, batch) in batches.iter_mut().enumerate() { - let remainder_for_batch: u128 = if (i + 1) as u128 <= remainder { 1 } else { 0 }; - let uluna_for_batch = uluna_per_batch + remainder_for_batch; + let remainder_for_batch: u128 = u128::from((i + 1) as u128 <= remainder); + let native_for_batch = Uint128::new(native_per_batch + remainder_for_batch); + + if batch.amount_unclaimed < native_for_batch && batch_count > 1 { + // remaining_underflow += native_for_batch - batch.amount_unclaimed; + underflows.insert(i, native_for_batch - batch.amount_unclaimed); + } + batch.amount_unclaimed = batch.amount_unclaimed.saturating_sub(native_for_batch); - batch.uluna_unclaimed -= Uint128::new(uluna_for_batch); batch.reconciled = true; } + if !underflows.is_empty() { + let batch_count: u128 = batch_count - (underflows.len() as u128); + let to_deduct: Uint128 = underflows.iter().map(|v| v.1).sum(); + let native_per_batch = to_deduct.u128() / batch_count; + let remainder = to_deduct.u128() % batch_count; + let mut remaining_underflow = Uint128::zero(); + // the remaining underflow will be applied by oldest batch first. + for (i, batch) in batches.iter_mut().enumerate() { + if !batch.amount_unclaimed.is_zero() { + let remainder_for_batch: u128 = u128::from((i + 1) as u128 <= remainder); + let native_for_batch = Uint128::new(native_per_batch + remainder_for_batch); + if batch.amount_unclaimed < native_for_batch && batch_count > 1 { + remaining_underflow += native_for_batch - batch.amount_unclaimed; + } + batch.amount_unclaimed = batch.amount_unclaimed.saturating_sub(native_for_batch); + } + } + + if !remaining_underflow.is_zero() { + // the remaining underflow will be applied by oldest batch first. + for batch in batches.iter_mut() { + //.enumerate() { + if !batch.amount_unclaimed.is_zero() && !remaining_underflow.is_zero() { + if batch.amount_unclaimed >= remaining_underflow { + batch.amount_unclaimed -= remaining_underflow; + remaining_underflow = Uint128::zero() + } else { + remaining_underflow -= batch.amount_unclaimed; + batch.amount_unclaimed = Uint128::zero(); + } + } + } + + if !remaining_underflow.is_zero() { + // no way to reconcile right now, need to top up some funds. + } + } + } } diff --git a/contracts/hub/src/migrations.rs b/contracts/hub/src/migrations.rs new file mode 100644 index 00000000..6e8288fe --- /dev/null +++ b/contracts/hub/src/migrations.rs @@ -0,0 +1,96 @@ +use cosmwasm_std::{Addr, Order, QuerierWrapper, StdError, StdResult, Storage, Uint128}; +use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex}; +use pfc_steak::hub::Batch; +use serde::{Deserialize, Serialize}; + +use crate::{ + helpers::get_denom_balance, + state::{BATCH_KEY_V101, State}, + types::BooleanKey, +}; + +const BATCH_KEY_V100: &str = "previous_batches"; +const BATCH_KEY_RECONCILED_V100: &str = "previous_batches__reconciled"; + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct BatchV100 { + /// ID of this batch + pub id: u64, + /// Whether this batch has already been reconciled + pub reconciled: bool, + /// Total amount of shares remaining this batch. Each `usteak` burned = 1 share + pub total_shares: Uint128, + /// Amount of `denom` in this batch that have not been claimed + pub uluna_unclaimed: Uint128, + /// Estimated time when this batch will finish unbonding + pub est_unbond_end_time: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct ConfigV100 {} + +impl ConfigV100 { + pub fn upgrade_stores( + storage: &mut dyn Storage, + querier: &QuerierWrapper, + contract_addr: Addr, + ) -> StdResult { + if BATCH_KEY_V101 == BATCH_KEY_V100 { + Err(StdError::generic_err("STEAK: Migration Failed. Config keys are the same")) + } else { + let pb_indexes_v100 = PreviousBatchesIndexesV100 { + reconciled: MultiIndex::new( + |d: &BatchV100| d.reconciled.into(), + BATCH_KEY_V100, + BATCH_KEY_RECONCILED_V100, + ), + }; + + let old: IndexedMap<'_, u64, BatchV100, PreviousBatchesIndexesV100<'_>> = + IndexedMap::new(BATCH_KEY_V100, pb_indexes_v100); + let state = State::default(); + let denom = state.denom.load(storage)?; + state.prev_denom.save(storage, &get_denom_balance(querier, contract_addr, denom)?)?; + + let old_batches = old + .range(storage, None, None, Order::Ascending) + .map(|item| { + let (_, v) = item?; + Ok(v) + // Ok(v) + }) + .collect::>>()?; + + { + old_batches.into_iter().for_each(|v| { + { + let batch: Batch = Batch { + id: v.id, + reconciled: v.reconciled, + total_shares: v.total_shares, + amount_unclaimed: v.uluna_unclaimed, + est_unbond_end_time: v.est_unbond_end_time, + }; + state.previous_batches.save(storage, v.id, &batch).unwrap(); + } + // Ok(v) + }); + let validators = state.validators.load(storage)?; + state.validators_active.save(storage, &validators)?; + Ok(ConfigV100 {}) + } + } + } +} + +pub(crate) struct PreviousBatchesIndexesV100<'a> { + // pk goes to second tuple element + pub reconciled: MultiIndex<'a, BooleanKey, BatchV100, Vec>, +} + +impl IndexList for PreviousBatchesIndexesV100<'_> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.reconciled]; + Box::new(v.into_iter()) + } +} diff --git a/contracts/hub/src/queries.rs b/contracts/hub/src/queries.rs index 55f73de1..4ba95911 100644 --- a/contracts/hub/src/queries.rs +++ b/contracts/hub/src/queries.rs @@ -1,26 +1,49 @@ +use std::{collections::BTreeSet, iter::FromIterator}; + use cosmwasm_std::{Addr, Decimal, Deps, Env, Order, StdResult, Uint128}; use cw_storage_plus::{Bound, CwIntKey}; - -use steak::hub::{ +use pfc_steak::hub::{ Batch, ConfigResponse, PendingBatch, StateResponse, UnbondRequestsByBatchResponseItem, UnbondRequestsByUserResponseItem, }; -use crate::helpers::{query_cw20_total_supply, query_delegations}; -use crate::state::State; +use crate::{ + helpers::{query_all_delegations, query_cw20_total_supply}, + state::State, + types::BooleanKey, +}; const MAX_LIMIT: u32 = 30; const DEFAULT_LIMIT: u32 = 10; pub fn config(deps: Deps) -> StdResult { let state = State::default(); + let mut validators: BTreeSet = + BTreeSet::from_iter(state.validators.load(deps.storage)?); + let validators_active: BTreeSet = + BTreeSet::from_iter(state.validators_active.load(deps.storage)?); + + for v in validators_active.iter() { + validators.remove(v); + } + let validator_active_vec: Vec = Vec::from_iter(validators_active); + let paused_validators: Vec = Vec::from_iter(validators); + Ok(ConfigResponse { owner: state.owner.load(deps.storage)?.into(), new_owner: state.new_owner.may_load(deps.storage)?.map(|addr| addr.into()), steak_token: state.steak_token.load(deps.storage)?.into(), epoch_period: state.epoch_period.load(deps.storage)?, unbond_period: state.unbond_period.load(deps.storage)?, - validators: state.validators.load(deps.storage)?, + denom: state.denom.load(deps.storage)?, + fee_type: state.fee_account_type.load(deps.storage)?.to_string(), + fee_account: state.fee_account.load(deps.storage)?.to_string(), + fee_rate: state.fee_rate.load(deps.storage)?, + max_fee_rate: state.max_fee_rate.load(deps.storage)?, + validators: validator_active_vec, + paused_validators, + dust_collector: None, + token_factory: None, }) } @@ -30,19 +53,18 @@ pub fn state(deps: Deps, env: Env) -> StdResult { let steak_token = state.steak_token.load(deps.storage)?; let total_usteak = query_cw20_total_supply(&deps.querier, &steak_token)?; - let validators = state.validators.load(deps.storage)?; - let delegations = query_delegations(&deps.querier, &validators, &env.contract.address)?; - let total_uluna: u128 = delegations.iter().map(|d| d.amount).sum(); + let delegations = query_all_delegations(&deps.querier, &env.contract.address)?; + let total_native: u128 = delegations.iter().map(|d| d.amount).sum(); let exchange_rate = if total_usteak.is_zero() { Decimal::one() } else { - Decimal::from_ratio(total_uluna, total_usteak) + Decimal::from_ratio(total_native, total_usteak) }; Ok(StateResponse { total_usteak, - total_uluna: Uint128::new(total_uluna), + total_native: Uint128::new(total_native), exchange_rate, unlocked_coins: state.unlocked_coins.load(deps.storage)?, }) @@ -78,7 +100,21 @@ pub fn previous_batches( }) .collect() } +pub fn previous_batches_unreconciled(deps: Deps) -> StdResult> { + let state = State::default(); + state + .previous_batches + .idx + .reconciled + .prefix(BooleanKey::new(false)) + .range(deps.storage, None, None, Order::Ascending) + .map(|item| { + let (_, v) = item?; + Ok(v) + }) + .collect() +} pub fn unbond_requests_by_batch( deps: Deps, id: u64, diff --git a/contracts/hub/src/state.rs b/contracts/hub/src/state.rs index 8e251a55..9a04cd8e 100644 --- a/contracts/hub/src/state.rs +++ b/contracts/hub/src/state.rs @@ -1,15 +1,25 @@ -use cosmwasm_std::{Addr, Coin, StdError, StdResult, Storage}; +use cosmwasm_std::{Addr, Coin, Decimal, StdError, StdResult, Storage, Uint128}; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, MultiIndex}; - -use steak::hub::{Batch, PendingBatch, UnbondRequest}; +use pfc_steak::hub::{Batch, FeeType, PendingBatch, UnbondRequest}; use crate::types::BooleanKey; +pub(crate) const BATCH_KEY_V101: &str = "previous_batches_101"; +pub(crate) const BATCH_KEY_RECONCILED_V101: &str = "previous_batches__reconciled_101"; pub(crate) struct State<'a> { /// Account who can call certain privileged functions pub owner: Item<'a, Addr>, /// Pending ownership transfer, awaiting acceptance by the new owner pub new_owner: Item<'a, Addr>, + pub fee_account_type: Item<'a, FeeType>, + /// Account to send fees to + pub fee_account: Item<'a, Addr>, + /// Current fee rate + pub fee_rate: Item<'a, Decimal>, + /// Maximum fee rate + pub max_fee_rate: Item<'a, Decimal>, + /// denom to accept + pub denom: Item<'a, String>, /// Address of the Steak token pub steak_token: Item<'a, Addr>, /// How often the unbonding queue is to be executed @@ -18,14 +28,21 @@ pub(crate) struct State<'a> { pub unbond_period: Item<'a, u64>, /// Validators who will receive the delegations pub validators: Item<'a, Vec>, + /// Coins that can be reinvested pub unlocked_coins: Item<'a, Vec>, /// The current batch of unbonding requests queded to be executed pub pending_batch: Item<'a, PendingBatch>, + /// Previous batches that have started unbonding but not yet finished pub previous_batches: IndexedMap<'a, u64, Batch, PreviousBatchesIndexes<'a>>, /// Users' shares in unbonding batches pub unbond_requests: IndexedMap<'a, (u64, &'a Addr), UnbondRequest, UnbondRequestsIndexes<'a>>, + pub validators_active: Item<'a, Vec>, + /// coins in 'denom' held before reinvest was called. + pub prev_denom: Item<'a, Uint128>, + /// Dust Collector contract + pub dust_collector: Item<'a, Option>, } impl Default for State<'static> { @@ -33,8 +50,8 @@ impl Default for State<'static> { let pb_indexes = PreviousBatchesIndexes { reconciled: MultiIndex::new( |d: &Batch| d.reconciled.into(), - "previous_batches", - "previous_batches__reconciled", + BATCH_KEY_V101, + BATCH_KEY_RECONCILED_V101, ), }; let ubr_indexes = UnbondRequestsIndexes { @@ -47,19 +64,27 @@ impl Default for State<'static> { Self { owner: Item::new("owner"), new_owner: Item::new("new_owner"), + fee_account: Item::new("fee_account"), + fee_rate: Item::new("fee_rate"), + max_fee_rate: Item::new("max_fee_rate"), + denom: Item::new("denom"), steak_token: Item::new("steak_token"), epoch_period: Item::new("epoch_period"), unbond_period: Item::new("unbond_period"), validators: Item::new("validators"), unlocked_coins: Item::new("unlocked_coins"), pending_batch: Item::new("pending_batch"), - previous_batches: IndexedMap::new("previous_batches", pb_indexes), + previous_batches: IndexedMap::new(BATCH_KEY_V101, pb_indexes), unbond_requests: IndexedMap::new("unbond_requests", ubr_indexes), + validators_active: Item::new("validators_active"), + prev_denom: Item::new("prev_denom"), + fee_account_type: Item::new("fee_account_type"), + dust_collector: Item::new("dust_collector"), } } } -impl<'a> State<'a> { +impl State<'_> { pub fn assert_owner(&self, storage: &dyn Storage, sender: &Addr) -> StdResult<()> { let owner = self.owner.load(storage)?; if *sender == owner { @@ -75,7 +100,7 @@ pub(crate) struct PreviousBatchesIndexes<'a> { pub reconciled: MultiIndex<'a, BooleanKey, Batch, Vec>, } -impl<'a> IndexList for PreviousBatchesIndexes<'a> { +impl IndexList for PreviousBatchesIndexes<'_> { fn get_indexes(&'_ self) -> Box> + '_> { let v: Vec<&dyn Index> = vec![&self.reconciled]; Box::new(v.into_iter()) @@ -87,7 +112,7 @@ pub(crate) struct UnbondRequestsIndexes<'a> { pub user: MultiIndex<'a, String, UnbondRequest, Vec>, } -impl<'a> IndexList for UnbondRequestsIndexes<'a> { +impl IndexList for UnbondRequestsIndexes<'_> { fn get_indexes(&'_ self) -> Box> + '_> { let v: Vec<&dyn Index> = vec![&self.user]; Box::new(v.into_iter()) diff --git a/contracts/hub/src/testing/custom_querier.rs b/contracts/hub/src/testing/custom_querier.rs index b120b65b..f70715f0 100644 --- a/contracts/hub/src/testing/custom_querier.rs +++ b/contracts/hub/src/testing/custom_querier.rs @@ -1,17 +1,15 @@ use std::collections::HashMap; -use cosmwasm_std::testing::{BankQuerier, StakingQuerier, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - from_binary, from_slice, Addr, Coin, Empty, FullDelegation, Querier, QuerierResult, - QueryRequest, SystemError, WasmQuery, + Addr, Coin, Empty, FullDelegation, Querier, QuerierResult, QueryRequest, SystemError, + WasmQuery, from_json, + testing::{BankQuerier, MOCK_CONTRACT_ADDR, StakingQuerier}, }; use cw20::Cw20QueryMsg; +use super::{cw20_querier::Cw20Querier, helpers::err_unsupported_query}; use crate::types::Delegation; -use super::cw20_querier::Cw20Querier; -use super::helpers::err_unsupported_query; - #[derive(Default)] pub(super) struct CustomQuerier { pub cw20_querier: Cw20Querier, @@ -21,14 +19,14 @@ pub(super) struct CustomQuerier { impl Querier for CustomQuerier { fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - let request: QueryRequest<_> = match from_slice(bin_request) { + let request: QueryRequest<_> = match from_json(bin_request) { Ok(v) => v, Err(e) => { return Err(SystemError::InvalidRequest { error: format!("Parsing query request: {}", e), request: bin_request.into(), }) - .into() + .into(); }, }; self.handle_query(&request) @@ -51,9 +49,7 @@ impl CustomQuerier { } pub fn set_cw20_total_supply(&mut self, token: &str, total_supply: u128) { - self.cw20_querier - .total_supplies - .insert(token.to_string(), total_supply); + self.cw20_querier.total_supplies.insert(token.to_string(), total_supply); } pub fn set_bank_balances(&mut self, balances: &[Coin]) { @@ -81,8 +77,8 @@ impl CustomQuerier { contract_addr, msg, }) => { - if let Ok(query) = from_binary::(msg) { - return self.cw20_querier.handle_query(&contract_addr, query); + if let Ok(query) = from_json::(msg) { + return self.cw20_querier.handle_query(contract_addr, query); } err_unsupported_query(msg) diff --git a/contracts/hub/src/testing/cw20_querier.rs b/contracts/hub/src/testing/cw20_querier.rs index b80115a6..4a3be011 100644 --- a/contracts/hub/src/testing/cw20_querier.rs +++ b/contracts/hub/src/testing/cw20_querier.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use cosmwasm_std::{to_binary, QuerierResult, SystemError, Uint128}; +use cosmwasm_std::{QuerierResult, SystemError, Uint128, to_json_binary}; use cw20::{BalanceResponse, Cw20QueryMsg, TokenInfoResponse}; use super::helpers::err_unsupported_query; @@ -26,7 +26,7 @@ impl Cw20Querier { }) .unwrap(); - Ok(to_binary(&TokenInfoResponse { + Ok(to_json_binary(&TokenInfoResponse { name: "".to_string(), symbol: "".to_string(), decimals: 0, @@ -51,12 +51,15 @@ impl Cw20Querier { let balance = contract_balances .get(address) .ok_or_else(|| SystemError::InvalidRequest { - error: format!("[mock] balance not set for cw20 `{}` and user `{}`", contract_addr, address), + error: format!( + "[mock] balance not set for cw20 `{}` and user `{}`", + contract_addr, address + ), request: Default::default(), }) .unwrap(); - Ok(to_binary(&BalanceResponse { + Ok(to_json_binary(&BalanceResponse { balance: Uint128::new(*balance), }) .into()) diff --git a/contracts/hub/src/testing/helpers.rs b/contracts/hub/src/testing/helpers.rs index 3e15439f..fd1aecac 100644 --- a/contracts/hub/src/testing/helpers.rs +++ b/contracts/hub/src/testing/helpers.rs @@ -1,15 +1,13 @@ -use cosmwasm_std::testing::{mock_env, MockApi, MockStorage, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - from_binary, Addr, BlockInfo, ContractInfo, Deps, Env, OwnedDeps, QuerierResult, SystemError, - SystemResult, Timestamp, + Addr, BlockInfo, ContractInfo, Deps, Env, OwnedDeps, QuerierResult, SystemError, SystemResult, + Timestamp, from_json, + testing::{MOCK_CONTRACT_ADDR, MockApi, MockStorage, mock_env}, }; +use pfc_steak::hub::QueryMsg; use serde::de::DeserializeOwned; -use steak::hub::QueryMsg; - -use crate::contract::query; - use super::custom_querier::CustomQuerier; +use crate::contract::query; pub(super) fn err_unsupported_query(request: T) -> QuerierResult { SystemResult::Err(SystemError::InvalidRequest { @@ -42,5 +40,5 @@ pub(super) fn mock_env_at_timestamp(timestamp: u64) -> Env { } pub(super) fn query_helper(deps: Deps, msg: QueryMsg) -> T { - from_binary(&query(deps, mock_env(), msg).unwrap()).unwrap() + from_json(query(deps, mock_env(), msg).unwrap()).unwrap() } diff --git a/contracts/hub/src/testing/tests.rs b/contracts/hub/src/testing/tests.rs index dc1824a6..e68f927f 100644 --- a/contracts/hub/src/testing/tests.rs +++ b/contracts/hub/src/testing/tests.rs @@ -1,29 +1,35 @@ use std::str::FromStr; -use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockStorage, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - to_binary, Addr, BankMsg, Coin, CosmosMsg, Decimal, DistributionMsg, Event, Order, OwnedDeps, - Reply, ReplyOn, StdError, SubMsg, SubMsgResponse, Uint128, WasmMsg, + Addr, BankMsg, Coin, CosmosMsg, Decimal, DistributionMsg, Event, Order, OwnedDeps, Reply, + ReplyOn, StdError, SubMsg, SubMsgResponse, Uint128, WasmMsg, + testing::{MOCK_CONTRACT_ADDR, MockApi, MockStorage, mock_env, mock_info}, + to_json_binary, }; use cw20::{Cw20ExecuteMsg, MinterResponse}; use cw20_base::msg::InstantiateMsg as Cw20InstantiateMsg; - -use steak::hub::{ +use pfc_steak::hub::{ Batch, CallbackMsg, ConfigResponse, ExecuteMsg, InstantiateMsg, PendingBatch, QueryMsg, ReceiveMsg, StateResponse, UnbondRequest, UnbondRequestsByBatchResponseItem, UnbondRequestsByUserResponseItem, }; -use crate::contract::{execute, instantiate, reply}; -use crate::helpers::{parse_coin, parse_received_fund}; -use crate::math::{ - compute_redelegations_for_rebalancing, compute_redelegations_for_removal, compute_undelegations, +use super::{ + custom_querier::CustomQuerier, + helpers::{mock_dependencies, mock_env_at_timestamp, query_helper}, +}; +use crate::{ + contract::{ + REPLY_INSTANTIATE_TOKEN, REPLY_REGISTER_RECEIVED_COINS, execute, instantiate, reply, + }, + helpers::{parse_coin, parse_received_fund}, + math::{ + compute_redelegations_for_rebalancing, compute_redelegations_for_removal, + compute_undelegations, + }, + state::State, + types::{Coins, Delegation, Redelegation, Undelegation}, }; -use crate::state::State; -use crate::types::{Coins, Delegation, Redelegation, Undelegation}; - -use super::custom_querier::CustomQuerier; -use super::helpers::{mock_dependencies, mock_env_at_timestamp, query_helper}; //-------------------------------------------------------------------------------------------------- // Test setup @@ -41,10 +47,18 @@ fn setup_test() -> OwnedDeps { owner: "larry".to_string(), name: "Steak Token".to_string(), symbol: "STEAK".to_string(), + denom: "uxyz".to_string(), + fee_account_type: "Wallet".to_string(), + fee_account: "the_fee_man".to_string(), + fee_amount: Decimal::from_ratio(10_u128, 100_u128), //10% + max_fee_amount: Decimal::from_ratio(20_u128, 100_u128), //20% decimals: 6, epoch_period: 259200, // 3 * 24 * 60 * 60 = 3 days unbond_period: 1814400, // 21 * 24 * 60 * 60 = 21 days validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string()], + label: None, + marketing: None, + dust_collector: None, }, ) .unwrap(); @@ -56,14 +70,14 @@ fn setup_test() -> OwnedDeps { CosmosMsg::Wasm(WasmMsg::Instantiate { admin: Some("larry".to_string()), code_id: 69420, - msg: to_binary(&Cw20InstantiateMsg { + msg: to_json_binary(&Cw20InstantiateMsg { name: "Steak Token".to_string(), symbol: "STEAK".to_string(), decimals: 6, initial_balances: vec![], mint: Some(MinterResponse { minter: MOCK_CONTRACT_ADDR.to_string(), - cap: None + cap: None, }), marketing: None, }) @@ -71,7 +85,7 @@ fn setup_test() -> OwnedDeps { funds: vec![], label: "steak_token".to_string(), }), - 1 + REPLY_INSTANTIATE_TOKEN, ) ); @@ -79,19 +93,88 @@ fn setup_test() -> OwnedDeps { .add_attribute("code_id", "69420") .add_attribute("_contract_address", "steak_token"); - let res = reply( + let res = reply(deps.as_mut(), mock_env_at_timestamp(10000), Reply { + id: REPLY_INSTANTIATE_TOKEN, + result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { + events: vec![event], + data: None, + }), + }) + .unwrap(); + + assert_eq!(res.messages.len(), 0); + + deps.querier.set_cw20_total_supply("steak_token", 0); + deps +} + +fn setup_test_fee_split() -> OwnedDeps { + let mut deps = mock_dependencies(); + + let res = instantiate( deps.as_mut(), mock_env_at_timestamp(10000), - Reply { - id: 1, - result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { - events: vec![event], - data: None, - }), + mock_info("deployer", &[]), + InstantiateMsg { + cw20_code_id: 69420, + owner: "larry".to_string(), + name: "Steak Token".to_string(), + symbol: "STEAK".to_string(), + denom: "uxyz".to_string(), + fee_account_type: "FeeSplit".to_string(), + fee_account: "fee_split_contract".to_string(), + fee_amount: Decimal::from_ratio(10_u128, 100_u128), //10% + max_fee_amount: Decimal::from_ratio(20_u128, 100_u128), //20% + decimals: 6, + epoch_period: 259200, // 3 * 24 * 60 * 60 = 3 days + unbond_period: 1814400, // 21 * 24 * 60 * 60 = 21 days + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string()], + label: None, + marketing: None, + dust_collector: None, }, ) .unwrap(); + assert_eq!(res.messages.len(), 1); + assert_eq!( + res.messages[0], + SubMsg::reply_on_success( + CosmosMsg::Wasm(WasmMsg::Instantiate { + admin: Some("larry".to_string()), + code_id: 69420, + msg: to_json_binary(&Cw20InstantiateMsg { + name: "Steak Token".to_string(), + symbol: "STEAK".to_string(), + decimals: 6, + initial_balances: vec![], + mint: Some(MinterResponse { + minter: MOCK_CONTRACT_ADDR.to_string(), + cap: None, + }), + marketing: None, + }) + .unwrap(), + funds: vec![], + label: "steak_token".to_string(), + }), + REPLY_INSTANTIATE_TOKEN, + ) + ); + + let event = Event::new("instantiate") + .add_attribute("code_id", "69420") + .add_attribute("_contract_address", "steak_token"); + + let res = reply(deps.as_mut(), mock_env_at_timestamp(10000), Reply { + id: REPLY_INSTANTIATE_TOKEN, + result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { + events: vec![event], + data: None, + }), + }) + .unwrap(); + assert_eq!(res.messages.len(), 0); deps.querier.set_cw20_total_supply("steak_token", 0); @@ -107,38 +190,56 @@ fn proper_instantiation() { let deps = setup_test(); let res: ConfigResponse = query_helper(deps.as_ref(), QueryMsg::Config {}); - assert_eq!( - res, - ConfigResponse { - owner: "larry".to_string(), - new_owner: None, - steak_token: "steak_token".to_string(), - epoch_period: 259200, - unbond_period: 1814400, - validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string()] - } - ); + assert_eq!(res, ConfigResponse { + owner: "larry".to_string(), + new_owner: None, + steak_token: "steak_token".to_string(), + epoch_period: 259200, + unbond_period: 1814400, + denom: "uxyz".to_string(), + fee_type: "Wallet".to_string(), + fee_account: "the_fee_man".to_string(), + fee_rate: Decimal::from_ratio(10_u128, 100_u128), + max_fee_rate: Decimal::from_ratio(20_u128, 100_u128), + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string(),], + paused_validators: vec![], + dust_collector: None, + token_factory: None + }); let res: StateResponse = query_helper(deps.as_ref(), QueryMsg::State {}); - assert_eq!( - res, - StateResponse { - total_usteak: Uint128::zero(), - total_uluna: Uint128::zero(), - exchange_rate: Decimal::one(), - unlocked_coins: vec![], - }, - ); + assert_eq!(res, StateResponse { + total_usteak: Uint128::zero(), + total_native: Uint128::zero(), + exchange_rate: Decimal::one(), + unlocked_coins: vec![], + },); let res: PendingBatch = query_helper(deps.as_ref(), QueryMsg::PendingBatch {}); - assert_eq!( - res, - PendingBatch { - id: 1, - usteak_to_burn: Uint128::zero(), - est_unbond_start_time: 269200, // 10,000 + 259,200 - }, - ); + assert_eq!(res, PendingBatch { + id: 1, + usteak_to_burn: Uint128::zero(), + est_unbond_start_time: 269200, // 10,000 + 259,200 + },); + let deps_fee_split = setup_test_fee_split(); + + let res_fee_split: ConfigResponse = query_helper(deps_fee_split.as_ref(), QueryMsg::Config {}); + assert_eq!(res_fee_split, ConfigResponse { + owner: "larry".to_string(), + new_owner: None, + steak_token: "steak_token".to_string(), + epoch_period: 259200, + unbond_period: 1814400, + denom: "uxyz".to_string(), + fee_type: "FeeSplit".to_string(), + fee_account: "fee_split_contract".to_string(), + fee_rate: Decimal::from_ratio(10_u128, 100_u128), + max_fee_rate: Decimal::from_ratio(20_u128, 100_u128), + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string(),], + paused_validators: vec![], + dust_collector: None, + token_factory: None + }); } #[test] @@ -150,42 +251,63 @@ fn bonding() { let res = execute( deps.as_mut(), mock_env(), - mock_info("user_1", &[Coin::new(1000000, "uluna")]), + mock_info("user_1", &[Coin::new(1_000_000, "uxyz")]), ExecuteMsg::Bond { receiver: None, + exec_msg: None, }, ) .unwrap(); - assert_eq!(res.messages.len(), 2); + // 3 messages. (switched to 3 so we can 'send' instead of 'transfer' minted tokens, so contract + // will know about it 1 - delegate + // 2 - mint token (to ourselves) + // 3 - send/transfer it + assert_eq!(res.messages.len(), 3); assert_eq!( res.messages[0], - SubMsg::reply_on_success(Delegation::new("alice", 1000000).to_cosmos_msg(), 2) - ); - assert_eq!( - res.messages[1], - SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: "steak_token".to_string(), - msg: to_binary(&Cw20ExecuteMsg::Mint { - recipient: "user_1".to_string(), - amount: Uint128::new(1000000) - }) - .unwrap(), - funds: vec![] - }), - gas_limit: None, - reply_on: ReplyOn::Never, - } + SubMsg::reply_on_success( + Delegation::new("alice", 1_000_000, "uxyz").to_cosmos_msg(), + REPLY_REGISTER_RECEIVED_COINS + ) ); + assert_eq!(res.messages[1], SubMsg { + id: 0, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: "steak_token".to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Mint { + recipient: "cosmos2contract".to_string(), + amount: Uint128::new(1_000_000), + }) + .unwrap(), + funds: vec![], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + assert_eq!(res.messages[2], SubMsg { + id: 0, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: "steak_token".to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Transfer { + recipient: "user_1".to_string(), + amount: Uint128::new(1000000), + }) + .unwrap(), + funds: vec![], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); // Bond when there are existing delegations, and Luna:Steak exchange rate is >1 - // Previously user 1 delegated 1,000,000 uluna. We assume we have accumulated 2.5% yield at 1025000 staked + // Previously user 1 delegated 1,000,000 uluna. We assume we have accumulated 2.5% yield at + // 1025000 staked deps.querier.set_staking_delegations(&[ - Delegation::new("alice", 341667), - Delegation::new("bob", 341667), - Delegation::new("charlie", 341666), + Delegation::new("alice", 341667, "uxyz"), + Delegation::new("bob", 341667, "uxyz"), + Delegation::new("charlie", 341666, "uxyz"), ]); deps.querier.set_cw20_total_supply("steak_token", 1000000); @@ -193,54 +315,110 @@ fn bonding() { let res = execute( deps.as_mut(), mock_env(), - mock_info("user_2", &[Coin::new(12345, "uluna")]), + mock_info("user_2", &[Coin::new(12345, "uxyz")]), ExecuteMsg::Bond { receiver: Some("user_3".to_string()), + exec_msg: None, }, ) .unwrap(); - assert_eq!(res.messages.len(), 2); + assert_eq!(res.messages.len(), 3); assert_eq!( res.messages[0], - SubMsg::reply_on_success(Delegation::new("charlie", 12345).to_cosmos_msg(), 2) - ); - assert_eq!( - res.messages[1], - SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: "steak_token".to_string(), - msg: to_binary(&Cw20ExecuteMsg::Mint { - recipient: "user_3".to_string(), - amount: Uint128::new(12043) - }) - .unwrap(), - funds: vec![] - }), - gas_limit: None, - reply_on: ReplyOn::Never - } + SubMsg::reply_on_success( + Delegation::new("charlie", 12345, "uxyz").to_cosmos_msg(), + REPLY_REGISTER_RECEIVED_COINS + ) ); + assert_eq!(res.messages[1], SubMsg { + id: 0, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: "steak_token".to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Mint { + recipient: "cosmos2contract".to_string(), + amount: Uint128::new(12043), + }) + .unwrap(), + funds: vec![], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + assert_eq!(res.messages[2], SubMsg { + id: 0, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: "steak_token".to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Transfer { + recipient: "user_3".to_string(), + amount: Uint128::new(12043), + }) + .unwrap(), + funds: vec![], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); // Check the state after bonding deps.querier.set_staking_delegations(&[ - Delegation::new("alice", 341667), - Delegation::new("bob", 341667), - Delegation::new("charlie", 354011), + Delegation::new("alice", 341667, "uxyz"), + Delegation::new("bob", 341667, "uxyz"), + Delegation::new("charlie", 354011, "uxyz"), ]); deps.querier.set_cw20_total_supply("steak_token", 1012043); let res: StateResponse = query_helper(deps.as_ref(), QueryMsg::State {}); + assert_eq!(res, StateResponse { + total_usteak: Uint128::new(1012043), + total_native: Uint128::new(1037345), + exchange_rate: Decimal::from_ratio(1037345u128, 1012043u128), + unlocked_coins: vec![], + }); +} +#[test] +fn bond_ex() { + let mut deps = setup_test(); + + // Bond when no delegation has been made + // In this case, the full deposit simply goes to the first validator + let res = execute( + deps.as_mut(), + mock_env(), + mock_info("user_1", &[Coin::new(1_000_000, "uxyz")]), + ExecuteMsg::BondEx { + receiver: None, + }, + ) + .unwrap(); + + // 3 messages. (switched to 3 so we can 'send' instead of 'transfer' minted tokens, so contract + // will know about it 1 - delegate + // 2 - mint token (to ourselves) + // 3 - send/transfer it + assert_eq!(res.messages.len(), 2); assert_eq!( - res, - StateResponse { - total_usteak: Uint128::new(1012043), - total_uluna: Uint128::new(1037345), - exchange_rate: Decimal::from_ratio(1037345u128, 1012043u128), - unlocked_coins: vec![], - } + res.messages[0], + SubMsg::reply_on_success( + Delegation::new("alice", 1_000_000, "uxyz").to_cosmos_msg(), + REPLY_REGISTER_RECEIVED_COINS + ) ); + assert_eq!(res.messages[1], SubMsg { + id: 0, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: "steak_token".to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Mint { + recipient: "user_1".to_string(), + amount: Uint128::new(1_000_000), + }) + .unwrap(), + funds: vec![], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); } #[test] @@ -249,19 +427,14 @@ fn harvesting() { // Assume users have bonded a total of 1,000,000 uluna and minted the same amount of usteak deps.querier.set_staking_delegations(&[ - Delegation::new("alice", 341667), - Delegation::new("bob", 341667), - Delegation::new("charlie", 341666), + Delegation::new("alice", 341667, "uxyz"), + Delegation::new("bob", 341667, "uxyz"), + Delegation::new("charlie", 341666, "uxyz"), ]); deps.querier.set_cw20_total_supply("steak_token", 1000000); - let res = execute( - deps.as_mut(), - mock_env(), - mock_info("worker", &[]), - ExecuteMsg::Harvest {} - ) - .unwrap(); + let res = execute(deps.as_mut(), mock_env(), mock_info("worker", &[]), ExecuteMsg::Harvest {}) + .unwrap(); assert_eq!(res.messages.len(), 4); assert_eq!( @@ -270,7 +443,7 @@ fn harvesting() { CosmosMsg::Distribution(DistributionMsg::WithdrawDelegatorReward { validator: "alice".to_string(), }), - 2, + REPLY_REGISTER_RECEIVED_COINS, ) ); assert_eq!( @@ -279,7 +452,7 @@ fn harvesting() { CosmosMsg::Distribution(DistributionMsg::WithdrawDelegatorReward { validator: "bob".to_string(), }), - 2, + REPLY_REGISTER_RECEIVED_COINS, ) ); assert_eq!( @@ -288,22 +461,19 @@ fn harvesting() { CosmosMsg::Distribution(DistributionMsg::WithdrawDelegatorReward { validator: "charlie".to_string(), }), - 2, + REPLY_REGISTER_RECEIVED_COINS, ) ); - assert_eq!( - res.messages[3], - SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: MOCK_CONTRACT_ADDR.to_string(), - msg: to_binary(&ExecuteMsg::Callback(CallbackMsg::Reinvest {})).unwrap(), - funds: vec![] - }), - gas_limit: None, - reply_on: ReplyOn::Never - } - ); + assert_eq!(res.messages[3], SubMsg { + id: 0, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: MOCK_CONTRACT_ADDR.to_string(), + msg: to_json_binary(&ExecuteMsg::Callback(CallbackMsg::Reinvest {})).unwrap(), + funds: vec![], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); } #[test] @@ -311,58 +481,118 @@ fn registering_unlocked_coins() { let mut deps = setup_test(); let state = State::default(); - // After withdrawing staking rewards, we parse the `coin_received` event to find the received amounts + // After withdrawing staking rewards, we parse the `coin_received` event to find the received + // amounts let event = Event::new("coin_received") .add_attribute("receiver", MOCK_CONTRACT_ADDR.to_string()) - .add_attribute("amount", "123ukrw,234uluna,345uusd,69420ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B"); + .add_attribute( + "amount", + "123ukrw,234uxyz,345uusd,69420ibc/\ + 0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ); + + reply(deps.as_mut(), mock_env(), Reply { + id: 2, + result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { + events: vec![event], + data: None, + }), + }) + .unwrap(); + + // Unlocked coins in contract state should have been updated + let unlocked_coins = state.unlocked_coins.load(deps.as_ref().storage).unwrap(); + assert_eq!(unlocked_coins, vec![ + Coin::new(123, "ukrw"), + Coin::new(234, "uxyz"), + Coin::new(345, "uusd"), + Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B",), + ]); +} + +#[test] +fn reinvesting() { + let mut deps = setup_test(); + let state = State::default(); + + deps.querier.set_staking_delegations(&[ + Delegation::new("alice", 333334, "uxyz"), + Delegation::new("bob", 333333, "uxyz"), + Delegation::new("charlie", 333333, "uxyz"), + ]); + state.prev_denom.save(deps.as_mut().storage, &Uint128::zero()).unwrap(); + deps.querier.set_bank_balances(&[Coin::new(234u128, "uxyz")]); + + // After the swaps, `unlocked_coins` should contain only uxyz and unknown denoms + state + .unlocked_coins + .save(deps.as_mut().storage, &vec![ + Coin::new(234, "uxyz"), + Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ), + ]) + .unwrap(); - reply( + // Bob has the smallest amount of delegations, so all proceeds go to him + let res = execute( deps.as_mut(), mock_env(), - Reply { - id: 2, - result: cosmwasm_std::SubMsgResult::Ok(SubMsgResponse { - events: vec![event], - data: None, - }), - }, + mock_info(MOCK_CONTRACT_ADDR, &[]), + ExecuteMsg::Callback(CallbackMsg::Reinvest {}), ) .unwrap(); - // Unlocked coins in contract state should have been updated + assert_eq!(res.messages.len(), 2); + assert_eq!(res.messages[0], SubMsg { + id: 0, + msg: Delegation::new("bob", 234 - 23, "uxyz").to_cosmos_msg(), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + let send_msg = BankMsg::Send { + to_address: "the_fee_man".into(), + amount: vec![Coin::new(23u128, "uxyz")], + }; + assert_eq!(res.messages[1], SubMsg { + id: 0, + msg: CosmosMsg::Bank(send_msg), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + + // Storage should have been updated let unlocked_coins = state.unlocked_coins.load(deps.as_ref().storage).unwrap(); - assert_eq!( - unlocked_coins, - vec![ - Coin::new(123, "ukrw"), - Coin::new(234, "uluna"), - Coin::new(345, "uusd"), - Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B"), - ] - ); + assert_eq!(unlocked_coins, vec![Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + )],); } #[test] -fn reinvesting() { - let mut deps = setup_test(); +fn reinvesting_fee_split() { + let mut deps = setup_test_fee_split(); let state = State::default(); deps.querier.set_staking_delegations(&[ - Delegation::new("alice", 333334), - Delegation::new("bob", 333333), - Delegation::new("charlie", 333333), + Delegation::new("alice", 333334, "uxyz"), + Delegation::new("bob", 333333, "uxyz"), + Delegation::new("charlie", 333333, "uxyz"), ]); + state.prev_denom.save(deps.as_mut().storage, &Uint128::zero()).unwrap(); + deps.querier.set_bank_balances(&[Coin::new(234u128, "uxyz")]); - // After the swaps, `unlocked_coins` should contain only uluna and unknown denoms + // After the swaps, `unlocked_coins` should contain only uxyz and unknown denoms state .unlocked_coins - .save( - deps.as_mut().storage, - &vec![ - Coin::new(234, "uluna"), - Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B"), - ], - ) + .save(deps.as_mut().storage, &vec![ + Coin::new(234, "uxyz"), + Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ), + ]) .unwrap(); // Bob has the smallest amount of delegations, so all proceeds go to him @@ -374,23 +604,32 @@ fn reinvesting() { ) .unwrap(); - assert_eq!(res.messages.len(), 1); - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - msg: Delegation::new("bob", 234).to_cosmos_msg(), - gas_limit: None, - reply_on: ReplyOn::Never - } - ); + assert_eq!(res.messages.len(), 2); + assert_eq!(res.messages[0], SubMsg { + id: 0, + msg: Delegation::new("bob", 234 - 23, "uxyz").to_cosmos_msg(), + gas_limit: None, + reply_on: ReplyOn::Never, + }); + let send_msg = pfc_fee_split::fee_split_msg::ExecuteMsg::Deposit { + flush: false, + }; + + assert_eq!(res.messages[1], SubMsg { + id: 0, + msg: send_msg + .into_cosmos_msg("fee_split_contract", vec![Coin::new(23u128, "uxyz")]) + .unwrap(), + gas_limit: None, + reply_on: ReplyOn::Never, + }); // Storage should have been updated let unlocked_coins = state.unlocked_coins.load(deps.as_ref().storage).unwrap(); - assert_eq!( - unlocked_coins, - vec![Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B")], - ); + assert_eq!(unlocked_coins, vec![Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + )],); } #[test] @@ -406,7 +645,7 @@ fn queuing_unbond() { ExecuteMsg::Receive(cw20::Cw20ReceiveMsg { sender: "hacker".to_string(), amount: Uint128::new(69420), - msg: to_binary(&ReceiveMsg::QueueUnbond { + msg: to_json_binary(&ReceiveMsg::QueueUnbond { receiver: None, }) .unwrap(), @@ -425,7 +664,7 @@ fn queuing_unbond() { ExecuteMsg::Receive(cw20::Cw20ReceiveMsg { sender: "user_1".to_string(), amount: Uint128::new(23456), - msg: to_binary(&ReceiveMsg::QueueUnbond { + msg: to_json_binary(&ReceiveMsg::QueueUnbond { receiver: None, }) .unwrap(), @@ -444,7 +683,7 @@ fn queuing_unbond() { ExecuteMsg::Receive(cw20::Cw20ReceiveMsg { sender: "user_2".to_string(), amount: Uint128::new(69420), - msg: to_binary(&ReceiveMsg::QueueUnbond { + msg: to_json_binary(&ReceiveMsg::QueueUnbond { receiver: Some("user_3".to_string()), }) .unwrap(), @@ -453,57 +692,45 @@ fn queuing_unbond() { .unwrap(); assert_eq!(res.messages.len(), 1); - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: MOCK_CONTRACT_ADDR.to_string(), - msg: to_binary(&ExecuteMsg::SubmitBatch {}).unwrap(), - funds: vec![] - }), - gas_limit: None, - reply_on: ReplyOn::Never - } - ); + assert_eq!(res.messages[0], SubMsg { + id: 0, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: MOCK_CONTRACT_ADDR.to_string(), + msg: to_json_binary(&ExecuteMsg::SubmitBatch {}).unwrap(), + funds: vec![], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); // The users' unbonding requests should have been saved let ubr1 = state .unbond_requests - .load(deps.as_ref().storage, (1u64.into(), &Addr::unchecked("user_1"))) + .load(deps.as_ref().storage, (1u64, &Addr::unchecked("user_1"))) .unwrap(); let ubr2 = state .unbond_requests - .load(deps.as_ref().storage, (1u64.into(), &Addr::unchecked("user_3"))) + .load(deps.as_ref().storage, (1u64, &Addr::unchecked("user_3"))) .unwrap(); - assert_eq!( - ubr1, - UnbondRequest { - id: 1, - user: Addr::unchecked("user_1"), - shares: Uint128::new(23456) - } - ); - assert_eq!( - ubr2, - UnbondRequest { - id: 1, - user: Addr::unchecked("user_3"), - shares: Uint128::new(69420) - } - ); + assert_eq!(ubr1, UnbondRequest { + id: 1, + user: Addr::unchecked("user_1"), + shares: Uint128::new(23456), + }); + assert_eq!(ubr2, UnbondRequest { + id: 1, + user: Addr::unchecked("user_3"), + shares: Uint128::new(69420), + }); // Pending batch should have been updated let pending_batch = state.pending_batch.load(deps.as_ref().storage).unwrap(); - assert_eq!( - pending_batch, - PendingBatch { - id: 1, - usteak_to_burn: Uint128::new(92876), // 23,456 + 69,420 - est_unbond_start_time: 269200 - } - ); + assert_eq!(pending_batch, PendingBatch { + id: 1, + usteak_to_burn: Uint128::new(92876), // 23,456 + 69,420 + est_unbond_start_time: 269200, + }); } #[test] @@ -515,9 +742,9 @@ fn submitting_batch() { // usteak supply: 1,012,043 // uluna per ustake: 1.025 deps.querier.set_staking_delegations(&[ - Delegation::new("alice", 345782), - Delegation::new("bob", 345782), - Delegation::new("charlie", 345781), + Delegation::new("alice", 345782, "uxyz"), + Delegation::new("bob", 345782, "uxyz"), + Delegation::new("charlie", 345781, "uxyz"), ]); deps.querier.set_cw20_total_supply("steak_token", 1012043); @@ -540,7 +767,7 @@ fn submitting_batch() { .unbond_requests .save( deps.as_mut().storage, - (unbond_request.id.into(), &Addr::unchecked(unbond_request.user.clone())), + (unbond_request.id, &Addr::unchecked(unbond_request.user.clone())), unbond_request, ) .unwrap(); @@ -548,14 +775,11 @@ fn submitting_batch() { state .pending_batch - .save( - deps.as_mut().storage, - &PendingBatch { - id: 1, - usteak_to_burn: Uint128::new(92876), // 23,456 + 69,420 - est_unbond_start_time: 269200, - }, - ) + .save(deps.as_mut().storage, &PendingBatch { + id: 1, + usteak_to_burn: Uint128::new(92876), // 23,456 + 69,420 + est_unbond_start_time: 269200, + }) .unwrap(); // Anyone can invoke `submit_batch`. Here we continue from the previous test and assume it is @@ -580,56 +804,56 @@ fn submitting_batch() { assert_eq!(res.messages.len(), 4); assert_eq!( res.messages[0], - SubMsg::reply_on_success(Undelegation::new("alice", 31732).to_cosmos_msg(), 2) + SubMsg::reply_on_success( + Undelegation::new("alice", 31732, "uxyz").to_cosmos_msg(), + REPLY_REGISTER_RECEIVED_COINS + ) ); assert_eq!( res.messages[1], - SubMsg::reply_on_success(Undelegation::new("bob", 31733).to_cosmos_msg(), 2) + SubMsg::reply_on_success( + Undelegation::new("bob", 31733, "uxyz").to_cosmos_msg(), + REPLY_REGISTER_RECEIVED_COINS + ) ); assert_eq!( res.messages[2], - SubMsg::reply_on_success(Undelegation::new("charlie", 31732).to_cosmos_msg(), 2) - ); - assert_eq!( - res.messages[3], - SubMsg { - id: 0, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: "steak_token".to_string(), - msg: to_binary(&Cw20ExecuteMsg::Burn { - amount: Uint128::new(92876) - }) - .unwrap(), - funds: vec![] - }), - gas_limit: None, - reply_on: ReplyOn::Never - } + SubMsg::reply_on_success( + Undelegation::new("charlie", 31732, "uxyz").to_cosmos_msg(), + REPLY_REGISTER_RECEIVED_COINS, + ) ); + assert_eq!(res.messages[3], SubMsg { + id: 0, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: "steak_token".to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Burn { + amount: Uint128::new(92876) + }) + .unwrap(), + funds: vec![], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); // A new pending batch should have been created let pending_batch = state.pending_batch.load(deps.as_ref().storage).unwrap(); - assert_eq!( - pending_batch, - PendingBatch { - id: 2, - usteak_to_burn: Uint128::zero(), - est_unbond_start_time: 528401 // 269,201 + 259,200 - } - ); + assert_eq!(pending_batch, PendingBatch { + id: 2, + usteak_to_burn: Uint128::zero(), + est_unbond_start_time: 528401, // 269,201 + 259,200 + }); // Previous batch should have been updated - let previous_batch = state.previous_batches.load(deps.as_ref().storage, 1u64.into()).unwrap(); - assert_eq!( - previous_batch, - Batch { - id: 1, - reconciled: false, - total_shares: Uint128::new(92876), - uluna_unclaimed: Uint128::new(95197), - est_unbond_end_time: 2083601 // 269,201 + 1,814,400 - } - ); + let previous_batch = state.previous_batches.load(deps.as_ref().storage, 1u64).unwrap(); + assert_eq!(previous_batch, Batch { + id: 1, + reconciled: false, + total_shares: Uint128::new(92876), + amount_unclaimed: Uint128::new(95197), + est_unbond_end_time: 2083601, // 269,201 + 1,814,400 + }); } #[test] @@ -642,54 +866,54 @@ fn reconciling() { id: 1, reconciled: true, total_shares: Uint128::new(92876), - uluna_unclaimed: Uint128::new(95197), // 1.025 Luna per Steak + amount_unclaimed: Uint128::new(95197), // 1.025 Luna per Steak est_unbond_end_time: 10000, }, Batch { id: 2, reconciled: false, total_shares: Uint128::new(1345), - uluna_unclaimed: Uint128::new(1385), // 1.030 Luna per Steak + amount_unclaimed: Uint128::new(1385), // 1.030 Luna per Steak est_unbond_end_time: 20000, }, Batch { id: 3, reconciled: false, total_shares: Uint128::new(1456), - uluna_unclaimed: Uint128::new(1506), // 1.035 Luna per Steak + amount_unclaimed: Uint128::new(1506), // 1.035 Luna per Steak est_unbond_end_time: 30000, }, Batch { id: 4, reconciled: false, total_shares: Uint128::new(1567), - uluna_unclaimed: Uint128::new(1629), // 1.040 Luna per Steak - est_unbond_end_time: 40000, // not yet finished unbonding, ignored + amount_unclaimed: Uint128::new(1629), // 1.040 Luna per Steak + est_unbond_end_time: 40000, // not yet finished unbonding, ignored }, ]; for previous_batch in &previous_batches { state .previous_batches - .save(deps.as_mut().storage, previous_batch.id.into(), previous_batch) + .save(deps.as_mut().storage, previous_batch.id, previous_batch) .unwrap(); } state .unlocked_coins - .save( - deps.as_mut().storage, - &vec![ - Coin::new(10000, "uluna"), - Coin::new(234, "ukrw"), - Coin::new(345, "uusd"), - Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B"), - ], - ) + .save(deps.as_mut().storage, &vec![ + Coin::new(10000, "uxyz"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ), + ]) .unwrap(); deps.querier.set_bank_balances(&[ - Coin::new(12345, "uluna"), + Coin::new(12345, "uxyz"), Coin::new(234, "ukrw"), Coin::new(345, "uusd"), Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B"), @@ -713,35 +937,29 @@ fn reconciling() { // remainder: 0 // batch 2: 1385 - 273 = 1112 // batch 3: 1506 - 273 = 1233 - let batch = state.previous_batches.load(deps.as_ref().storage, 2u64.into()).unwrap(); - assert_eq!( - batch, - Batch { - id: 2, - reconciled: true, - total_shares: Uint128::new(1345), - uluna_unclaimed: Uint128::new(1112), // 1385 - 273 - est_unbond_end_time: 20000, - } - ); - - let batch = state.previous_batches.load(deps.as_ref().storage, 3u64.into()).unwrap(); - assert_eq!( - batch, - Batch { - id: 3, - reconciled: true, - total_shares: Uint128::new(1456), - uluna_unclaimed: Uint128::new(1233), // 1506 - 273 - est_unbond_end_time: 30000, - } - ); + let batch = state.previous_batches.load(deps.as_ref().storage, 2u64).unwrap(); + assert_eq!(batch, Batch { + id: 2, + reconciled: true, + total_shares: Uint128::new(1345), + amount_unclaimed: Uint128::new(1112), // 1385 - 273 + est_unbond_end_time: 20000, + }); + + let batch = state.previous_batches.load(deps.as_ref().storage, 3u64).unwrap(); + assert_eq!(batch, Batch { + id: 3, + reconciled: true, + total_shares: Uint128::new(1456), + amount_unclaimed: Uint128::new(1233), // 1506 - 273 + est_unbond_end_time: 30000, + }); // Batches 1 and 4 should not have changed - let batch = state.previous_batches.load(deps.as_ref().storage, 1u64.into()).unwrap(); + let batch = state.previous_batches.load(deps.as_ref().storage, 1u64).unwrap(); assert_eq!(batch, previous_batches[0]); - let batch = state.previous_batches.load(deps.as_ref().storage, 4u64.into()).unwrap(); + let batch = state.previous_batches.load(deps.as_ref().storage, 4u64).unwrap(); assert_eq!(batch, previous_batches[3]); } @@ -787,7 +1005,7 @@ fn withdrawing_unbonded() { .unbond_requests .save( deps.as_mut().storage, - (unbond_request.id.into(), &Addr::unchecked(unbond_request.user.clone())), + (unbond_request.id, &Addr::unchecked(unbond_request.user.clone())), unbond_request, ) .unwrap(); @@ -798,49 +1016,47 @@ fn withdrawing_unbonded() { id: 1, reconciled: true, total_shares: Uint128::new(92876), - uluna_unclaimed: Uint128::new(95197), // 1.025 Luna per Steak + amount_unclaimed: Uint128::new(95197), // 1.025 Luna per Steak est_unbond_end_time: 10000, }, Batch { id: 2, reconciled: true, total_shares: Uint128::new(34567), - uluna_unclaimed: Uint128::new(35604), // 1.030 Luna per Steak + amount_unclaimed: Uint128::new(35604), // 1.030 Luna per Steak est_unbond_end_time: 20000, }, Batch { id: 3, reconciled: false, // finished unbonding, but not reconciled; ignored total_shares: Uint128::new(45678), - uluna_unclaimed: Uint128::new(47276), // 1.035 Luna per Steak + amount_unclaimed: Uint128::new(47276), // 1.035 Luna per Steak est_unbond_end_time: 20000, }, Batch { id: 4, reconciled: true, total_shares: Uint128::new(56789), - uluna_unclaimed: Uint128::new(59060), // 1.040 Luna per Steak - est_unbond_end_time: 30000, // reconciled, but not yet finished unbonding; ignored + amount_unclaimed: Uint128::new(59060), // 1.040 Luna per Steak + est_unbond_end_time: 30000, /* reconciled, but not yet finished unbonding; + * ignored */ }, ]; for previous_batch in &previous_batches { state .previous_batches - .save(deps.as_mut().storage, previous_batch.id.into(), previous_batch) + .save(deps.as_mut().storage, previous_batch.id, previous_batch) .unwrap(); } state .pending_batch - .save( - deps.as_mut().storage, - &PendingBatch { - id: 4, - usteak_to_burn: Uint128::new(56789), - est_unbond_start_time: 100000, - }, - ) + .save(deps.as_mut().storage, &PendingBatch { + id: 4, + usteak_to_burn: Uint128::new(56789), + est_unbond_start_time: 100000, + }) .unwrap(); // Attempt to withdraw before any batch has completed unbonding. Should error @@ -878,56 +1094,47 @@ fn withdrawing_unbonded() { .unwrap(); assert_eq!(res.messages.len(), 1); - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - msg: CosmosMsg::Bank(BankMsg::Send { - to_address: "user_1".to_string(), - amount: vec![Coin::new(59646, "uluna")] - }), - gas_limit: None, - reply_on: ReplyOn::Never - } - ); + assert_eq!(res.messages[0], SubMsg { + id: 0, + msg: CosmosMsg::Bank(BankMsg::Send { + to_address: "user_1".to_string(), + amount: vec![Coin::new(59646, "uxyz")], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); // Previous batches should have been updated - let batch = state.previous_batches.load(deps.as_ref().storage, 1u64.into()).unwrap(); - assert_eq!( - batch, - Batch { - id: 1, - reconciled: true, - total_shares: Uint128::new(69420), - uluna_unclaimed: Uint128::new(71155), - est_unbond_end_time: 10000, - } - ); - - let err = state.previous_batches.load(deps.as_ref().storage, 2u64.into()).unwrap_err(); - assert_eq!( - err, - StdError::NotFound { kind: "steak::hub::Batch".to_string() } - ); + let batch = state.previous_batches.load(deps.as_ref().storage, 1u64).unwrap(); + assert_eq!(batch, Batch { + id: 1, + reconciled: true, + total_shares: Uint128::new(69420), + amount_unclaimed: Uint128::new(71155), + est_unbond_end_time: 10000, + }); + + let err = state.previous_batches.load(deps.as_ref().storage, 2u64).unwrap_err(); + assert_eq!(err, StdError::NotFound { + kind: "pfc_steak::hub::Batch".to_string(), + }); // User 1's unbond requests in batches 1 and 2 should have been deleted let err1 = state .unbond_requests - .load(deps.as_ref().storage, (1u64.into(), &Addr::unchecked("user_1"))) + .load(deps.as_ref().storage, (1u64, &Addr::unchecked("user_1"))) .unwrap_err(); let err2 = state .unbond_requests - .load(deps.as_ref().storage, (1u64.into(), &Addr::unchecked("user_1"))) + .load(deps.as_ref().storage, (1u64, &Addr::unchecked("user_1"))) .unwrap_err(); - assert_eq!( - err1, - StdError::NotFound { kind: "steak::hub::UnbondRequest".to_string() } - ); - assert_eq!( - err2, - StdError::NotFound { kind: "steak::hub::UnbondRequest".to_string() } - ); + assert_eq!(err1, StdError::NotFound { + kind: "pfc_steak::hub::UnbondRequest".to_string() + }); + assert_eq!(err2, StdError::NotFound { + kind: "pfc_steak::hub::UnbondRequest".to_string() + }); // User 3 attempt to withdraw; also specifying a receiver let res = execute( @@ -941,37 +1148,30 @@ fn withdrawing_unbonded() { .unwrap(); assert_eq!(res.messages.len(), 1); - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - msg: CosmosMsg::Bank(BankMsg::Send { - to_address: "user_2".to_string(), - amount: vec![Coin::new(71155, "uluna")] - }), - gas_limit: None, - reply_on: ReplyOn::Never - } - ); + assert_eq!(res.messages[0], SubMsg { + id: 0, + msg: CosmosMsg::Bank(BankMsg::Send { + to_address: "user_2".to_string(), + amount: vec![Coin::new(71155, "uxyz")], + }), + gas_limit: None, + reply_on: ReplyOn::Never, + }); // Batch 1 and user 2's unbonding request should have been purged from storage - let err = state.previous_batches.load(deps.as_ref().storage, 1u64.into()).unwrap_err(); - assert_eq!( - err, - StdError::NotFound { - kind: "steak::hub::Batch".to_string() - } - ); + let err = state.previous_batches.load(deps.as_ref().storage, 1u64).unwrap_err(); + assert_eq!(err, StdError::NotFound { + kind: "pfc_steak::hub::Batch".to_string() + }); let err = state .unbond_requests - .load(deps.as_ref().storage, (1u64.into(), &Addr::unchecked("user_3"))) + .load(deps.as_ref().storage, (1u64, &Addr::unchecked("user_3"))) .unwrap_err(); - assert_eq!( - err, - StdError::NotFound { kind: "steak::hub::UnbondRequest".to_string() } - ); + assert_eq!(err, StdError::NotFound { + kind: "pfc_steak::hub::UnbondRequest".to_string() + }); } #[test] @@ -979,52 +1179,37 @@ fn adding_validator() { let mut deps = setup_test(); let state = State::default(); - let err = execute( - deps.as_mut(), - mock_env(), - mock_info("jake", &[]), - ExecuteMsg::AddValidator { + let err = + execute(deps.as_mut(), mock_env(), mock_info("jake", &[]), ExecuteMsg::AddValidator { validator: "dave".to_string(), - }, - ) - .unwrap_err(); + }) + .unwrap_err(); assert_eq!(err, StdError::generic_err("unauthorized: sender is not owner")); - let err = execute( - deps.as_mut(), - mock_env(), - mock_info("larry", &[]), - ExecuteMsg::AddValidator { + let err = + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::AddValidator { validator: "alice".to_string(), - }, - ) - .unwrap_err(); + }) + .unwrap_err(); assert_eq!(err, StdError::generic_err("validator is already whitelisted")); - let res = execute( - deps.as_mut(), - mock_env(), - mock_info("larry", &[]), - ExecuteMsg::AddValidator { + let res = + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::AddValidator { validator: "dave".to_string(), - }, - ) - .unwrap(); + }) + .unwrap(); assert_eq!(res.messages.len(), 0); let validators = state.validators.load(deps.as_ref().storage).unwrap(); - assert_eq!( - validators, - vec![ - String::from("alice"), - String::from("bob"), - String::from("charlie"), - String::from("dave") - ], - ); + assert_eq!(validators, vec![ + String::from("alice"), + String::from("bob"), + String::from("charlie"), + String::from("dave"), + ],); } #[test] @@ -1033,32 +1218,24 @@ fn removing_validator() { let state = State::default(); deps.querier.set_staking_delegations(&[ - Delegation::new("alice", 341667), - Delegation::new("bob", 341667), - Delegation::new("charlie", 341666), + Delegation::new("alice", 341667, "uxyz"), + Delegation::new("bob", 341667, "uxyz"), + Delegation::new("charlie", 341666, "uxyz"), ]); - let err = execute( - deps.as_mut(), - mock_env(), - mock_info("jake", &[]), - ExecuteMsg::RemoveValidator { + let err = + execute(deps.as_mut(), mock_env(), mock_info("jake", &[]), ExecuteMsg::RemoveValidator { validator: "charlie".to_string(), - }, - ) - .unwrap_err(); + }) + .unwrap_err(); assert_eq!(err, StdError::generic_err("unauthorized: sender is not owner")); - let err = execute( - deps.as_mut(), - mock_env(), - mock_info("larry", &[]), - ExecuteMsg::RemoveValidator { + let err = + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::RemoveValidator { validator: "dave".to_string(), - }, - ) - .unwrap_err(); + }) + .unwrap_err(); assert_eq!(err, StdError::generic_err("validator is not already whitelisted")); @@ -1066,24 +1243,26 @@ fn removing_validator() { // Remainder: 0 // Alice: 512500 + 0 - 341667 = 170833 // Bob: 512500 + 0 - 341667 = 170833 - let res = execute( - deps.as_mut(), - mock_env(), - mock_info("larry", &[]), - ExecuteMsg::RemoveValidator { + let res = + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::RemoveValidator { validator: "charlie".to_string(), - }, - ) - .unwrap(); + }) + .unwrap(); assert_eq!(res.messages.len(), 2); assert_eq!( res.messages[0], - SubMsg::reply_on_success(Redelegation::new("charlie", "alice", 170833).to_cosmos_msg(), 2), + SubMsg::reply_on_success( + Redelegation::new("charlie", "alice", 170833, "uxyz").to_cosmos_msg(), + REPLY_REGISTER_RECEIVED_COINS, + ), ); assert_eq!( res.messages[1], - SubMsg::reply_on_success(Redelegation::new("charlie", "bob", 170833).to_cosmos_msg(), 2), + SubMsg::reply_on_success( + Redelegation::new("charlie", "bob", 170833, "uxyz").to_cosmos_msg(), + REPLY_REGISTER_RECEIVED_COINS, + ), ); let validators = state.validators.load(deps.as_ref().storage).unwrap(); @@ -1095,15 +1274,11 @@ fn transferring_ownership() { let mut deps = setup_test(); let state = State::default(); - let err = execute( - deps.as_mut(), - mock_env(), - mock_info("jake", &[]), - ExecuteMsg::TransferOwnership { + let err = + execute(deps.as_mut(), mock_env(), mock_info("jake", &[]), ExecuteMsg::TransferOwnership { new_owner: "jake".to_string(), - }, - ) - .unwrap_err(); + }) + .unwrap_err(); assert_eq!(err, StdError::generic_err("unauthorized: sender is not owner")); @@ -1132,13 +1307,9 @@ fn transferring_ownership() { assert_eq!(err, StdError::generic_err("unauthorized: sender is not new owner")); - let res = execute( - deps.as_mut(), - mock_env(), - mock_info("jake", &[]), - ExecuteMsg::AcceptOwnership {} - ) - .unwrap(); + let res = + execute(deps.as_mut(), mock_env(), mock_info("jake", &[]), ExecuteMsg::AcceptOwnership {}) + .unwrap(); assert_eq!(res.messages.len(), 0); @@ -1146,6 +1317,82 @@ fn transferring_ownership() { assert_eq!(owner, Addr::unchecked("jake")); } +#[test] +fn splitting_fees() { + let mut deps = setup_test(); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info("jake", &[]), + ExecuteMsg::TransferFeeAccount { + fee_account_type: "Wallet".to_string(), + new_fee_account: "charlie".to_string(), + }, + ) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("unauthorized: sender is not owner")); + + let err = execute( + deps.as_mut(), + mock_env(), + mock_info("larry", &[]), + ExecuteMsg::TransferFeeAccount { + fee_account_type: "xxxx".to_string(), + new_fee_account: "charlie".to_string(), + }, + ) + .unwrap_err(); + + assert_eq!(err, StdError::generic_err("Invalid Fee type: Wallet or FeeSplit only")); + + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::TransferFeeAccount { + fee_account_type: "Wallet".to_string(), + new_fee_account: "charlie".to_string(), + }) + .unwrap(); + let res: ConfigResponse = query_helper(deps.as_ref(), QueryMsg::Config {}); + assert_eq!(res, ConfigResponse { + owner: "larry".to_string(), + new_owner: None, + steak_token: "steak_token".to_string(), + epoch_period: 259200, + unbond_period: 1814400, + denom: "uxyz".to_string(), + fee_type: "Wallet".to_string(), + fee_account: "charlie".to_string(), + fee_rate: Decimal::from_ratio(10_u128, 100_u128), + max_fee_rate: Decimal::from_ratio(20_u128, 100_u128), + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string(),], + paused_validators: vec![], + dust_collector: None, + token_factory: None + }); + + execute(deps.as_mut(), mock_env(), mock_info("larry", &[]), ExecuteMsg::TransferFeeAccount { + fee_account_type: "FeeSplit".to_string(), + new_fee_account: "contract".to_string(), + }) + .unwrap(); + let res: ConfigResponse = query_helper(deps.as_ref(), QueryMsg::Config {}); + assert_eq!(res, ConfigResponse { + owner: "larry".to_string(), + new_owner: None, + steak_token: "steak_token".to_string(), + epoch_period: 259200, + unbond_period: 1814400, + denom: "uxyz".to_string(), + fee_type: "FeeSplit".to_string(), + fee_account: "contract".to_string(), + fee_rate: Decimal::from_ratio(10_u128, 100_u128), + max_fee_rate: Decimal::from_ratio(20_u128, 100_u128), + validators: vec!["alice".to_string(), "bob".to_string(), "charlie".to_string(),], + paused_validators: vec![], + dust_collector: None, + token_factory: None + }); +} //-------------------------------------------------------------------------------------------------- // Queries //-------------------------------------------------------------------------------------------------- @@ -1159,35 +1406,35 @@ fn querying_previous_batches() { id: 1, reconciled: false, total_shares: Uint128::new(123), - uluna_unclaimed: Uint128::new(678), + amount_unclaimed: Uint128::new(678), est_unbond_end_time: 10000, }, Batch { id: 2, reconciled: true, total_shares: Uint128::new(234), - uluna_unclaimed: Uint128::new(789), + amount_unclaimed: Uint128::new(789), est_unbond_end_time: 15000, }, Batch { id: 3, reconciled: false, total_shares: Uint128::new(345), - uluna_unclaimed: Uint128::new(890), + amount_unclaimed: Uint128::new(890), est_unbond_end_time: 20000, }, Batch { id: 4, reconciled: true, total_shares: Uint128::new(456), - uluna_unclaimed: Uint128::new(999), + amount_unclaimed: Uint128::new(999), est_unbond_end_time: 25000, }, ]; let state = State::default(); for batch in &batches { - state.previous_batches.save(deps.as_mut().storage, batch.id.into(), batch).unwrap(); + state.previous_batches.save(deps.as_mut().storage, batch.id, batch).unwrap(); } // Querying a single batch @@ -1198,31 +1445,22 @@ fn querying_previous_batches() { assert_eq!(res, batches[1].clone()); // Query multiple batches - let res: Vec = query_helper( - deps.as_ref(), - QueryMsg::PreviousBatches { - start_after: None, - limit: None, - }, - ); + let res: Vec = query_helper(deps.as_ref(), QueryMsg::PreviousBatches { + start_after: None, + limit: None, + }); assert_eq!(res, batches.clone()); - let res: Vec = query_helper( - deps.as_ref(), - QueryMsg::PreviousBatches { - start_after: Some(1), - limit: None, - }, - ); + let res: Vec = query_helper(deps.as_ref(), QueryMsg::PreviousBatches { + start_after: Some(1), + limit: None, + }); assert_eq!(res, vec![batches[1].clone(), batches[2].clone(), batches[3].clone()]); - let res: Vec = query_helper( - deps.as_ref(), - QueryMsg::PreviousBatches { - start_after: Some(4), - limit: None, - }, - ); + let res: Vec = query_helper(deps.as_ref(), QueryMsg::PreviousBatches { + start_after: Some(4), + limit: None, + }); assert_eq!(res, vec![]); // Query multiple batches, indexed by whether it has been reconciled @@ -1288,57 +1526,46 @@ fn querying_unbond_requests() { .unbond_requests .save( deps.as_mut().storage, - (unbond_request.id.into(), &Addr::unchecked(unbond_request.user.clone())), + (unbond_request.id, &Addr::unchecked(unbond_request.user.clone())), unbond_request, ) .unwrap(); } - let res: Vec = query_helper( - deps.as_ref(), - QueryMsg::UnbondRequestsByBatch { + let res: Vec = + query_helper(deps.as_ref(), QueryMsg::UnbondRequestsByBatch { id: 1, start_after: None, limit: None, - }, - ); - assert_eq!( - res, - vec![ - unbond_requests[0].clone().into(), - unbond_requests[1].clone().into(), - unbond_requests[2].clone().into(), - ] - ); + }); + assert_eq!(res, vec![ + unbond_requests[0].clone().into(), + unbond_requests[1].clone().into(), + unbond_requests[2].clone().into(), + ]); - let res: Vec = query_helper( - deps.as_ref(), - QueryMsg::UnbondRequestsByBatch { + let res: Vec = + query_helper(deps.as_ref(), QueryMsg::UnbondRequestsByBatch { id: 2, start_after: None, limit: None, - }, - ); + }); assert_eq!(res, vec![unbond_requests[3].clone().into()]); - let res: Vec = query_helper( - deps.as_ref(), - QueryMsg::UnbondRequestsByUser { + let res: Vec = + query_helper(deps.as_ref(), QueryMsg::UnbondRequestsByUser { user: "alice".to_string(), start_after: None, limit: None, - }, - ); - assert_eq!(res, vec![unbond_requests[0].clone().into(), unbond_requests[3].clone().into()]); + }); + assert_eq!(res, vec![unbond_requests[0].clone().into(), unbond_requests[3].clone().into(),]); - let res: Vec = query_helper( - deps.as_ref(), - QueryMsg::UnbondRequestsByUser { + let res: Vec = + query_helper(deps.as_ref(), QueryMsg::UnbondRequestsByUser { user: "alice".to_string(), start_after: Some(2), limit: None, - }, - ); + }); assert_eq!(res, vec![unbond_requests[3].clone().into()]); } @@ -1349,9 +1576,9 @@ fn querying_unbond_requests() { #[test] fn computing_undelegations() { let current_delegations = vec![ - Delegation::new("alice", 400), - Delegation::new("bob", 300), - Delegation::new("charlie", 200), + Delegation::new("alice", 400, "uxyz"), + Delegation::new("bob", 300, "uxyz"), + Delegation::new("charlie", 200, "uxyz"), ]; // Target: (400 + 300 + 200 - 451) / 3 = 149 @@ -1359,11 +1586,11 @@ fn computing_undelegations() { // Alice: 400 - (149 + 1) = 250 // Bob: 300 - (149 + 1) = 150 // Charlie: 200 - (149 + 0) = 51 - let new_undelegations = compute_undelegations(Uint128::new(451), ¤t_delegations); + let new_undelegations = compute_undelegations(Uint128::new(451), ¤t_delegations, "uxyz"); let expected = vec![ - Undelegation::new("alice", 250), - Undelegation::new("bob", 150), - Undelegation::new("charlie", 51), + Undelegation::new("alice", 250, "uxyz"), + Undelegation::new("bob", 150, "uxyz"), + Undelegation::new("charlie", 51, "uxyz"), ]; assert_eq!(new_undelegations, expected); } @@ -1371,10 +1598,10 @@ fn computing_undelegations() { #[test] fn computing_redelegations_for_removal() { let current_delegations = vec![ - Delegation::new("alice", 13000), - Delegation::new("bob", 12000), - Delegation::new("charlie", 11000), - Delegation::new("dave", 10000), + Delegation::new("alice", 13000, "uxyz"), + Delegation::new("bob", 12000, "uxyz"), + Delegation::new("charlie", 11000, "uxyz"), + Delegation::new("dave", 10000, "uxyz"), ]; // Suppose Dave will be removed @@ -1384,13 +1611,17 @@ fn computing_redelegations_for_removal() { // to Bob: 15333 + 0 - 12000 = 3333 // to Charlie: 15333 + 0 - 11000 = 4333 let expected = vec![ - Redelegation::new("dave", "alice", 2334), - Redelegation::new("dave", "bob", 3333), - Redelegation::new("dave", "charlie", 4333), + Redelegation::new("dave", "alice", 2334, "uxyz"), + Redelegation::new("dave", "bob", 3333, "uxyz"), + Redelegation::new("dave", "charlie", 4333, "uxyz"), ]; assert_eq!( - compute_redelegations_for_removal(¤t_delegations[3], ¤t_delegations[..3]), + compute_redelegations_for_removal( + ¤t_delegations[3], + ¤t_delegations[..3], + "uxyz", + ), expected, ); } @@ -1398,13 +1629,19 @@ fn computing_redelegations_for_removal() { #[test] fn computing_redelegations_for_rebalancing() { let current_delegations = vec![ - Delegation::new("alice", 69420), - Delegation::new("bob", 1234), - Delegation::new("charlie", 88888), - Delegation::new("dave", 40471), - Delegation::new("evan", 2345), + Delegation::new("alice", 69420, "uxyz"), + Delegation::new("bob", 1234, "uxyz"), + Delegation::new("charlie", 88888, "uxyz"), + Delegation::new("dave", 40471, "uxyz"), + Delegation::new("evan", 2345, "uxyz"), + ]; + let active_validators: Vec = vec![ + "alice".to_string(), + "bob".to_string(), + "charlie".to_string(), + "dave".to_string(), + "evan".to_string(), ]; - // uluna_per_validator = (69420 + 88888 + 1234 + 40471 + 2345) / 4 = 40471 // remainer = 3 // src_delegations: @@ -1430,12 +1667,49 @@ fn computing_redelegations_for_rebalancing() { // Round 3: charlie --(38126)--> evan // Queues are emptied let expected = vec![ - Redelegation::new("alice", "bob", 28948), - Redelegation::new("charlie", "bob", 10290), - Redelegation::new("charlie", "evan", 38126), + Redelegation::new("alice", "bob", 28948, "uxyz"), + Redelegation::new("charlie", "bob", 10290, "uxyz"), + Redelegation::new("charlie", "evan", 38126, "uxyz"), + ]; + + assert_eq!( + compute_redelegations_for_rebalancing( + active_validators, + ¤t_delegations, + Uint128::from(10u64) + ), + expected, + ); + + let partially_active = + vec!["alice".to_string(), "charlie".to_string(), "dave".to_string(), "evan".to_string()]; + + let partially_expected = vec![ + Redelegation::new("alice", "dave", 10118, "uxyz"), + Redelegation::new("alice", "evan", 8712, "uxyz"), + Redelegation::new("charlie", "evan", 38299, "uxyz"), ]; + assert_eq!( + compute_redelegations_for_rebalancing( + partially_active.clone(), + ¤t_delegations, + Uint128::from(10u64) + ), + partially_expected, + ); - assert_eq!(compute_redelegations_for_rebalancing(¤t_delegations), expected,); + let partially_expected_minimums = vec![ + Redelegation::new("alice", "evan", 18830, "uxyz"), + Redelegation::new("charlie", "evan", 29414, "uxyz"), + ]; + assert_eq!( + compute_redelegations_for_rebalancing( + partially_active, + ¤t_delegations, + Uint128::from(15_000u64) + ), + partially_expected_minimums, + ); } //-------------------------------------------------------------------------------------------------- @@ -1447,8 +1721,13 @@ fn parsing_coin() { let coin = parse_coin("12345uatom").unwrap(); assert_eq!(coin, Coin::new(12345, "uatom")); - let coin = parse_coin("23456ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B").unwrap(); - assert_eq!(coin, Coin::new(23456, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B")); + let coin = + parse_coin("23456ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B") + .unwrap(); + assert_eq!( + coin, + Coin::new(23456, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B",) + ); let err = parse_coin("69420").unwrap_err(); assert_eq!(err, StdError::generic_err("failed to parse coin: 69420")); @@ -1465,8 +1744,8 @@ fn parsing_coins() { let coins = Coins::from_str("12345uatom").unwrap(); assert_eq!(coins.0, vec![Coin::new(12345, "uatom")]); - let coins = Coins::from_str("12345uatom,23456uluna").unwrap(); - assert_eq!(coins.0, vec![Coin::new(12345, "uatom"), Coin::new(23456, "uluna")]); + let coins = Coins::from_str("12345uatom,23456uxyz").unwrap(); + assert_eq!(coins.0, vec![Coin::new(12345, "uatom"), Coin::new(23456, "uxyz")]); } #[test] @@ -1476,27 +1755,166 @@ fn adding_coins() { coins.add(&Coin::new(12345, "uatom")).unwrap(); assert_eq!(coins.0, vec![Coin::new(12345, "uatom")]); - coins.add(&Coin::new(23456, "uluna")).unwrap(); - assert_eq!(coins.0, vec![Coin::new(12345, "uatom"), Coin::new(23456, "uluna")]); + coins.add(&Coin::new(23456, "uxyz")).unwrap(); + assert_eq!(coins.0, vec![Coin::new(12345, "uatom"), Coin::new(23456, "uxyz")]); coins.add_many(&Coins::from_str("76543uatom,69420uusd").unwrap()).unwrap(); - assert_eq!(coins.0, vec![Coin::new(88888, "uatom"), Coin::new(23456, "uluna"), Coin::new(69420, "uusd")]); + assert_eq!(coins.0, vec![ + Coin::new(88888, "uatom"), + Coin::new(23456, "uxyz"), + Coin::new(69420, "uusd"), + ]); } #[test] fn receiving_funds() { - let err = parse_received_fund(&[], "uluna").unwrap_err(); + let err = parse_received_fund(&[], "uxyz").unwrap_err(); assert_eq!(err, StdError::generic_err("must deposit exactly one coin; received 0")); - let err = parse_received_fund(&[Coin::new(12345, "uatom"), Coin::new(23456, "uluna")], "uluna").unwrap_err(); + let err = parse_received_fund(&[Coin::new(12345, "uatom"), Coin::new(23456, "uxyz")], "uxyz") + .unwrap_err(); assert_eq!(err, StdError::generic_err("must deposit exactly one coin; received 2")); - let err = parse_received_fund(&[Coin::new(12345, "uatom")], "uluna").unwrap_err(); - assert_eq!(err, StdError::generic_err("expected uluna deposit, received uatom")); + let err = parse_received_fund(&[Coin::new(12345, "uatom")], "uxyz").unwrap_err(); + assert_eq!(err, StdError::generic_err("expected uxyz deposit, received uatom")); - let err = parse_received_fund(&[Coin::new(0, "uluna")], "uluna").unwrap_err(); + let err = parse_received_fund(&[Coin::new(0, "uxyz")], "uxyz").unwrap_err(); assert_eq!(err, StdError::generic_err("deposit amount must be non-zero")); - let amount = parse_received_fund(&[Coin::new(69420, "uluna")], "uluna").unwrap(); + let amount = parse_received_fund(&[Coin::new(69420, "uxyz")], "uxyz").unwrap(); assert_eq!(amount, Uint128::new(69420)); } + +#[test] +fn reconciling_underflow() { + let mut deps = setup_test(); + let state = State::default(); + let previous_batches = vec![ + Batch { + id: 1, + reconciled: true, + total_shares: Uint128::new(92876), + amount_unclaimed: Uint128::new(95197), // 1.025 Token per Stake + est_unbond_end_time: 10000, + }, + Batch { + id: 2, + reconciled: false, + total_shares: Uint128::new(1345), + amount_unclaimed: Uint128::new(1385), // 1.030 Token per Stake + est_unbond_end_time: 20000, + }, + Batch { + id: 3, + reconciled: false, + total_shares: Uint128::new(1456), + amount_unclaimed: Uint128::new(1506), // 1.035 Token per Stake + est_unbond_end_time: 30000, + }, + Batch { + id: 4, + reconciled: false, + total_shares: Uint128::new(1), + amount_unclaimed: Uint128::new(1), + est_unbond_end_time: 30001, + }, + ]; + for previous_batch in &previous_batches { + state + .previous_batches + .save(deps.as_mut().storage, previous_batch.id, previous_batch) + .unwrap(); + } + state + .unlocked_coins + .save(deps.as_mut().storage, &vec![ + Coin::new(10000, "uatom"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ), + ]) + .unwrap(); + deps.querier.set_bank_balances(&[ + Coin::new(12345, "uatom"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B"), + ]); + execute( + deps.as_mut(), + mock_env_at_timestamp(35000), + mock_info("worker", &[]), + ExecuteMsg::Reconcile {}, + ) + .unwrap(); +} + +#[test] +fn reconciling_underflow_second() { + let mut deps = setup_test(); + let state = State::default(); + let previous_batches = vec![ + Batch { + id: 1, + reconciled: true, + total_shares: Uint128::new(92876), + amount_unclaimed: Uint128::new(95197), // 1.025 Token per Stake + est_unbond_end_time: 10000, + }, + Batch { + id: 2, + reconciled: false, + total_shares: Uint128::new(1345), + amount_unclaimed: Uint128::new(1385), // 1.030 Token per Stake + est_unbond_end_time: 20000, + }, + Batch { + id: 3, + reconciled: false, + total_shares: Uint128::new(176), + amount_unclaimed: Uint128::new(183), // 1.035 Token per Stake + est_unbond_end_time: 30000, + }, + Batch { + id: 4, + reconciled: false, + total_shares: Uint128::new(1), + amount_unclaimed: Uint128::new(1), + est_unbond_end_time: 30001, + }, + ]; + for previous_batch in &previous_batches { + state + .previous_batches + .save(deps.as_mut().storage, previous_batch.id, previous_batch) + .unwrap(); + } + state + .unlocked_coins + .save(deps.as_mut().storage, &vec![ + Coin::new(10000, "uatom"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new( + 69420, + "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B", + ), + ]) + .unwrap(); + deps.querier.set_bank_balances(&[ + Coin::new(12345 - 1323, "uatom"), + Coin::new(234, "ukrw"), + Coin::new(345, "uusd"), + Coin::new(69420, "ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B"), + ]); + execute( + deps.as_mut(), + mock_env_at_timestamp(35000), + mock_info("worker", &[]), + ExecuteMsg::Reconcile {}, + ) + .unwrap(); +} diff --git a/contracts/hub/src/types/coins.rs b/contracts/hub/src/types/coins.rs index 073d86f6..3cd73ef7 100644 --- a/contracts/hub/src/types/coins.rs +++ b/contracts/hub/src/types/coins.rs @@ -48,8 +48,8 @@ impl Coins { pub fn find(&self, denom: &str) -> Coin { self.0 .iter() - .cloned() .find(|coin| coin.denom == denom) + .cloned() .unwrap_or_else(|| Coin::new(0, denom)) } } diff --git a/contracts/hub/src/types/keys.rs b/contracts/hub/src/types/keys.rs index 5c2af964..25dab9a2 100644 --- a/contracts/hub/src/types/keys.rs +++ b/contracts/hub/src/types/keys.rs @@ -27,7 +27,7 @@ impl From for BooleanKey { } } -impl<'a> PrimaryKey<'a> for BooleanKey { +impl PrimaryKey<'_> for BooleanKey { type Prefix = (); type SubPrefix = (); type Suffix = (); @@ -38,7 +38,7 @@ impl<'a> PrimaryKey<'a> for BooleanKey { } } -impl<'a> Prefixer<'a> for BooleanKey { +impl Prefixer<'_> for BooleanKey { fn prefix(&self) -> Vec { self.wrapped.prefix() } diff --git a/contracts/hub/src/types/staking.rs b/contracts/hub/src/types/staking.rs index a4d4fa32..39cb5997 100644 --- a/contracts/hub/src/types/staking.rs +++ b/contracts/hub/src/types/staking.rs @@ -5,20 +5,22 @@ use cosmwasm_std::{Coin, CosmosMsg, StakingMsg}; pub struct Delegation { pub validator: String, pub amount: u128, + pub denom: String, } impl Delegation { - pub fn new(validator: &str, amount: u128) -> Self { + pub fn new(validator: &str, amount: u128, denom: &str) -> Self { Self { validator: validator.to_string(), amount, + denom: denom.to_string(), } } pub fn to_cosmos_msg(&self) -> CosmosMsg { CosmosMsg::Staking(StakingMsg::Delegate { validator: self.validator.clone(), - amount: Coin::new(self.amount, "uluna"), + amount: Coin::new(self.amount, self.denom.to_string()), }) } } @@ -27,20 +29,22 @@ impl Delegation { pub struct Undelegation { pub validator: String, pub amount: u128, + pub denom: String, } impl Undelegation { - pub fn new(validator: &str, amount: u128) -> Self { + pub fn new(validator: &str, amount: u128, denom: &str) -> Self { Self { validator: validator.to_string(), amount, + denom: denom.to_string(), } } pub fn to_cosmos_msg(&self) -> CosmosMsg { CosmosMsg::Staking(StakingMsg::Undelegate { validator: self.validator.clone(), - amount: Coin::new(self.amount, "uluna"), + amount: Coin::new(self.amount, self.denom.to_string()), }) } } @@ -50,14 +54,16 @@ pub struct Redelegation { pub src: String, pub dst: String, pub amount: u128, + pub denom: String, } impl Redelegation { - pub fn new(src: &str, dst: &str, amount: u128) -> Self { + pub fn new(src: &str, dst: &str, amount: u128, denom: &str) -> Self { Self { src: src.to_string(), dst: dst.to_string(), amount, + denom: denom.into(), } } @@ -65,7 +71,7 @@ impl Redelegation { CosmosMsg::Staking(StakingMsg::Redelegate { src_validator: self.src.clone(), dst_validator: self.dst.clone(), - amount: Coin::new(self.amount, "uluna"), + amount: Coin::new(self.amount, &self.denom), }) } } diff --git a/contracts/token/Cargo.toml b/contracts/token/Cargo.toml index 571e642f..4b30c4ce 100644 --- a/contracts/token/Cargo.toml +++ b/contracts/token/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "steak-token" -version = "2.0.0" +name = "pfc-steak-token" +version = "3.0.20" authors = ["larry ", "PFC "] edition = "2018" license = "GPL-3.0-or-later" @@ -10,9 +10,9 @@ repository = "https://github.com/st4k3h0us3/steak-contracts" crate-type = ["cdylib", "rlib"] [features] -backtraces = ["cosmwasm-std/backtraces"] +# backtraces = ["cosmwasm-std/backtraces"] [dependencies] -cosmwasm-std = { version = "1.0" } +cosmwasm-std = { version = "1.5.8" } cw20 = "0.13" cw20-base = { version = "0.13", features = ["library"] } diff --git a/contracts/token/src/lib.rs b/contracts/token/src/lib.rs index 45956abb..6b290406 100644 --- a/contracts/token/src/lib.rs +++ b/contracts/token/src/lib.rs @@ -1,15 +1,15 @@ use cosmwasm_std::{ - entry_point, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, - Storage, + Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, + entry_point, }; -use cw20_base::contract::{ - execute as cw20_execute, instantiate as cw20_instantiate, query as cw20_query, +use cw20_base::{ + ContractError, + contract::{execute as cw20_execute, instantiate as cw20_instantiate, query as cw20_query}, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + state::{MinterData, TOKEN_INFO}, }; -use cw20_base::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use cw20_base::state::{MinterData, TOKEN_INFO}; -use cw20_base::ContractError; -#[cfg_attr(not(feature = "library"), entry_point)] +#[entry_point] pub fn instantiate( deps: DepsMut, env: Env, @@ -19,7 +19,7 @@ pub fn instantiate( cw20_instantiate(deps, env, info, msg) } -#[cfg_attr(not(feature = "library"), entry_point)] +#[entry_point] pub fn execute( deps: DepsMut, env: Env, @@ -29,8 +29,12 @@ pub fn execute( // For `burn`, we assert that the caller is the minter // For `burn_from`, we simply disable it match msg { - ExecuteMsg::Burn { .. } => assert_minter(deps.storage, &info.sender)?, - ExecuteMsg::BurnFrom { .. } => return Err(StdError::generic_err("`burn_from` command is disabled").into()), + ExecuteMsg::Burn { + .. + } => assert_minter(deps.storage, &info.sender)?, + ExecuteMsg::BurnFrom { + .. + } => return Err(StdError::generic_err("`burn_from` command is disabled").into()), _ => (), } @@ -40,7 +44,11 @@ pub fn execute( fn assert_minter(storage: &dyn Storage, sender: &Addr) -> Result<(), ContractError> { let token_info = TOKEN_INFO.load(storage)?; - if let Some(MinterData { minter, .. }) = &token_info.mint { + if let Some(MinterData { + minter, + .. + }) = &token_info.mint + { if sender != minter { return Err(StdError::generic_err("only minter can execute token burn").into()); } @@ -49,18 +57,18 @@ fn assert_minter(storage: &dyn Storage, sender: &Addr) -> Result<(), ContractErr Ok(()) } -#[cfg_attr(not(feature = "library"), entry_point)] +#[entry_point] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { cw20_query(deps, env, msg) } #[cfg(test)] mod tests { - use cosmwasm_std::testing::{ - mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, + use cosmwasm_std::{ + OwnedDeps, Uint128, + testing::{MockApi, MockQuerier, MockStorage, mock_dependencies, mock_env, mock_info}, }; - use cosmwasm_std::{OwnedDeps, Uint128}; - use cw20_base::state::{TokenInfo, BALANCES}; + use cw20_base::state::{BALANCES, TokenInfo}; use super::*; @@ -68,35 +76,24 @@ mod tests { let mut deps = mock_dependencies(); TOKEN_INFO - .save( - deps.as_mut().storage, - &TokenInfo { - name: "Steak Token".to_string(), - symbol: "STEAK".to_string(), - decimals: 6, - total_supply: Uint128::new(200), - mint: Some(MinterData { - minter: Addr::unchecked("steak_hub"), - cap: None, - }), - }, - ) + .save(deps.as_mut().storage, &TokenInfo { + name: "Steak Token".to_string(), + symbol: "STEAK".to_string(), + decimals: 6, + total_supply: Uint128::new(200), + mint: Some(MinterData { + minter: Addr::unchecked("steak_hub"), + cap: None, + }), + }) .unwrap(); BALANCES - .save( - deps.as_mut().storage, - &Addr::unchecked("steak_hub"), - &Uint128::new(100) - ) + .save(deps.as_mut().storage, &Addr::unchecked("steak_hub"), &Uint128::new(100)) .unwrap(); BALANCES - .save( - deps.as_mut().storage, - &Addr::unchecked("alice"), - &Uint128::new(100) - ) + .save(deps.as_mut().storage, &Addr::unchecked("alice"), &Uint128::new(100)) .unwrap(); deps @@ -107,25 +104,16 @@ mod tests { let mut deps = setup_test(); // Alice is not allowed to burn her balance - let res = execute( - deps.as_mut(), - mock_env(), - mock_info("alice", &[]), - ExecuteMsg::Burn { - amount: Uint128::new(100), - }, - ); + let res = execute(deps.as_mut(), mock_env(), mock_info("alice", &[]), ExecuteMsg::Burn { + amount: Uint128::new(100), + }); assert_eq!(res, Err(StdError::generic_err("only minter can execute token burn").into())); // Steak Hub can burn - let res = execute( - deps.as_mut(), - mock_env(), - mock_info("steak_hub", &[]), - ExecuteMsg::Burn { + let res = + execute(deps.as_mut(), mock_env(), mock_info("steak_hub", &[]), ExecuteMsg::Burn { amount: Uint128::new(100), - }, - ); + }); assert!(res.is_ok()); // Steak Hub's token balance should have been reduced @@ -142,15 +130,11 @@ mod tests { let mut deps = setup_test(); // Not even Steak Hub can invoke `burn_from` - let res = execute( - deps.as_mut(), - mock_env(), - mock_info("steak_hub", &[]), - ExecuteMsg::BurnFrom { + let res = + execute(deps.as_mut(), mock_env(), mock_info("steak_hub", &[]), ExecuteMsg::BurnFrom { owner: "alice".to_string(), amount: Uint128::new(100), - }, - ); + }); assert_eq!(res, Err(StdError::generic_err("`burn_from` command is disabled").into())); } } diff --git a/init/archway_hub_init.json b/init/archway_hub_init.json new file mode 100644 index 00000000..be098473 --- /dev/null +++ b/init/archway_hub_init.json @@ -0,0 +1,25 @@ +{ + "cw20_code_id": 701, + "owner": "archway1lu92zj8q6cmrptu09rp3343x9969r9qrg72hzj", + "name": "boneARCH Token", + "symbol": "boneARCH", + "decimals": 18, + "epoch_period": 259200, + "unbond_period": 1814400, + "validators": [ + "archwayvaloper1hgzw3udgagqylcvp9kyqq2w7ws8enucqpw094n", + "archwayvaloper140l6y2gp3gxvay6qtn70re7z2s0gn57zekxzpl", + "archwayvaloper1p4n7pw4rdfyn3rgc4cndcqedwmcu3dt6kfdwpt" + ], + "denom": "aarch", + "fee_account": "archway1pzjprqczpn0fvn5kvqzs74mggg4539rt2vpmp07ffp3pufp6cc3qqmhdg3", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "FeeSplit", + "label": "boneARCH", + "marketing": { + "project": "https://backbonelabs.io", + "description": "The GraveDigger is the Liquid Staking Product of BackBone Labs. It's liquid staking derivative (LSD) is boneARCH.", + "marketing": "archway1lu92zj8q6cmrptu09rp3343x9969r9qrg72hzj" + } +} \ No newline at end of file diff --git a/init/chihuahua_hub_init.json b/init/chihuahua_hub_init.json new file mode 100644 index 00000000..fa50dd54 --- /dev/null +++ b/init/chihuahua_hub_init.json @@ -0,0 +1,25 @@ +{ + "cw20_code_id": 275, + "owner": "chihuahua1lu92zj8q6cmrptu09rp3343x9969r9qr7qmaf8", + "name": "bHUAHUA Token", + "symbol": "bHUAHUA", + "decimals": 6, + "epoch_period": 259200, + "unbond_period": 1814400, + "validators": [ + "chihuahuavaloper15tnycxe9csn7mkul4vvlyxlkd9jyqlw4q80nmy", + "chihuahuavaloper1670dvuv348eynr9lsmdrhqu3g7vpmzx96h4l2d", + "chihuahuavaloper13x4kf2zcdtegl8tytg0j0tclgv9atvt443k8hg" + ], + "denom": "uhuahua", + "fee_account": "chihuahua1dhlrnqrpafcynx2w3mmveasv7rcmjhx0gsr7k740yx8x4q833vcq7tzlr4", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "FeeSplit", + "label": "boneHUAHUA", + "marketing": { + "project": "https://backbonelabs.io", + "description": "The GraveDigger is the Liquid Staking Product of BackBone Labs. It's liquid staking derivative (LSD) is bHUAHUA.", + "marketing": "chihuahua1lu92zj8q6cmrptu09rp3343x9969r9qr7qmaf8" + } +} \ No newline at end of file diff --git a/init/elgafar_hub_init.json b/init/elgafar_hub_init.json new file mode 100644 index 00000000..bcca70b2 --- /dev/null +++ b/init/elgafar_hub_init.json @@ -0,0 +1,25 @@ +{ + "cw20_code_id": 801, + "owner": "stars1kdtdg0lvy8asxn8clnjfpusuvf93zuknxrad8g", + "name": "bSTARS Token", + "symbol": "bSTARS", + "decimals": 6, + "epoch_period": 259200, + "unbond_period": 1209600, + "validators": [ + "starsvaloper1jt9w26mpxxjsk63mvd4m2ynj0af09cslura0ec", + "starsvaloper12xlxetf292m9llh5y9gwv92wkqwwagxulwl5l0", + "starsvaloper1ck4n6fq2er7g7380pdzqhxjlk6tevyzp7389vm" + ], + "denom": "ustars", + "fee_account": "stars1xek55lr3hzchhdp5z0t0xgzhcf0362vz54teqgne50f0ysgn9yrshdhd0z", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "FeeSplit", + "label": "boneSTARS", + "marketing": { + "project": "https://backbonelabs.io", + "description": "The GraveDigger is the Liquid Staking Product of BackBone Labs. It's liquid staking derivative (LSD) is boneSTARS.", + "marketing": "stars1kdtdg0lvy8asxn8clnjfpusuvf93zuknxrad8g" + } +} \ No newline at end of file diff --git a/init/harpoon_hub-tf_init.json b/init/harpoon_hub-tf_init.json new file mode 100644 index 00000000..1c416e94 --- /dev/null +++ b/init/harpoon_hub-tf_init.json @@ -0,0 +1,17 @@ +{ + "owner": "kujira16q6p6nkzc0525qnnrdd93urxv6pr8zhcmpcu7g", + "epoch_period": 259200, + "unbond_period": 1209600, + "validators": [ + "kujiravaloper1pshqems6hdka48gc56r2ykshyaarkt40hl0rlh", + "kujiravaloper16q6p6nkzc0525qnnrdd93urxv6pr8zhcu5t0z8", + "kujiravaloper1e9rm4nszmfg3fdhrw6s9j69stqddk7ga2x84yf" + ], + "denom": "ukuji", + "steak_denom": "boneKuji", + "fee_account": "kujira16q6p6nkzc0525qnnrdd93urxv6pr8zhcmpcu7g", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "Wallet", + "kuji_token_factory": true +} \ No newline at end of file diff --git a/init/injective_hub-tf_init-mainnet.json b/init/injective_hub-tf_init-mainnet.json new file mode 100644 index 00000000..719b67c8 --- /dev/null +++ b/init/injective_hub-tf_init-mainnet.json @@ -0,0 +1,16 @@ +{ + "owner": "inj1d3yya2s4cejxfe5et9djq38qgtvlsmn9nqjeu9", + "epoch_period": 259200, + "unbond_period": 1814400, + "validators": [ + "injvaloper15vlkdnu2c0k0gaclgycnyjm7c5f3hsde034f5p", + "injvaloper1p66ez0ywzssuq26f7056mx3ydgvkfqw8lf93e8" + ], + "denom": "inj", + "steak_denom": "bINJ", + "fee_account": "inj1rckxty603r8feyzzrgzdyp0y055k8htr90qqtc", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "FeeSplit", + "token_factory": "Injective" +} \ No newline at end of file diff --git a/init/injective_hub-tf_init.json b/init/injective_hub-tf_init.json new file mode 100644 index 00000000..2e80b327 --- /dev/null +++ b/init/injective_hub-tf_init.json @@ -0,0 +1,17 @@ +{ + "owner": "inj1d3yya2s4cejxfe5et9djq38qgtvlsmn9nqjeu9", + "epoch_period": 259200, + "unbond_period": 1814400, + "validators": [ + "injvaloper1t0q28eyvuvdp6aw8rdgxewgw29qlaskm5dhaf2", + "injvaloper156t3yxd4udv0h9gwagfcmwnmm3quy0nph7tyh5", + "injvaloper1kk523rsm9pey740cx4plalp40009ncs0wrchfe" + ], + "denom": "inj", + "steak_denom": "bINJ", + "fee_account": "inj1z7s6eqeql5r23gptjrfhvks3z79vwweuezk77g", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "FeeSplit", + "token_factory": "Injective" +} \ No newline at end of file diff --git a/init/juno_hub_init.json b/init/juno_hub_init.json new file mode 100644 index 00000000..8a123ac0 --- /dev/null +++ b/init/juno_hub_init.json @@ -0,0 +1,28 @@ +{ + "cw20_code_id": 2241, + "owner": "juno1lu92zj8q6cmrptu09rp3343x9969r9qrt84g0e", + "name": "bJuno Token", + "symbol": "bJuno", + "decimals": 6, + "epoch_period": 345600, + "unbond_period": 2419200, + "validators": [ + "junovaloper1yy3tnegzmkdcm7czzcy3flw5z0zyr9vkkxrfse", + "junovaloper1083svrca4t350mphfv9x45wq9asrs60cpqzg0y", + "junovaloper1wd02ktcvpananlvd9u6jm3x3ap3vmw59jv9vez", + "junovaloper1dfeenydr9f2vggp8m7jpalvy8jv7fdndncedrm", + "junovaloper1wj4jhrll90knap3vt8z4qs5rrm9cla2kpc5944", + "junovaloper10wxn2lv29yqnw2uf4jf439kwy5ef00qdelfp7r" + ], + "denom": "ujuno", + "fee_account": "juno167yrnjppr034exsj940q39kfc3nmdg36jtfv63tymyzlyhxg4a3snesqhv", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "FeeSplit", + "label": "bJuno", + "marketing": { + "project": "https://backbonelabs.io", + "description": "The GraveDigger is the Liquid Staking Product of BackBone Labs. It's liquid staking derivative (LSD) is bJuno.", + "marketing": "juno1lu92zj8q6cmrptu09rp3343x9969r9qrt84g0e" + } +} \ No newline at end of file diff --git a/init/kaiyo_hub-tf_init.json b/init/kaiyo_hub-tf_init.json new file mode 100644 index 00000000..47b30148 --- /dev/null +++ b/init/kaiyo_hub-tf_init.json @@ -0,0 +1,15 @@ +{ + "owner": "kujira1lu92zj8q6cmrptu09rp3343x9969r9qrva5t90", + "epoch_period": 259200, + "unbond_period": 1209600, + "validators": [ + "kujiravaloper1670dvuv348eynr9lsmdrhqu3g7vpmzx9ugf8fk" + ], + "denom": "ukuji", + "steak_denom": "boneKuji", + "fee_account": "kujira1lu92zj8q6cmrptu09rp3343x9969r9qrva5t90", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "Wallet", + "token_factory": "Kuji" +} \ No newline at end of file diff --git a/init/migaloo_hub-tf_init.json b/init/migaloo_hub-tf_init.json new file mode 100644 index 00000000..003c51b3 --- /dev/null +++ b/init/migaloo_hub-tf_init.json @@ -0,0 +1,17 @@ +{ + "owner": "migaloo1lu92zj8q6cmrptu09rp3343x9969r9qrsplfat", + "epoch_period": 259200, + "unbond_period": 1814400, + "validators": [ + "migaloovaloper1670dvuv348eynr9lsmdrhqu3g7vpmzx94s460k", + "migaloovaloper1m9s6jkkt3fnt3qzx5htrsaxd6xhufyvtq4l7ps", + "migaloovaloper14ts0j42qkpr43a3tgxr7zz6l6zdf7hdeaayydd" + ], + "denom": "uwhale", + "steak_denom": "boneWhale", + "fee_account": "migaloo1wug8sewp6cedgkmrmvhl3lf3tulagm9hnvy8p0rppz9yjw0g4wtqvk723g", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "FeeSplit", + "kuji_token_factory": false +} \ No newline at end of file diff --git a/init/migaloo_hub_init.json b/init/migaloo_hub_init.json new file mode 100644 index 00000000..7ba886d7 --- /dev/null +++ b/init/migaloo_hub_init.json @@ -0,0 +1,25 @@ +{ + "cw20_code_id": 3, + "owner": "migaloo1lu92zj8q6cmrptu09rp3343x9969r9qrsplfat", + "name": "bWHALE Token", + "symbol": "bWHALE", + "decimals": 6, + "epoch_period": 259200, + "unbond_period": 1814400, + "validators": [ + "migaloovaloper1670dvuv348eynr9lsmdrhqu3g7vpmzx94s460k", + "migaloovaloper1m9s6jkkt3fnt3qzx5htrsaxd6xhufyvtq4l7ps", + "migaloovaloper14ts0j42qkpr43a3tgxr7zz6l6zdf7hdeaayydd" + ], + "denom": "uwhale", + "fee_account": "migaloo1wug8sewp6cedgkmrmvhl3lf3tulagm9hnvy8p0rppz9yjw0g4wtqvk723g", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "FeeSplit", + "label": "boneWHALE", + "marketing": { + "project": "https://backbonelabs.io", + "description": "The GraveDigger is the Liquid Staking Product of BackBone Labs. It's liquid staking derivative (LSD) is boneWHALE.", + "marketing": "migaloo1lu92zj8q6cmrptu09rp3343x9969r9qrsplfat" + } +} \ No newline at end of file diff --git a/init/narwhal_hub-tf_init.json b/init/narwhal_hub-tf_init.json new file mode 100644 index 00000000..37bfa39e --- /dev/null +++ b/init/narwhal_hub-tf_init.json @@ -0,0 +1,17 @@ +{ + "owner": "migaloo1wpayju4jcn2mhv6yewclf6rcq9fyqzvavppjal", + "epoch_period": 259200, + "unbond_period": 300, + "validators": [ + "migaloovaloper1jt9w26mpxxjsk63mvd4m2ynj0af09csluyns75", + "migaloovaloper1wpayju4jcn2mhv6yewclf6rcq9fyqzva7s97l5", + "migaloovaloper1xzern65ppact6sgh4htllnr8pg9dpnc8uj5vuu" + ], + "denom": "uwhale", + "steak_denom": "boneWhale", + "fee_account": "migaloo1wpayju4jcn2mhv6yewclf6rcq9fyqzvavppjal", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "Wallet", + "token_factory": "Osmosis" +} \ No newline at end of file diff --git a/init/osmo-test_hub-tf_init.json b/init/osmo-test_hub-tf_init.json new file mode 100644 index 00000000..ae251666 --- /dev/null +++ b/init/osmo-test_hub-tf_init.json @@ -0,0 +1,15 @@ +{ + "owner": "osmo1lu92zj8q6cmrptu09rp3343x9969r9qr4w9r7h", + "epoch_period": 259200, + "unbond_period": 432000, + "validators": [ + "osmovaloper1qjy5g20ksjq9qgfgmru0aehex6lxhtee5whfcy" + ], + "denom": "uosmo", + "steak_denom": "boneOsmo", + "fee_account": "osmo1lu92zj8q6cmrptu09rp3343x9969r9qr4w9r7h", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "Wallet", + "token_factory": "Osmosis" +} \ No newline at end of file diff --git a/init/osmo-tf_init.json b/init/osmo-tf_init.json new file mode 100644 index 00000000..472c46f5 --- /dev/null +++ b/init/osmo-tf_init.json @@ -0,0 +1,16 @@ +{ + "owner": "osmo1lu92zj8q6cmrptu09rp3343x9969r9qr4w9r7h", + "epoch_period": 259200, + "unbond_period": 1209600, + "validators": [ + "osmovaloper14ts0j42qkpr43a3tgxr7zz6l6zdf7hdes5jpma", + "osmovaloper135p8cjwa3hnnnksjxchda0a6339z9zm7cudyxn" + ], + "denom": "uosmo", + "steak_denom": "boneOsmo", + "fee_account": "osmo1ctrfxgxdjqgd0usaepzdan2razpd3sge7jvckakh036yzm24ymgsnktjf8", + "fee_amount": "0.10", + "max_fee_amount": "0.10", + "fee_account_type": "Wallet", + "token_factory": "Osmosis" +} \ No newline at end of file diff --git a/justfile b/justfile new file mode 100644 index 00000000..d73be2f9 --- /dev/null +++ b/justfile @@ -0,0 +1,18 @@ +check: + cargo check --target wasm32-unknown-unknown --lib + +clippy: + cargo +nightly clippy --tests + +format: + cargo +nightly fmt + +test: + cargo test + +optimize: + docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + --platform linux/amd64 \ + cosmwasm/optimizer:0.16.0 diff --git a/packages/steak/Cargo.toml b/packages/steak/Cargo.toml index bbdd5009..2c6e8364 100644 --- a/packages/steak/Cargo.toml +++ b/packages/steak/Cargo.toml @@ -1,16 +1,18 @@ [package] -name = "steak" -version = "2.0.0" +name = "pfc-steak" +version = "3.0.20" authors = ["larry ", "PFC "] edition = "2018" -description = "Liquid staking protocol for the cosmos" +description = "Liquid steaking protocol for the cosmos" license = "GPL-3.0-or-later" -homepage = "https://steak.club" -repository = "https://github.com/st4k3h0us3/steak-contracts" +homepage = "https://liquidsteaking.app" +repository = "https://github.com/PFC-developer/steak-contracts" [dependencies] -cosmwasm-std = "1.0" -cw20 = "0.13" -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } +cosmwasm-std = { version = "1.5.8", features = ["ibc3"] } +cosmwasm-schema = "1.5.8" +cw20 = "1.1.2" +cw20-base = { version = "1.1.2", features = ["library"] } +schemars = "0.8.21" +serde = { version = "1.0.210", default-features = false, features = ["derive"] } diff --git a/packages/steak/src/hub.rs b/packages/steak/src/hub.rs index b793be12..d5e71f32 100644 --- a/packages/steak/src/hub.rs +++ b/packages/steak/src/hub.rs @@ -1,8 +1,24 @@ -use cosmwasm_std::{to_binary, Addr, Coin, CosmosMsg, Decimal, Empty, StdResult, Uint128, WasmMsg}; +use std::{fmt::Display, str::FromStr}; + +use cosmwasm_std::{ + Addr, Binary, Coin, CosmosMsg, Decimal, Empty, StdResult, Uint128, WasmMsg, to_json_binary, +}; use cw20::Cw20ReceiveMsg; +use cw20_base::msg::InstantiateMarketingInfo as Cw20InstantiateMarketingInfo; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub enum Cw20HookMsg { + Distribute {}, + Transfer {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub enum EnterpriseCw20HookMsg { + Distribute {}, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InstantiateMsg { /// Code ID of the CW20 token contract @@ -21,6 +37,21 @@ pub struct InstantiateMsg { pub unbond_period: u64, /// Initial set of validators who will receive the delegations pub validators: Vec, + /// denomination of coins to steak (uXXXX) + pub denom: String, + /// type of fee account + pub fee_account_type: String, + /// Fee Account to send fees too + pub fee_account: String, + /// Fee "1.00 = 100%" + pub fee_amount: Decimal, + /// Max Fee "1.00 = 100%" + pub max_fee_amount: Decimal, + /// label for the CW20 token we create + pub label: Option, + /// Marketing info for the CW20 we create + pub marketing: Option, + pub dust_collector: Option, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -31,11 +62,20 @@ pub enum ExecuteMsg { /// Bond specified amount of Luna Bond { receiver: Option, + exec_msg: Option, + }, + /// Bond specified amount of Luna, just minting it directly (cw-20 version only) + BondEx { + receiver: Option, }, /// Withdraw Luna that have finished unbonding in previous batches WithdrawUnbonded { receiver: Option, }, + /// Withdraw Luna that has finished unbonding in previous batches, for given address + WithdrawUnbondedAdmin { + address: String, + }, /// Add a validator to the whitelist; callable by the owner AddValidator { validator: String, @@ -44,6 +84,21 @@ pub enum ExecuteMsg { RemoveValidator { validator: String, }, + /// Remove a validator from the whitelist; callable by the owner. Does not undelegate. use for + /// typos + RemoveValidatorEx { + validator: String, + }, + + /// Pause a validator from accepting new delegations + PauseValidator { + validator: String, + }, + /// Unpause a validator from accepting new delegations + UnPauseValidator { + validator: String, + }, + /// Transfer ownership to another account; will not take effect unless the new owner accepts TransferOwnership { new_owner: String, @@ -53,16 +108,51 @@ pub enum ExecuteMsg { /// Claim staking rewards, swap all for Luna, and restake Harvest {}, /// Use redelegations to balance the amounts of Luna delegated to validators - Rebalance {}, + Rebalance { + minimum: Uint128, + }, + /// redelegate stake from one validator to another + Redelegate { + validator_from: String, + validator_to: String, + }, /// Update Luna amounts in unbonding batches to reflect any slashing or rounding errors Reconcile {}, /// Submit the current pending batch of unbonding requests to be unbonded SubmitBatch {}, + /// Set unbond period + SetUnbondPeriod { + unbond_period: u64, + }, + + /// Transfer Fee collection account to another account + TransferFeeAccount { + fee_account_type: String, + new_fee_account: String, + }, + /// Update fee collection amount + UpdateFee { + new_fee: Decimal, + }, /// Callbacks; can only be invoked by the contract itself Callback(CallbackMsg), + // Set The Duster. + SetDustCollector { + dust_collector: Option, + }, + /// Collect the Dust + CollectDust { + max_tokens: u32, + }, + /// Return the Dust in shiny 'base denom' + ReturnDenom {}, + /// admin: setBaseDenom - in case you muck up the init and need to change it + SetBaseDenom { + new_denom: String, + }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ReceiveMsg { /// Submit an unbonding request to the current unbonding queue; automatically invokes `unbond` @@ -72,7 +162,7 @@ pub enum ReceiveMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum CallbackMsg { /// Following the swaps, stake the Luna acquired to the whitelisted validators @@ -83,13 +173,13 @@ impl CallbackMsg { pub fn into_cosmos_msg(&self, contract_addr: &Addr) -> StdResult { Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: contract_addr.to_string(), - msg: to_binary(&ExecuteMsg::Callback(self.clone()))?, + msg: to_json_binary(&ExecuteMsg::Callback(self.clone()))?, funds: vec![], })) } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { /// The contract's configurations. Response: `ConfigResponse` @@ -98,22 +188,26 @@ pub enum QueryMsg { State {}, /// The current batch on unbonding requests pending submission. Response: `PendingBatch` PendingBatch {}, + /// list of batches with reconciled == false + Unreconciled {}, /// Query an individual batch that has previously been submitted for unbonding but have not yet /// fully withdrawn. Response: `Batch` PreviousBatch(u64), - /// Enumerate all previous batches that have previously been submitted for unbonding but have not - /// yet fully withdrawn. Response: `Vec` + /// Enumerate all previous batches that have previously been submitted for unbonding but have + /// not yet fully withdrawn. Response: `Vec` PreviousBatches { start_after: Option, limit: Option, }, - /// Enumerate all outstanding unbonding requests in a given batch. Response: `Vec` + /// Enumerate all outstanding unbonding requests in a given batch. Response: + /// `Vec` UnbondRequestsByBatch { id: u64, start_after: Option, limit: Option, }, - /// Enumreate all outstanding unbonding requests from given a user. Response: `Vec` + /// Enumerate all outstanding unbonding requests from given a user. Response: + /// `Vec` UnbondRequestsByUser { user: String, start_after: Option, @@ -121,7 +215,7 @@ pub enum QueryMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] pub struct ConfigResponse { /// Account who can call certain privileged functions pub owner: String, @@ -133,23 +227,36 @@ pub struct ConfigResponse { pub epoch_period: u64, /// The staking module's unbonding time, in seconds pub unbond_period: u64, - /// Initial set of validators who will receive the delegations + /// denomination of coins to steak (uXXXX) + pub denom: String, + /// type of account to send the fees too + pub fee_type: String, + /// Fee Account to send fees too + pub fee_account: String, + /// Fee "1.00 = 100%" + pub fee_rate: Decimal, + /// Max Fee "1.00 = 100%" + pub max_fee_rate: Decimal, + /// Set of validators who will receive the delegations pub validators: Vec, + pub paused_validators: Vec, + pub dust_collector: Option, + pub token_factory: Option, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] pub struct StateResponse { /// Total supply to the Steak token pub total_usteak: Uint128, - /// Total amount of uluna staked - pub total_uluna: Uint128, - /// The exchange rate between usteak and uluna, in terms of uluna per usteak + /// Total amount of native staked + pub total_native: Uint128, + /// The exchange rate between usteak and native, in terms of native per usteak pub exchange_rate: Decimal, /// Staking rewards currently held by the contract that are ready to be reinvested pub unlocked_coins: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] pub struct PendingBatch { /// ID of this batch pub id: u64, @@ -159,7 +266,7 @@ pub struct PendingBatch { pub est_unbond_start_time: u64, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] pub struct Batch { /// ID of this batch pub id: u64, @@ -167,13 +274,13 @@ pub struct Batch { pub reconciled: bool, /// Total amount of shares remaining this batch. Each `usteak` burned = 1 share pub total_shares: Uint128, - /// Amount of `uluna` in this batch that have not been claimed - pub uluna_unclaimed: Uint128, + /// Amount of `denom` in this batch that have not been claimed + pub amount_unclaimed: Uint128, /// Estimated time when this batch will finish unbonding pub est_unbond_end_time: u64, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] pub struct UnbondRequest { /// ID of the batch pub id: u64, @@ -183,7 +290,7 @@ pub struct UnbondRequest { pub shares: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] pub struct UnbondRequestsByBatchResponseItem { /// The user's address pub user: String, @@ -200,7 +307,7 @@ impl From for UnbondRequestsByBatchResponseItem { } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] pub struct UnbondRequestsByUserResponseItem { /// ID of the batch pub id: u64, @@ -218,3 +325,31 @@ impl From for UnbondRequestsByUserResponseItem { } pub type MigrateMsg = Empty; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Copy, JsonSchema)] +pub enum FeeType { + Wallet, + FeeSplit, +} + +impl FromStr for FeeType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "Wallet" => Ok(FeeType::Wallet), + "FeeSplit" => Ok(FeeType::FeeSplit), + _ => Err(()), + } + } +} + +impl Display for FeeType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = match &self { + FeeType::Wallet => String::from("Wallet"), + FeeType::FeeSplit => String::from("FeeSplit"), + }; + write!(f, "{}", str) + } +} diff --git a/packages/steak/src/hub_tf.rs b/packages/steak/src/hub_tf.rs new file mode 100644 index 00000000..fee4d354 --- /dev/null +++ b/packages/steak/src/hub_tf.rs @@ -0,0 +1,167 @@ +use std::{fmt::Display, str::FromStr}; + +use cosmwasm_schema::cw_serde; +// +use cosmwasm_std::{Decimal, Uint128}; + +use crate::hub::CallbackMsg; + +#[cw_serde] +pub enum TokenFactoryType { + CosmWasm = 1, + Kujira = 2, + Injective = 3, + Osmosis = 4, +} +impl Display for TokenFactoryType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let str = match &self { + TokenFactoryType::CosmWasm => String::from("CosmWasm"), + TokenFactoryType::Kujira => String::from("Kujira"), + TokenFactoryType::Injective => String::from("Injective"), + TokenFactoryType::Osmosis => String::from("Osmosis"), + }; + write!(f, "{}", str) + } +} +impl FromStr for TokenFactoryType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "CosmWasm" => Ok(TokenFactoryType::CosmWasm), + "Kujira" => Ok(TokenFactoryType::Kujira), + "Injective" => Ok(TokenFactoryType::Injective), + "Osmosis" => Ok(TokenFactoryType::Osmosis), + _ => Err(()), + } + } +} + +#[cw_serde] +pub struct InstantiateMsg { + /// Account who can call certain privileged functions + pub owner: String, + /// How often the un-bonding queue is to be executed, in seconds + pub epoch_period: u64, + /// The staking module's un-bonding time, in seconds + pub unbond_period: u64, + /// Initial set of validators who will receive the delegations + pub validators: Vec, + /// denomination of coins to steak (uXXXX) + pub denom: String, + /// denomination of the steak token (eg steakLuna) + pub steak_denom: String, + /// type of fee account + pub fee_account_type: String, + /// Fee Account to send fees too + pub fee_account: String, + /// Fee "1.00 = 100%" + pub fee_amount: Decimal, + /// Max Fee "1.00 = 100%" + pub max_fee_amount: Decimal, + // different chains have different token factory implementations + pub token_factory: String, + /// The Dust collector contract + pub dust_collector: Option, +} + +#[cw_serde] +pub enum ExecuteMsg { + /// Bond specified amount of Luna + Bond { + receiver: Option, + exec_msg: Option, + }, + /// Bond specified amount of Luna + Unbond { + receiver: Option, + }, + + /// Withdraw Luna that have finished un-bonding in previous batches + WithdrawUnbonded { + receiver: Option, + }, + /// Withdraw Luna that has finished unbonding in previous batches, for given address + WithdrawUnbondedAdmin { + address: String, + }, + /// Add a validator to the whitelist; callable by the owner + AddValidator { + validator: String, + }, + /// Remove a validator from the whitelist; callable by the owner + RemoveValidator { + validator: String, + }, + /// Remove a validator from the whitelist; callable by the owner. Does not undelegate. use for + /// typos + RemoveValidatorEx { + validator: String, + }, + + /// Pause a validator from accepting new delegations + PauseValidator { + validator: String, + }, + /// Unpause a validator from accepting new delegations + UnPauseValidator { + validator: String, + }, + + /// Transfer ownership to another account; will not take effect unless the new owner accepts + TransferOwnership { + new_owner: String, + }, + /// Accept an ownership transfer + AcceptOwnership {}, + /// Claim staking rewards, swap all for Luna, and restake + Harvest {}, + /// Use redelegations to balance the amounts of Luna delegated to validators + Rebalance { + minimum: Uint128, + }, + /// redelegate stake from one validator to another + Redelegate { + validator_from: String, + validator_to: String, + }, + /// Update Luna amounts in unbonding batches to reflect any slashing or rounding errors + Reconcile {}, + /// Submit the current pending batch of unbonding requests to be unbonded + SubmitBatch {}, + /// Set unbond period + SetUnbondPeriod { + unbond_period: u64, + }, + + /// Transfer Fee collection account to another account + TransferFeeAccount { + fee_account_type: String, + new_fee_account: String, + }, + /// Update fee collection amount + UpdateFee { + new_fee: Decimal, + }, + /// Callbacks; can only be invoked by the contract itself + Callback(CallbackMsg), + /// Set Dust Collector Contract + SetDustCollector { + dust_collector: Option, + }, + /// Collect the Dust + CollectDust { + max_tokens: u32, + }, + /// Return the Dust in shiny 'base denom' + ReturnDenom {}, + /// admin: setBaseDenom - in case you muck up the init and need to change it + SetBaseDenom { + new_denom: String, + }, + /// change tokenfactory type (ADMIN only) + ChangeTokenFactory { + token_factory_type: String, + }, +} diff --git a/packages/steak/src/lib.rs b/packages/steak/src/lib.rs index 2dbca3b4..6f466d32 100644 --- a/packages/steak/src/lib.rs +++ b/packages/steak/src/lib.rs @@ -1 +1,64 @@ pub mod hub; +pub mod hub_tf; + +// this was copied from eris-staking's branch of STEAK. +// +mod decimal_checked_ops { + use std::{convert::TryInto, str::FromStr}; + + use cosmwasm_std::{Decimal, Decimal256, Fraction, OverflowError, StdError, Uint128, Uint256}; + + // pub trait Decimal256CheckedOps { + // fn to_decimal(self) -> Result; + // } + + // impl Decimal256CheckedOps for Decimal256 { + // fn to_decimal(self) -> Result { + // let U256(ref arr) = self.0; + // if arr[2] == 0u64 || arr[3] == 0u64 { + // return Err(StdError::generic_err( + // "overflow error by casting decimal256 to decimal", + // )); + // } + // Decimal::from_str(&self.to_string()) + // } + // } + + pub trait DecimalCheckedOps { + fn checked_add(self, other: Decimal) -> Result; + fn checked_mul_uint(self, other: Uint128) -> Result; + fn to_decimal256(self) -> Decimal256; + } + + impl DecimalCheckedOps for Decimal { + fn checked_add(self, other: Decimal) -> Result { + self.numerator() + .checked_add(other.numerator()) + .map(|_| self + other) + .map_err(StdError::overflow) + } + + fn checked_mul_uint(self, other: Uint128) -> Result { + if self.is_zero() || other.is_zero() { + return Ok(Uint128::zero()); + } + let multiply_ratio = + other.full_mul(self.numerator()) / Uint256::from(self.denominator()); + if multiply_ratio > Uint256::from(Uint128::MAX) { + Err(StdError::overflow(OverflowError::new( + cosmwasm_std::OverflowOperation::Mul, + self, + other, + ))) + } else { + Ok(multiply_ratio.try_into().unwrap()) + } + } + + fn to_decimal256(self) -> Decimal256 { + Decimal256::from_str(&self.to_string()).unwrap() + } + } +} + +pub use decimal_checked_ops::DecimalCheckedOps; diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..023436f9 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,24 @@ +# https://rust-lang.github.io/rustfmt +format_code_in_doc_comments = true # nightly +group_imports = "StdExternalCrate" # nightly +imports_granularity = "Crate" # nightly +match_block_trailing_comma = true +max_width = 100 +use_small_heuristics = "Off" + +edition = "2021" +style_edition = "2024" + +comment_width = 100 + +error_on_line_overflow = true +format_macro_bodies = true +format_macro_matchers = true +format_strings = true +newline_style = "Unix" +reorder_impl_items = true +use_field_init_shorthand = true + +wrap_comments = true + + diff --git a/scripts/.eslintrc.json b/scripts/.eslintrc.json deleted file mode 100644 index 93c4a4e3..00000000 --- a/scripts/.eslintrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaVersion": 2020, - "sourceType": "module" - }, - "extends": [ - "plugin:@typescript-eslint/recommended" - ], - "rules": {} -} diff --git a/scripts/1_manage_keys.ts b/scripts/1_manage_keys.ts deleted file mode 100644 index b53e21e3..00000000 --- a/scripts/1_manage_keys.ts +++ /dev/null @@ -1,102 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import * as promptly from "promptly"; -import yargs from "yargs"; -import { hideBin } from "yargs/helpers"; -import * as keystore from "./keystore"; - -async function addKey(keyName: string, keyDir: string, coinType: number) { - if (!fs.existsSync(keyDir)) { - fs.mkdirSync(keyDir, { recursive: true }); - } - - const mnemonic = await promptly.prompt("Enter BIP-39 seed phrase:"); - - const password = await promptly.password("Enter a password to encrypt the key:"); - const repeat = await promptly.password("Repeat the password:"); - if (password != repeat) { - throw new Error("Passwords don't match!"); - } - - const accAddress = keystore.save(keyName, keyDir, mnemonic, coinType, password); - console.log("Success! Address:", accAddress); -} - -function listKeys(keyDir: string) { - fs.readdirSync(keyDir) - .filter((fn) => { - return fn.endsWith(".json"); - }) - .sort() - .forEach((fn) => { - const entity: keystore.Entity = JSON.parse(fs.readFileSync(path.join(keyDir, fn), "utf8")); - console.log(`- name: ${entity.name}`); - console.log(` address: ${entity.address}`); - }); -} - -function removeKey(keyName: string, keyDir: string) { - keystore.remove(keyName, keyDir); - console.log("Success!"); -} - -yargs(hideBin(process.argv)) - .command( - "add ", - "Add a key with the given name", - (yargs) => { - return yargs - .positional("key", { - type: "string", - describe: "name of the key", - demandOption: true, - }) - .option("key-dir", { - type: "string", - describe: "path to the directory where encrypted key files are stored", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }) - .option("coin-type", { - type: "number", - describe: "SLIP-0044 coin type for use in derivation of the private key", - demandOption: false, - default: 118, // Terra = 330, Cosmos = 118 - }); - }, - (argv) => addKey(argv["key"], argv["key-dir"], argv["coin-type"]).catch(console.log) - ) - .command( - "rm ", - "Remove a key of the given name", - (yargs) => { - return yargs - .positional("key", { - type: "string", - describe: "name of the key", - demandOption: true, - }) - .option("key-dir", { - type: "string", - describe: "path to the directory where encrypted key files are stored", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }); - }, - (argv) => removeKey(argv["key"], argv["key-dir"]) - ) - .command( - "ls", - "List all keys", - (yargs) => { - return yargs.option("key-dir", { - type: "string", - describe: "path to the directory where encrypted key files are stored", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }); - }, - (argv) => listKeys(argv["key-dir"]) - ) - .wrap(100) - .parse(); diff --git a/scripts/2_deploy.ts b/scripts/2_deploy.ts deleted file mode 100644 index f8c16221..00000000 --- a/scripts/2_deploy.ts +++ /dev/null @@ -1,84 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import yargs from "yargs/yargs"; -import * as keystore from "./keystore"; -import { - createLCDClient, - createWallet, - waitForConfirm, - storeCodeWithConfirm, - instantiateWithConfirm, -} from "./helpers"; -import { Wallet } from "@terra-money/terra.js"; - -const argv = yargs(process.argv) - .options({ - network: { - type: "string", - demandOption: true, - }, - key: { - type: "string", - demandOption: true, - }, - "key-dir": { - type: "string", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }, - admin: { - type: "string", - demandOption: false, - }, - msg: { - type: "string", - demandOption: true, - }, - "hub-code-id": { - type: "number", - demandOption: false, - }, - "token-code-id": { - type: "number", - demandOption: false, - }, - "hub-binary": { - type: "string", - demandOption: false, - default: "../artifacts/steak_hub.wasm", - }, - "token-binary": { - type: "string", - demandOption: false, - default: "../artifacts/steak_token.wasm", - }, - }) - .parseSync(); - -async function uploadCode(deployer: Wallet, path: string) { - await waitForConfirm(`Upload code ${path}?`); - const codeId = await storeCodeWithConfirm(deployer, path); - console.log(`Code uploaded! ID: ${codeId}`); - return codeId; -} - -(async function () { - const terra = createLCDClient(argv["network"]); - const deployer = await createWallet(terra, argv["key"], argv["key-dir"]); - - const hubCodeId = argv["hub-code-id"] ?? await uploadCode(deployer, path.resolve(argv["hub-binary"])); - const tokenCodeId = argv["token-code-id"] ?? await uploadCode(deployer, path.resolve(argv["token-binary"])); - - const msg = JSON.parse(fs.readFileSync(path.resolve(argv["msg"]), "utf8")); - msg["cw20_code_id"] = tokenCodeId; - - await waitForConfirm("Proceed to deploy contracts?"); - const result = await instantiateWithConfirm( - deployer, - argv["admin"] ?? deployer.key.accAddress, - hubCodeId, - msg - ); - const address = result.logs[0].eventsByType["instantiate_contract"]["contract_address"][0]; - console.log(`Contract instantiated! Address: ${address}`); -})(); diff --git a/scripts/3_migrate.ts b/scripts/3_migrate.ts deleted file mode 100644 index 4e82976f..00000000 --- a/scripts/3_migrate.ts +++ /dev/null @@ -1,69 +0,0 @@ -import * as path from "path"; -import yargs from "yargs/yargs"; -import { MsgMigrateContract } from "@terra-money/terra.js"; -import * as keystore from "./keystore"; -import { - createLCDClient, - createWallet, - waitForConfirm, - sendTxWithConfirm, - storeCodeWithConfirm, -} from "./helpers"; - -const argv = yargs(process.argv) - .options({ - network: { - type: "string", - demandOption: true, - }, - key: { - type: "string", - demandOption: true, - }, - "key-dir": { - type: "string", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }, - "contract-address": { - type: "string", - demandOption: true, - }, - msg: { - type: "string", - demandOption: false, - default: "{}", - }, - "code-id": { - type: "number", - demandOption: false, - }, - binary: { - type: "string", - demandOption: false, - default: "../artifacts/steak_hub.wasm", - }, - }) - .parseSync(); - -(async function () { - const terra = createLCDClient(argv["network"]); - const admin = await createWallet(terra, argv["key"], argv["key-dir"]); - - let codeId = argv["code-id"]; - if (!codeId) { - codeId = await storeCodeWithConfirm(admin, path.resolve(argv["binary"])); - console.log(`Code uploaded! codeId: ${codeId}`); - await waitForConfirm("Proceed to migrate contract?"); - } - - const { txhash } = await sendTxWithConfirm(admin, [ - new MsgMigrateContract( - admin.key.accAddress, - argv["contract-address"], - codeId, - JSON.parse(argv["msg"]) - ), - ]); - console.log(`Contract migrated! Txhash: ${txhash}`); -})(); diff --git a/scripts/4_bond.ts b/scripts/4_bond.ts deleted file mode 100644 index 74c9c28a..00000000 --- a/scripts/4_bond.ts +++ /dev/null @@ -1,49 +0,0 @@ -import yargs from "yargs/yargs"; -import { MsgExecuteContract } from "@terra-money/terra.js"; -import * as keystore from "./keystore"; -import { createLCDClient, createWallet, sendTxWithConfirm } from "./helpers"; - -const argv = yargs(process.argv) - .options({ - network: { - type: "string", - demandOption: true, - }, - key: { - type: "string", - demandOption: true, - }, - "key-dir": { - type: "string", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }, - "hub-address": { - type: "string", - demandOption: true, - }, - amount: { - type: "string", - demandOption: true, - }, - }) - .parseSync(); - -(async function () { - const terra = createLCDClient(argv["network"]); - const user = await createWallet(terra, argv["key"], argv["key-dir"]); - - const { txhash } = await sendTxWithConfirm(user, [ - new MsgExecuteContract( - user.key.accAddress, - argv["hub-address"], - { - bond: {}, - }, - { - uluna: argv["amount"], - } - ), - ]); - console.log(`Success! Txhash: ${txhash}`); -})(); diff --git a/scripts/5_harvest.ts b/scripts/5_harvest.ts deleted file mode 100644 index 0588b39a..00000000 --- a/scripts/5_harvest.ts +++ /dev/null @@ -1,38 +0,0 @@ -import yargs from "yargs/yargs"; -import { MsgExecuteContract } from "@terra-money/terra.js"; -import * as keystore from "./keystore"; -import { createLCDClient, createWallet, sendTxWithConfirm } from "./helpers"; - -const argv = yargs(process.argv) - .options({ - network: { - type: "string", - demandOption: true, - }, - key: { - type: "string", - demandOption: true, - }, - "key-dir": { - type: "string", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }, - "hub-address": { - type: "string", - demandOption: true, - }, - }) - .parseSync(); - -(async function () { - const terra = createLCDClient(argv["network"]); - const worker = await createWallet(terra, argv["key"], argv["key-dir"]); - - const { txhash } = await sendTxWithConfirm(worker, [ - new MsgExecuteContract(worker.key.accAddress, argv["hub-address"], { - harvest: {}, - }), - ]); - console.log(`Success! Txhash: ${txhash}`); -})(); diff --git a/scripts/6_rebalance.ts b/scripts/6_rebalance.ts deleted file mode 100644 index 99caf2a7..00000000 --- a/scripts/6_rebalance.ts +++ /dev/null @@ -1,38 +0,0 @@ -import yargs from "yargs/yargs"; -import { MsgExecuteContract } from "@terra-money/terra.js"; -import * as keystore from "./keystore"; -import { createLCDClient, createWallet, sendTxWithConfirm } from "./helpers"; - -const argv = yargs(process.argv) - .options({ - network: { - type: "string", - demandOption: true, - }, - key: { - type: "string", - demandOption: true, - }, - "key-dir": { - type: "string", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }, - "hub-address": { - type: "string", - demandOption: true, - }, - }) - .parseSync(); - -(async function () { - const terra = createLCDClient(argv["network"]); - const worker = await createWallet(terra, argv["key"], argv["key-dir"]); - - const { txhash } = await sendTxWithConfirm(worker, [ - new MsgExecuteContract(worker.key.accAddress, argv["hub-address"], { - rebalance: {}, - }), - ]); - console.log(`Success! Txhash: ${txhash}`); -})(); diff --git a/scripts/7_queue_unbond.ts b/scripts/7_queue_unbond.ts deleted file mode 100644 index 1488ef85..00000000 --- a/scripts/7_queue_unbond.ts +++ /dev/null @@ -1,52 +0,0 @@ -import yargs from "yargs/yargs"; -import { MsgExecuteContract } from "@terra-money/terra.js"; -import * as keystore from "./keystore"; -import { createLCDClient, createWallet, encodeBase64, sendTxWithConfirm } from "./helpers"; - -const argv = yargs(process.argv) - .options({ - network: { - type: "string", - demandOption: true, - }, - key: { - type: "string", - demandOption: true, - }, - "key-dir": { - type: "string", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }, - "hub-address": { - type: "string", - demandOption: true, - }, - amount: { - type: "string", - demandOption: true, - }, - }) - .parseSync(); - -(async function () { - const terra = createLCDClient(argv["network"]); - const worker = await createWallet(terra, argv["key"], argv["key-dir"]); - - const config: { steak_token: string } = await terra.wasm.contractQuery(argv["hub-address"], { - config: {}, - }); - - const { txhash } = await sendTxWithConfirm(worker, [ - new MsgExecuteContract(worker.key.accAddress, config["steak_token"], { - send: { - contract: argv["hub-address"], - amount: argv["amount"], - msg: encodeBase64({ - queue_unbond: {}, - }), - }, - }), - ]); - console.log(`Success! Txhash: ${txhash}`); -})(); diff --git a/scripts/8_submit_batch.ts b/scripts/8_submit_batch.ts deleted file mode 100644 index 6e2e01f2..00000000 --- a/scripts/8_submit_batch.ts +++ /dev/null @@ -1,38 +0,0 @@ -import yargs from "yargs/yargs"; -import { MsgExecuteContract } from "@terra-money/terra.js"; -import * as keystore from "./keystore"; -import { createLCDClient, createWallet, sendTxWithConfirm } from "./helpers"; - -const argv = yargs(process.argv) - .options({ - network: { - type: "string", - demandOption: true, - }, - key: { - type: "string", - demandOption: true, - }, - "key-dir": { - type: "string", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }, - "hub-address": { - type: "string", - demandOption: true, - }, - }) - .parseSync(); - -(async function () { - const terra = createLCDClient(argv["network"]); - const worker = await createWallet(terra, argv["key"], argv["key-dir"]); - - const { txhash } = await sendTxWithConfirm(worker, [ - new MsgExecuteContract(worker.key.accAddress, argv["hub-address"], { - submit_batch: {}, - }), - ]); - console.log(`Success! Txhash: ${txhash}`); -})(); diff --git a/scripts/9_withdraw_unbonded.ts b/scripts/9_withdraw_unbonded.ts deleted file mode 100644 index e230621e..00000000 --- a/scripts/9_withdraw_unbonded.ts +++ /dev/null @@ -1,38 +0,0 @@ -import yargs from "yargs/yargs"; -import { MsgExecuteContract } from "@terra-money/terra.js"; -import * as keystore from "./keystore"; -import { createLCDClient, createWallet, sendTxWithConfirm } from "./helpers"; - -const argv = yargs(process.argv) - .options({ - network: { - type: "string", - demandOption: true, - }, - key: { - type: "string", - demandOption: true, - }, - "key-dir": { - type: "string", - demandOption: false, - default: keystore.DEFAULT_KEY_DIR, - }, - "hub-address": { - type: "string", - demandOption: true, - }, - }) - .parseSync(); - -(async function () { - const terra = createLCDClient(argv["network"]); - const worker = await createWallet(terra, argv["key"], argv["key-dir"]); - - const { txhash } = await sendTxWithConfirm(worker, [ - new MsgExecuteContract(worker.key.accAddress, argv["hub-address"], { - withdraw_unbonded: {}, - }), - ]); - console.log(`Success! Txhash: ${txhash}`); -})(); diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index 3096c824..00000000 --- a/scripts/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Scripts - -This directory contains scripts to deploy, migrate, or interact with Steak Hub smart contract. - -## How to Use - -Insteall dependencies: - -```bash -cd steak/scripts -npm install -``` - -Import the key to use to sign transactions. You will be prompted to enter the seed phrase and a password to encrypt the private key. By default, the encrypted key will be saved at `steak/scripts/keys/{keyname}.json`. The script also provide commands to list or remove keys. - -```bash -ts-node 1_manage_keys.ts add [--key-dir string] -``` - -To deploy the contract, create a JSON file containing the instantiation message, and use the following command. You will be prompted to enter the password to decrypt the private key. - -```bash -ts-node 2_deploy.ts \ - --network mainnet|testnet|localterra \ - --key keyname \ - --msg /path/to/instantiate_msg.json -``` - -To stake Luna and mint Steak: - -```bash -ts-node 4_bond.ts \ - --network mainnet|testnet|localterra \ - --key keyname \ - --contract-address terra... \ - --amount 1000000 -``` - -Other scripts work similarly to the examples above. diff --git a/scripts/helpers.ts b/scripts/helpers.ts deleted file mode 100644 index 7831cd46..00000000 --- a/scripts/helpers.ts +++ /dev/null @@ -1,114 +0,0 @@ -import * as fs from "fs"; -import * as promptly from "promptly"; -import { - isTxError, - LCDClient, - LocalTerra, - Msg, - MsgInstantiateContract, - MsgStoreCode, - Wallet, -} from "@terra-money/terra.js"; -import * as keystore from "./keystore"; - -const DEFAULT_GAS_SETTINGS = { - gasPrices: "1.25usek", - gasAdjustment: 1.2, -}; - -/** - * @notice Create an `LCDClient` instance based on provided network identifier - */ -export function createLCDClient(network: string): LCDClient { - if (network === "mainnet") { - return new LCDClient({ - chainID: "phoenix-1", - URL: "https://lcd.terra.dev", - }); - } else if (network === "testnet") { - return new LCDClient({ - chainID: "pisco-1", - URL: "https://terra-testnet-api.polkachu.com/", - }); - } else if (network === "localterra") { - return new LocalTerra(); - } else { - throw new Error( - `invalid network: ${network}, must be mainnet|testnet|localterra` - ); - } -} - -/** - * @notice Create a `Wallet` instance by loading the private key stored in the keystore - */ -export async function createWallet( - terra: LCDClient, - keyName: string, - keyDir: string -): Promise { - const password = await promptly.password( - "Enter password to decrypt the key:" - ); - return terra.wallet(keystore.load(keyName, keyDir, password)); -} - -/** - * @notice Pause script execution until user confirms - */ -export async function waitForConfirm(msg: string) { - const proceed = await promptly.confirm(`${msg} [y/N]:`); - if (!proceed) { - console.log("User aborted!"); - process.exit(1); - } -} - -/** - * @notice Same with `sendTransaction`, but requires confirmation for CLI before broadcasting - */ -export async function sendTxWithConfirm(signer: Wallet, msgs: Msg[]) { - const tx = await signer.createAndSignTx({ msgs, ...DEFAULT_GAS_SETTINGS }); - console.log("\n" + JSON.stringify(tx).replace(/\\/g, "") + "\n"); - - await waitForConfirm("Confirm transaction before broadcasting"); - - const result = await signer.lcd.tx.broadcast(tx); - if (isTxError(result)) { - throw new Error(`tx failed! raw log: ${result.raw_log}`); - } - return result; -} - -/** - * @notice Same with `storeCode`, but requires confirmation for CLI before broadcasting - */ -export async function storeCodeWithConfirm(signer: Wallet, filePath: string) { - const code = fs.readFileSync(filePath).toString("base64"); - const result = await sendTxWithConfirm(signer, [ - new MsgStoreCode(signer.key.accAddress, code), - ]); - return parseInt(result.logs[0].eventsByType["store_code"]["code_id"][0]); -} - -/** - * @notice Same with `instantiateContract`, but requires confirmation for CLI before broadcasting - */ -export async function instantiateWithConfirm( - signer: Wallet, - admin: string, - codeId: number, - initMsg: object -) { - const result = await sendTxWithConfirm(signer, [ - new MsgInstantiateContract(signer.key.accAddress, admin, codeId, initMsg), - ]); - return result; -} - -/** - * Encode a JSON object to base64 string - */ -export function encodeBase64(obj: object | string | number) { - return Buffer.from(JSON.stringify(obj)).toString("base64"); -} diff --git a/scripts/keystore.ts b/scripts/keystore.ts deleted file mode 100644 index 5f869bfa..00000000 --- a/scripts/keystore.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import * as crypto from "crypto"; - -import { MnemonicKey, RawKey } from "@terra-money/terra.js"; - -export const DEFAULT_KEY_DIR = path.join(__dirname, "./keys"); - -const KEY_SIZE = 256; -const ITERATIONS = 100; - -export type Entity = { - name: string; - address: string; - cipherText: string; -}; - -function encrypt(plainText: string, password: string): string { - const salt = crypto.randomBytes(16); - const iv = crypto.randomBytes(16); - const key = crypto.pbkdf2Sync(password, salt, ITERATIONS, KEY_SIZE / 8, "sha1"); - - const cipher = crypto.createCipheriv("AES-256-CBC", key, iv); - const encryptedText = Buffer.concat([cipher.update(plainText), cipher.final()]); - - return salt.toString("hex") + iv.toString("hex") + encryptedText.toString("base64"); -} - -function decrypt(cipherText: string, password: string): string { - const salt = Buffer.from(cipherText.slice(0, 32), "hex"); - const iv = Buffer.from(cipherText.slice(32, 64), "hex"); - const key = crypto.pbkdf2Sync(password, salt, ITERATIONS, KEY_SIZE / 8, "sha1"); - - const encrypedText = cipherText.slice(64); - const cipher = crypto.createDecipheriv("AES-256-CBC", key, iv); - const decryptedText = Buffer.concat([cipher.update(encrypedText, "base64"), cipher.final()]); - - return decryptedText.toString(); -} - -export function save( - keyName: string, - keyDir: string, - mnemonic: string, - coinType: number, - password: string -) { - const filePath = path.join(keyDir, `${keyName}.json`); - if (fs.existsSync(filePath)) { - throw new Error(`file ${filePath} already exists!`); - } - - const mnemonicKey = new MnemonicKey({ mnemonic, coinType }); - const privateKey = mnemonicKey.privateKey.toString("hex"); - const cipherText = encrypt(privateKey, password); - - const entity: Entity = { - name: keyName, - address: mnemonicKey.accAddress, - cipherText, - }; - fs.writeFileSync(filePath, JSON.stringify(entity, null, 2)); - - return mnemonicKey.accAddress; -} - -export function load(keyName: string, keyDir: string, password: string): RawKey { - const filePath = path.join(keyDir, `${keyName}.json`); - if (!fs.existsSync(filePath)) { - throw new Error(`file ${filePath} does not exist!`); - } - - const entity: Entity = JSON.parse(fs.readFileSync(filePath, "utf8")); - const privateKey = decrypt(entity.cipherText, password); - const rawKey = new RawKey(Buffer.from(privateKey, "hex")); - - return rawKey; -} - -export function remove(keyName: string, keyDir: string) { - const filePath = path.join(keyDir, `${keyName}.json`); - if (!fs.existsSync(filePath)) { - throw new Error(`file ${filePath} does not exist!`); - } - - fs.unlinkSync(filePath); -} diff --git a/scripts/package-lock.json b/scripts/package-lock.json deleted file mode 100644 index f0cb54c1..00000000 --- a/scripts/package-lock.json +++ /dev/null @@ -1,4182 +0,0 @@ -{ - "name": "scripts", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "dependencies": { - "@terra-money/terra.js": "^3.1.2", - "promptly": "^3.2.0", - "ts-node": "^10.7.0", - "yargs": "^17.4.0" - }, - "devDependencies": { - "@types/promptly": "^3.0.2", - "@types/yargs": "^17.0.10", - "@typescript-eslint/eslint-plugin": "^5.16.0", - "@typescript-eslint/parser": "^5.16.0", - "eslint": "^8.11.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-prettier": "^4.0.0", - "typescript": "^4.6.3" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", - "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", - "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.1", - "globals": "^13.9.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@improbable-eng/grpc-web": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.14.1.tgz", - "integrity": "sha512-XaIYuunepPxoiGVLLHmlnVminUGzBTnXr8Wv7khzmLWbNw4TCwJKX09GSMJlKhu/TRk6gms0ySFxewaETSBqgw==", - "dependencies": { - "browser-headers": "^0.4.1" - }, - "peerDependencies": { - "google-protobuf": "^3.14.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "node_modules/@terra-money/legacy.proto": { - "name": "@terra-money/terra.proto", - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.1.7.tgz", - "integrity": "sha512-NXD7f6pQCulvo6+mv6MAPzhOkUzRjgYVuHZE/apih+lVnPG5hDBU0rRYnOGGofwvKT5/jQoOENnFn/gioWWnyQ==", - "dependencies": { - "google-protobuf": "^3.17.3", - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, - "node_modules/@terra-money/terra.js": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-3.1.2.tgz", - "integrity": "sha512-jEvgujX/gbXzq6aTlYORw4chuk8b46weIArThDmpvQN8c3P3GrcMZYsI7uPUFtoWhcKO8+qI++dWzIIFoNhmMQ==", - "dependencies": { - "@terra-money/legacy.proto": "npm:@terra-money/terra.proto@^0.1.7", - "@terra-money/terra.proto": "^0.2.0-beta.4", - "axios": "^0.26.1", - "bech32": "^2.0.0", - "bip32": "^2.0.6", - "bip39": "^3.0.3", - "bufferutil": "^4.0.3", - "decimal.js": "^10.2.1", - "jscrypto": "^1.0.1", - "readable-stream": "^3.6.0", - "secp256k1": "^4.0.2", - "tmp": "^0.2.1", - "utf-8-validate": "^5.0.5", - "ws": "^7.5.5" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@terra-money/terra.proto": { - "version": "0.2.0-beta.4", - "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.2.0-beta.4.tgz", - "integrity": "sha512-ALwIOpiZgpELvUO27+Lgc4qQz6k5NB58YsUHX2Bf7gocspShRftps5kjj/bovZs/M6AO4J7Qj07QJRBekMinMA==", - "dependencies": { - "@improbable-eng/grpc-web": "^0.14.1", - "google-protobuf": "^3.17.3", - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "node_modules/@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" - }, - "node_modules/@types/promptly": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/promptly/-/promptly-3.0.2.tgz", - "integrity": "sha512-cJFwE7d8GlraY+DJoZ0NhpoJ55slkcbNsGIKMY0H+5h0xaGqXBqXz9zeu+Ey9KfN1UiHQXiIT0GroxyPYMPP/w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz", - "integrity": "sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.17.0.tgz", - "integrity": "sha512-qVstvQilEd89HJk3qcbKt/zZrfBZ+9h2ynpAGlWjWiizA7m/MtLT9RoX6gjtpE500vfIg8jogAkDzdCxbsFASQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.17.0", - "@typescript-eslint/type-utils": "5.17.0", - "@typescript-eslint/utils": "5.17.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.17.0.tgz", - "integrity": "sha512-aRzW9Jg5Rlj2t2/crzhA2f23SIYFlF9mchGudyP0uiD6SenIxzKoLjwzHbafgHn39dNV/TV7xwQkLfFTZlJ4ig==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.17.0", - "@typescript-eslint/types": "5.17.0", - "@typescript-eslint/typescript-estree": "5.17.0", - "debug": "^4.3.2" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.17.0.tgz", - "integrity": "sha512-062iCYQF/doQ9T2WWfJohQKKN1zmmXVfAcS3xaiialiw8ZUGy05Em6QVNYJGO34/sU1a7a+90U3dUNfqUDHr3w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.17.0", - "@typescript-eslint/visitor-keys": "5.17.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.17.0.tgz", - "integrity": "sha512-3hU0RynUIlEuqMJA7dragb0/75gZmwNwFf/QJokWzPehTZousP/MNifVSgjxNcDCkM5HI2K22TjQWUmmHUINSg==", - "dev": true, - "dependencies": { - "@typescript-eslint/utils": "5.17.0", - "debug": "^4.3.2", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.17.0.tgz", - "integrity": "sha512-AgQ4rWzmCxOZLioFEjlzOI3Ch8giDWx8aUDxyNw9iOeCvD3GEYAB7dxWGQy4T/rPVe8iPmu73jPHuaSqcjKvxw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.17.0.tgz", - "integrity": "sha512-X1gtjEcmM7Je+qJRhq7ZAAaNXYhTgqMkR10euC4Si6PIjb+kwEQHSxGazXUQXFyqfEXdkGf6JijUu5R0uceQzg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.17.0", - "@typescript-eslint/visitor-keys": "5.17.0", - "debug": "^4.3.2", - "globby": "^11.0.4", - "is-glob": "^4.0.3", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.17.0.tgz", - "integrity": "sha512-DVvndq1QoxQH+hFv+MUQHrrWZ7gQ5KcJzyjhzcqB1Y2Xes1UQQkTRPUfRpqhS8mhTWsSb2+iyvDW1Lef5DD7vA==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.17.0", - "@typescript-eslint/types": "5.17.0", - "@typescript-eslint/typescript-estree": "5.17.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.17.0.tgz", - "integrity": "sha512-6K/zlc4OfCagUu7Am/BD5k8PSWQOgh34Nrv9Rxe2tBzlJ7uOeJ/h7ugCGDCeEZHT6k2CJBhbk9IsbkPI0uvUkA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.17.0", - "eslint-visitor-keys": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "dependencies": { - "follow-redirects": "^1.14.8" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/bech32": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bip32": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz", - "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", - "dependencies": { - "@types/node": "10.12.18", - "bs58check": "^2.1.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "tiny-secp256k1": "^1.1.3", - "typeforce": "^1.11.5", - "wif": "^2.0.6" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/bip32/node_modules/@types/node": { - "version": "10.12.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" - }, - "node_modules/bip39": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", - "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", - "dependencies": { - "@types/node": "11.11.6", - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" - } - }, - "node_modules/bip39/node_modules/@types/node": { - "version": "11.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", - "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" - }, - "node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, - "node_modules/browser-headers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz", - "integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==" - }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/bufferutil": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", - "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", - "hasInstallScript": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.12.0.tgz", - "integrity": "sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.2.1", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", - "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/espree": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", - "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", - "dev": true, - "dependencies": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", - "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/google-protobuf": { - "version": "3.20.1", - "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.20.1.tgz", - "integrity": "sha512-XMf1+O32FjYIV3CYu6Tuh5PNbfNEU5Xu22X+Xkdb/DUexFlCzhvv7d5Iirm4AOwn8lv4al1YvIhzGrg2j9Zfzw==" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jscrypto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/jscrypto/-/jscrypto-1.0.3.tgz", - "integrity": "sha512-lryZl0flhodv4SZHOqyb1bx5sKcJxj0VBo0Kzb4QMAg3L021IC9uGpl0RCZa+9KJwlRGSK2C80ITcwbe19OKLQ==", - "bin": { - "jscrypto": "bin/cli.js" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, - "node_modules/nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" - }, - "node_modules/node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.1.tgz", - "integrity": "sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==", - "dev": true, - "peer": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/promptly": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/promptly/-/promptly-3.2.0.tgz", - "integrity": "sha512-WnR9obtgW+rG4oUV3hSnNGl1pHm3V1H/qD9iJBumGSmVsSC5HpZOLuu8qdMb6yCItGfT7dcRszejr/5P3i9Pug==", - "dependencies": { - "read": "^1.0.4" - } - }, - "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", - "dependencies": { - "mute-stream": "~0.0.4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "hasInstallScript": true, - "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/tiny-secp256k1": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", - "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", - "hasInstallScript": true, - "dependencies": { - "bindings": "^1.3.0", - "bn.js": "^4.11.8", - "create-hmac": "^1.1.7", - "elliptic": "^6.4.0", - "nan": "^2.13.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", - "dependencies": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typeforce": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", - "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" - }, - "node_modules/typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utf-8-validate": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", - "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", - "hasInstallScript": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", - "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wif": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", - "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", - "dependencies": { - "bs58check": "<3.0.0" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.0.tgz", - "integrity": "sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "engines": { - "node": ">=6" - } - } - }, - "dependencies": { - "@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==" - }, - "@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", - "requires": { - "@cspotcode/source-map-consumer": "0.8.0" - } - }, - "@eslint/eslintrc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", - "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.1", - "globals": "^13.9.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - } - }, - "@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@improbable-eng/grpc-web": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.14.1.tgz", - "integrity": "sha512-XaIYuunepPxoiGVLLHmlnVminUGzBTnXr8Wv7khzmLWbNw4TCwJKX09GSMJlKhu/TRk6gms0ySFxewaETSBqgw==", - "requires": { - "browser-headers": "^0.4.1" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, - "@terra-money/legacy.proto": { - "version": "npm:@terra-money/terra.proto@0.1.7", - "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.1.7.tgz", - "integrity": "sha512-NXD7f6pQCulvo6+mv6MAPzhOkUzRjgYVuHZE/apih+lVnPG5hDBU0rRYnOGGofwvKT5/jQoOENnFn/gioWWnyQ==", - "requires": { - "google-protobuf": "^3.17.3", - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, - "@terra-money/terra.js": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-3.1.2.tgz", - "integrity": "sha512-jEvgujX/gbXzq6aTlYORw4chuk8b46weIArThDmpvQN8c3P3GrcMZYsI7uPUFtoWhcKO8+qI++dWzIIFoNhmMQ==", - "requires": { - "@terra-money/legacy.proto": "npm:@terra-money/terra.proto@^0.1.7", - "@terra-money/terra.proto": "^0.2.0-beta.4", - "axios": "^0.26.1", - "bech32": "^2.0.0", - "bip32": "^2.0.6", - "bip39": "^3.0.3", - "bufferutil": "^4.0.3", - "decimal.js": "^10.2.1", - "jscrypto": "^1.0.1", - "readable-stream": "^3.6.0", - "secp256k1": "^4.0.2", - "tmp": "^0.2.1", - "utf-8-validate": "^5.0.5", - "ws": "^7.5.5" - } - }, - "@terra-money/terra.proto": { - "version": "0.2.0-beta.4", - "resolved": "https://registry.npmjs.org/@terra-money/terra.proto/-/terra.proto-0.2.0-beta.4.tgz", - "integrity": "sha512-ALwIOpiZgpELvUO27+Lgc4qQz6k5NB58YsUHX2Bf7gocspShRftps5kjj/bovZs/M6AO4J7Qj07QJRBekMinMA==", - "requires": { - "@improbable-eng/grpc-web": "^0.14.1", - "google-protobuf": "^3.17.3", - "long": "^4.0.0", - "protobufjs": "~6.11.2" - } - }, - "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" - }, - "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" - }, - "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" - }, - "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" - }, - "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" - }, - "@types/promptly": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/promptly/-/promptly-3.0.2.tgz", - "integrity": "sha512-cJFwE7d8GlraY+DJoZ0NhpoJ55slkcbNsGIKMY0H+5h0xaGqXBqXz9zeu+Ey9KfN1UiHQXiIT0GroxyPYMPP/w==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/yargs": { - "version": "17.0.10", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz", - "integrity": "sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.17.0.tgz", - "integrity": "sha512-qVstvQilEd89HJk3qcbKt/zZrfBZ+9h2ynpAGlWjWiizA7m/MtLT9RoX6gjtpE500vfIg8jogAkDzdCxbsFASQ==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.17.0", - "@typescript-eslint/type-utils": "5.17.0", - "@typescript-eslint/utils": "5.17.0", - "debug": "^4.3.2", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.2.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.17.0.tgz", - "integrity": "sha512-aRzW9Jg5Rlj2t2/crzhA2f23SIYFlF9mchGudyP0uiD6SenIxzKoLjwzHbafgHn39dNV/TV7xwQkLfFTZlJ4ig==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.17.0", - "@typescript-eslint/types": "5.17.0", - "@typescript-eslint/typescript-estree": "5.17.0", - "debug": "^4.3.2" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.17.0.tgz", - "integrity": "sha512-062iCYQF/doQ9T2WWfJohQKKN1zmmXVfAcS3xaiialiw8ZUGy05Em6QVNYJGO34/sU1a7a+90U3dUNfqUDHr3w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.17.0", - "@typescript-eslint/visitor-keys": "5.17.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.17.0.tgz", - "integrity": "sha512-3hU0RynUIlEuqMJA7dragb0/75gZmwNwFf/QJokWzPehTZousP/MNifVSgjxNcDCkM5HI2K22TjQWUmmHUINSg==", - "dev": true, - "requires": { - "@typescript-eslint/utils": "5.17.0", - "debug": "^4.3.2", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.17.0.tgz", - "integrity": "sha512-AgQ4rWzmCxOZLioFEjlzOI3Ch8giDWx8aUDxyNw9iOeCvD3GEYAB7dxWGQy4T/rPVe8iPmu73jPHuaSqcjKvxw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.17.0.tgz", - "integrity": "sha512-X1gtjEcmM7Je+qJRhq7ZAAaNXYhTgqMkR10euC4Si6PIjb+kwEQHSxGazXUQXFyqfEXdkGf6JijUu5R0uceQzg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.17.0", - "@typescript-eslint/visitor-keys": "5.17.0", - "debug": "^4.3.2", - "globby": "^11.0.4", - "is-glob": "^4.0.3", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/utils": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.17.0.tgz", - "integrity": "sha512-DVvndq1QoxQH+hFv+MUQHrrWZ7gQ5KcJzyjhzcqB1Y2Xes1UQQkTRPUfRpqhS8mhTWsSb2+iyvDW1Lef5DD7vA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.17.0", - "@typescript-eslint/types": "5.17.0", - "@typescript-eslint/typescript-estree": "5.17.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.17.0.tgz", - "integrity": "sha512-6K/zlc4OfCagUu7Am/BD5k8PSWQOgh34Nrv9Rxe2tBzlJ7uOeJ/h7ugCGDCeEZHT6k2CJBhbk9IsbkPI0uvUkA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.17.0", - "eslint-visitor-keys": "^3.0.0" - } - }, - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "axios": { - "version": "0.26.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", - "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", - "requires": { - "follow-redirects": "^1.14.8" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "bech32": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", - "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bip32": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz", - "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", - "requires": { - "@types/node": "10.12.18", - "bs58check": "^2.1.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "tiny-secp256k1": "^1.1.3", - "typeforce": "^1.11.5", - "wif": "^2.0.6" - }, - "dependencies": { - "@types/node": { - "version": "10.12.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" - } - } - }, - "bip39": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", - "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", - "requires": { - "@types/node": "11.11.6", - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" - }, - "dependencies": { - "@types/node": { - "version": "11.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", - "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" - } - } - }, - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, - "browser-headers": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/browser-headers/-/browser-headers-0.4.1.tgz", - "integrity": "sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==" - }, - "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", - "requires": { - "base-x": "^3.0.2" - } - }, - "bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "requires": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "bufferutil": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.6.tgz", - "integrity": "sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==", - "requires": { - "node-gyp-build": "^4.3.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==" - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.12.0.tgz", - "integrity": "sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.2.1", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true, - "requires": {} - }, - "eslint-plugin-prettier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", - "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", - "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", - "dev": true, - "requires": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", - "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "google-protobuf": { - "version": "3.20.1", - "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.20.1.tgz", - "integrity": "sha512-XMf1+O32FjYIV3CYu6Tuh5PNbfNEU5Xu22X+Xkdb/DUexFlCzhvv7d5Iirm4AOwn8lv4al1YvIhzGrg2j9Zfzw==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jscrypto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/jscrypto/-/jscrypto-1.0.3.tgz", - "integrity": "sha512-lryZl0flhodv4SZHOqyb1bx5sKcJxj0VBo0Kzb4QMAg3L021IC9uGpl0RCZa+9KJwlRGSK2C80ITcwbe19OKLQ==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, - "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" - }, - "node-gyp-build": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - }, - "pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.1.tgz", - "integrity": "sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==", - "dev": true, - "peer": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "promptly": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/promptly/-/promptly-3.2.0.tgz", - "integrity": "sha512-WnR9obtgW+rG4oUV3hSnNGl1pHm3V1H/qD9iJBumGSmVsSC5HpZOLuu8qdMb6yCItGfT7dcRszejr/5P3i9Pug==", - "requires": { - "read": "^1.0.4" - } - }, - "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", - "requires": { - "mute-stream": "~0.0.4" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "requires": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "tiny-secp256k1": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", - "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", - "requires": { - "bindings": "^1.3.0", - "bn.js": "^4.11.8", - "create-hmac": "^1.1.7", - "elliptic": "^6.4.0", - "nan": "^2.13.2" - } - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "requires": { - "rimraf": "^3.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-node": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", - "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", - "requires": { - "@cspotcode/source-map-support": "0.7.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.0", - "yn": "3.1.1" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typeforce": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", - "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" - }, - "typescript": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", - "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==" - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "utf-8-validate": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.9.tgz", - "integrity": "sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==", - "requires": { - "node-gyp-build": "^4.3.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-compile-cache-lib": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", - "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==" - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wif": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", - "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", - "requires": { - "bs58check": "<3.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "ws": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "requires": {} - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "17.4.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.0.tgz", - "integrity": "sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==", - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==" - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" - } - } -} diff --git a/scripts/package.json b/scripts/package.json deleted file mode 100644 index d60ba38a..00000000 --- a/scripts/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "dependencies": { - "@terra-money/terra.js": "^3.1.2", - "promptly": "^3.2.0", - "ts-node": "^10.7.0", - "yargs": "^17.4.0" - }, - "devDependencies": { - "@types/promptly": "^3.0.2", - "@types/yargs": "^17.0.10", - "@typescript-eslint/eslint-plugin": "^5.16.0", - "@typescript-eslint/parser": "^5.16.0", - "eslint": "^8.11.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-prettier": "^4.0.0", - "typescript": "^4.6.3" - }, - "engines": { - "node": ">=16" - } -} diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json deleted file mode 100644 index e80fc724..00000000 --- a/scripts/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "allowJs": false, - "strict": true, - "strictNullChecks": true, - "allowUnreachableCode": false, - "allowUnusedLabels": false, - "exactOptionalPropertyTypes": true, - "noEmit": true, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, - "noImplicitReturns": true, - "noPropertyAccessFromIndexSignature": true, - // "noUncheckedIndexedAccess": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "forceConsistentCasingInFileNames": true, - "esModuleInterop": true, - "resolveJsonModule": true - }, - "include": ["**/*.ts"] -} \ No newline at end of file