diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..8d4bc73 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,6 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --example schema" diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml index b5839c5..406f16c 100644 --- a/.github/workflows/rust.yaml +++ b/.github/workflows/rust.yaml @@ -35,8 +35,8 @@ jobs: - name: run tests uses: actions-rs/cargo@v1 with: - command: test - args: --locked + command: unit-test + args: --locked --tests env: RUST_BACKTRACE: 1 clippy: diff --git a/.gitignore b/.gitignore index ea5a67e..5120f8e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ artifacts/ # macOS .DS_Store + +.idea/ +.vscode/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a6dd4a5..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "[rust]": { - "editor.formatOnSave": false - } -} diff --git a/Cargo.lock b/Cargo.lock index 46fd314..caa2fef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + [[package]] name = "base16ct" version = "0.1.1" @@ -31,6 +40,22 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +[[package]] +name = "bigint" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +dependencies = [ + "byteorder", + "crunchy 0.1.6", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "block-buffer" version = "0.9.0" @@ -61,19 +86,28 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +dependencies = [ + "bitflags", +] + [[package]] name = "const-oid" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" [[package]] name = "cosmwasm-crypto" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28376836c7677e1ea6d6656a754582e88b91e544ce22fae42956d5fe5549a958" +checksum = "b1fc6d95cc171e56882d3e90689be1b38fecd31a4e6981129fc4973409de18b9" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -82,18 +116,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb69f4f7a8a4bce68c8fbd3646238fede1e77056e4ea31c5b6bfc37b709eec3" +checksum = "a552716cf87ad173cd6b593fd72bf24fab490e0e5420aa75a227d6817d06b5df" dependencies = [ "syn", ] [[package]] name = "cosmwasm-schema" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a227cfeb9a7152b26a354b1c990e930e962f75fd68f57ab5ae2ef888c8524292" +checksum = "98c025d629589ca5d43fb8ff06decc387a4d01ead14f6b505300777fc6f01e1d" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -104,9 +138,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3626cb42eef870de67f791e873711255325224d86f281bf628c42abd295f3a14" +checksum = "6f09a65d22861a24a1b30eab6aa6759333723629bc2cb568cd22569745e1dcb2" dependencies = [ "proc-macro2", "quote", @@ -115,9 +149,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bf9157d060abbc55152aeadcace799d03dc630575daa66604079a1206cb060" +checksum = "1240e76655f4b858b3fb94befbf48e253e92c6640d9d674a1491d15f10bc4d72" dependencies = [ "base64", "cosmwasm-crypto", @@ -129,7 +163,7 @@ dependencies = [ "serde", "serde-json-wasm", "thiserror", - "uint", + "uint 0.9.4", ] [[package]] @@ -141,6 +175,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" + [[package]] name = "crunchy" version = "0.2.2" @@ -182,6 +222,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-item-set" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05dad9dc2e6e9ab784bb5598d8528f4afe014b7b0ec05e1f466fe2e11aca368c" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.16.0", +] + +[[package]] +name = "cw-storage-plus" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6cf70ef7686e2da9ad7b067c5942cd3e88dd9453f7af42f54557f8af300fb0" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-storage-plus" version = "0.16.0" @@ -193,6 +254,21 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a84c6c1c0acc3616398eba50783934bd6c964bad6974241eaee3460c8f5b26" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.16.0" @@ -201,11 +277,40 @@ checksum = "91398113b806f4d2a8d5f8d05684704a20ffd5968bf87e3473e1973710b884ad" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", + "cw-storage-plus 0.16.0", "schemars", "serde", ] +[[package]] +name = "cw20" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45a8794a5dd33b66af34caee52a7beceb690856adcc1682b6e3db88b2cdee62" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw20-adapter" +version = "1.0.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-item-set", + "cw-storage-plus 0.16.0", + "cw2", + "cw20", + "injective-cosmwasm", + "regex", + "serde", + "thiserror", +] + [[package]] name = "der" version = "0.6.0" @@ -238,9 +343,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -289,7 +394,7 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "digest 0.10.5", + "digest 0.10.6", "ff", "generic-array", "group", @@ -300,22 +405,77 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ethbloom" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3932e82d64d347a045208924002930dc105a138995ccdc1479d0f05f0359f17c" +dependencies = [ + "crunchy 0.2.2", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b054df51e53f253837ea422681215b42823c02824bde982699d0dceecf6165a1" +dependencies = [ + "crunchy 0.2.2", + "ethbloom", + "ethereum-types-serialize", + "fixed-hash", + "serde", + "uint 0.5.0", +] + +[[package]] +name = "ethereum-types-serialize" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1873d77b32bc1891a79dad925f2acbc318ee942b38b9110f9dbc5fbeffcea350" +dependencies = [ + "serde", +] + [[package]] name = "ff" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ "rand_core 0.6.4", "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1a683d1234507e4f3bf2736eeddf0de1dc65996dc0164d57eba0a74bcf29489" +dependencies = [ + "byteorder", + "heapsize", + "rand", + "rustc-hex", + "static_assertions 0.2.5", +] + [[package]] name = "forward_ref" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "generic-array" version = "0.14.6" @@ -357,6 +517,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "heapsize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" +dependencies = [ + "winapi", +] + [[package]] name = "hex" version = "0.4.3" @@ -369,7 +538,53 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", +] + +[[package]] +name = "impl-rlp" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f7a72f11830b52333f36e3b09a288333888bf54380fd0ac0790a3c31ab0f3c5" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e3cae7e99c7ff5a995da2cf78dd0a5383740eda71d98cf7b1910c301ac69b8" +dependencies = [ + "serde", +] + +[[package]] +name = "injective-cosmwasm" +version = "0.1.74" +source = "git+https://github.com/InjectiveLabs/cw-injective.git?branch=dev#f0c00001f04ee004f9c97ecbe012127c7111bc44" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "ethereum-types", + "injective-math", + "schemars", + "serde", + "serde_repr", + "subtle-encoding", +] + +[[package]] +name = "injective-math" +version = "0.1.9" +source = "git+https://github.com/InjectiveLabs/cw-injective.git?branch=dev#f0c00001f04ee004f9c97ecbe012127c7111bc44" +dependencies = [ + "bigint", + "cosmwasm-std", + "ethereum-types", + "schemars", + "serde", + "subtle-encoding", ] [[package]] @@ -396,11 +611,17 @@ version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "once_cell" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "opaque-debug" @@ -436,6 +657,34 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" +dependencies = [ + "cloudabi", + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -451,17 +700,49 @@ dependencies = [ "getrandom", ] +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + [[package]] name = "rfc6979" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88c86280f057430a52f4861551b092a01b419b8eacefc7c995eacb9dc132fe32" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint", "hmac", "zeroize", ] +[[package]] +name = "rlp" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1190dcc8c3a512f1eef5d09bb8c84c7f39e1054e174d1795482e18f5272f2e73" +dependencies = [ + "rustc-hex", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "ryu" version = "1.0.11" @@ -506,6 +787,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" + [[package]] name = "serde" version = "1.0.147" @@ -557,6 +844,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.9.9" @@ -578,7 +876,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -587,7 +885,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", "rand_core 0.6.4", ] @@ -603,28 +901,15 @@ dependencies = [ [[package]] name = "static_assertions" -version = "1.1.0" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" [[package]] -name = "steak" -version = "0.0.0" -dependencies = [ - "cosmwasm-schema", -] - -[[package]] -name = "steak-hub" -version = "0.0.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "cw2", - "steak", - "thiserror", -] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" @@ -632,6 +917,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + [[package]] name = "syn" version = "1.0.103" @@ -663,12 +957,33 @@ dependencies = [ "syn", ] +[[package]] +name = "tiny-keccak" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8a021c69bb74a44ccedb824a046447e2c84a01df9e5c20779750acb38e11b2" +dependencies = [ + "crunchy 0.2.2", +] + [[package]] name = "typenum" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "uint" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "082df6964410f6aa929a61ddfafc997e4f32c62c22490e439ac351cec827f436" +dependencies = [ + "byteorder", + "crunchy 0.2.2", + "heapsize", + "rustc-hex", +] + [[package]] name = "uint" version = "0.9.4" @@ -676,9 +991,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", - "crunchy", + "crunchy 0.2.2", "hex", - "static_assertions", + "static_assertions 1.1.0", ] [[package]] @@ -699,6 +1014,28 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "zeroize" version = "1.5.7" diff --git a/Cargo.toml b/Cargo.toml index 6669f4c..b1adc77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,31 +1,44 @@ [workspace] -members = ["contracts/*", "packages/*"] +members = ["contracts/*"] [workspace.package] -version = "0.0.0" -authors = ["Larry Engineer "] -edition = "2021" -license = "AGPL-v3-or-later" -homepage = "https://larry.engineer" -repository = "https://github.com/steak-enjoyers/cw-template" -documentation = "https://github.com/steak-enjoyers/cw-template#readme" -keywords = ["blockchain", "cosmos", "cosmwasm"] -rust-version = "1.64.0" +version = "1.0.0" +authors = ["Antoni Mysliborski "] +edition = "2021" +license = "AGPL-v3-or-later" +homepage = "https://injectivelabs.org" +repository = "https://github.com/InjectiveLabs/cw20-adapter" +documentation = "https://github.com/InjectiveLabs/cw20-adapter#readme" +keywords = ["blockchain", "cosmos", "cosmwasm", "injective"] +rust-version = "1.64.0" [workspace.dependencies] cosmwasm-schema = "1.1" -cosmwasm-std = "1.1" -cw2 = "0.16" +cosmwasm-std = "1.1" +cw2 = "0.16" +cw20 = "0.16.0" cw-storage-plus = "0.16" -steak = { version = "0.0.0", path = "./packages/steak" } -thiserror = "1.0" +thiserror = "1.0" +cw-item-set = "0.6.0" +regex = "1.7.0" +injective-cosmwasm = "0.1.74" +anyhow = "1.0.66" +secp256k1 = "0.6.2" +rand = "0.4.6" +tiny-keccak = "1.2.1" +serde = "1.0.147" [profile.release] -codegen-units = 1 -debug = false +codegen-units = 1 +debug = false debug-assertions = false -incremental = false -lto = true -opt-level = 3 -overflow-checks = true -rpath = false +incremental = false +lto = true +opt-level = 3 +overflow-checks = true +rpath = false + +[patch.crates-io] +#injective-cosmwasm = { path = "../cw-injective/packages/injective-cosmwasm"} +injective-cosmwasm = { git = "https://github.com/InjectiveLabs/cw-injective.git", branch ="dev" } +#cw20-base = { path = "../cw-plus-inj/contracts/cw20-base" } \ No newline at end of file diff --git a/build_release.sh b/build_release.sh new file mode 100755 index 0000000..9be7559 --- /dev/null +++ b/build_release.sh @@ -0,0 +1,6 @@ +#!/bin/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.12.9 diff --git a/contracts/cw20-adapter/Cargo.toml b/contracts/cw20-adapter/Cargo.toml new file mode 100644 index 0000000..0010baf --- /dev/null +++ b/contracts/cw20-adapter/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "cw20-adapter" +description = "Description of the contract" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +documentation = { workspace = true } +keywords = { workspace = true } +rust-version = { workspace = true } + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw20 = { workspace = true } +cw-storage-plus = { workspace = true } +thiserror = { workspace = true } +cw-item-set = { workspace = true } +regex = { workspace = true } +injective-cosmwasm = { workspace = true } +serde = { workspace = true } \ No newline at end of file diff --git a/contracts/cw20-adapter/README.md b/contracts/cw20-adapter/README.md new file mode 100644 index 0000000..6fdc036 --- /dev/null +++ b/contracts/cw20-adapter/README.md @@ -0,0 +1,48 @@ +# CW-20 Adapter + +Contract that allows exchanging CW-20 tokens for injective-chain issued native tokens (using Token Factory module) and vice-versa. + +## Messages + +### RegisterCw20Contract { addr: Addr } +Registers a new CW-20 contract (addr) that will be handled by the adapter and creates a new +TokenFactory token in format `factory/{adapter_contract}/{cw20_contract}` + +Message must provide enough funds to create new TokenFactory denom (10 inj by default, but caller should query the +chain for the current value) + +### Receive { sender: String, amount: Uint128, msg: Binary }, +Implementation of Receiver CW-20 interface. Should be called by CW-20 contract only!! (never directly). +Sender will contain address that initiated Send method on CW-20 contract +Amount is amount of CW-20 tokens transferred +Msg is ignored + +Upon receiving this message, adapter will: +- check if calling address is registered - if not and contract address has enough funds, it will register it (see above). +- will mint and transfer to a `sender` address (original caller of cw20 send method) `amount` of TF tokens + +### RedeemAndTransfer { recipient: Option } +Will redeem attached TF tokens (will fail if no registered tokens are provided) +and will transfer CW-20 tokens to `recipient`. If recipient is not provided, they will be sent +to the message caller. + +This method uses CW-20 `transfer` method (so it will not notify recipient in any way) + +### RedeemAndSend { recipient: String, submessage: Binary } +Will redeem attached TF tokens (will fail if no registered tokens are provided) +and will send CW-20 tokens to `recipient` contract. Caller may provide optional submessage + +This method uses CW-20 `send` method + +### UpdateMetadata { addr : Addr} +Will query cw20 address (if registered) for metadata and will call setMetadata in the bank module (using TokenFactory +access method) +Warning: this require chain v1.9. Can be called any time + +# Queries + +### RegisteredContracts {} +Return a list of registered CW-20 contracts + +### NewDenomFee {} +Returns a fee required to register a new token-factory denom diff --git a/contracts/cw20-adapter/src/common.rs b/contracts/cw20-adapter/src/common.rs new file mode 100644 index 0000000..4156480 --- /dev/null +++ b/contracts/cw20-adapter/src/common.rs @@ -0,0 +1,127 @@ +use crate::error::ContractError; +use crate::state::CW20_CONTRACTS; +use cosmwasm_std::{Addr, Coin, CosmosMsg, DepsMut, Env, QuerierWrapper, StdResult}; +use cw20::{Cw20QueryMsg, TokenInfoResponse}; +use injective_cosmwasm::{create_new_denom_msg, InjectiveMsgWrapper, InjectiveQuerier, InjectiveQueryWrapper}; +use regex::Regex; + +// 42 since the string length of inj addresses is 42, e.g. "inj1mtl2fykeceen4c74ddldrtdkq6fvnz79n8c592" +pub fn denom_parser() -> Regex { + Regex::new(r"factory/(\w{42})/(\w{42})").unwrap() +} + +pub fn get_cw20_address_from_denom(parser: &Regex, denom: &str) -> Option { + let captures = parser.captures(denom)?; + let cw20addr = captures.get(2)?; + Some(cw20addr.as_str().to_string()) +} + +pub fn get_denom(adapter_address: &Addr, cw20addr: &Addr) -> String { + format!("factory/{}/{}", adapter_address, cw20addr) +} + +pub fn query_denom_creation_fee(querier_wrapper: &QuerierWrapper) -> StdResult> { + let querier = InjectiveQuerier::new(querier_wrapper); + Ok(querier.query_token_factory_creation_fee()?.fee) +} + +pub fn fetch_cw20_metadata(deps: &DepsMut, addr: &str) -> Result { + let msg = Cw20QueryMsg::TokenInfo {}; + deps.querier.query_wasm_smart(addr, &msg).map_err(|_e| ContractError::NotCw20Address) +} + +pub fn ensure_address_is_cw20(deps: &DepsMut, addr: &str) -> Result<(), ContractError> { + let msg = Cw20QueryMsg::TokenInfo {}; + let res: StdResult = deps.querier.query_wasm_smart(addr, &msg); + match res { + Ok(_) => Ok(()), + Err(_) => Err(ContractError::NotCw20Address), + } +} + +pub fn is_contract_registered(deps: &DepsMut, addr: &Addr) -> bool { + CW20_CONTRACTS.contains(deps.storage, addr.as_ref()) +} + +pub fn ensure_sufficient_create_denom_balance(deps: &DepsMut, env: &Env) -> Result<(), ContractError> { + let required_funds = query_denom_creation_fee(&deps.querier)?; + + for c in required_funds { + let balance = deps.querier.query_balance(env.contract.address.as_str(), c.denom)?; + if balance.amount < c.amount { + return Err(ContractError::NotEnoughBalanceToPayDenomCreationFee); + } + } + Ok(()) +} + +pub fn register_contract_and_get_message( + deps: DepsMut, + env: &Env, + addr: &Addr, +) -> Result, ContractError> { + let contract_address = addr.to_string(); + ensure_address_is_cw20(&deps, &contract_address)?; + CW20_CONTRACTS.insert(deps.storage, &contract_address)?; + let create_denom_message = create_new_denom_msg(env.contract.address.to_string(), contract_address); + + Ok(create_denom_message) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_returns_true_on_correct_token_factory_denom() { + assert!( + denom_parser().is_match("factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h"), + "input was not treated as token factory denom" + ) + } + + #[test] + fn it_returns_false_for_non_token_factory_denom() { + assert!( + !denom_parser().is_match(".factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7"), + "input was treated as token factory denom" + ) + } + + #[test] + fn it_returns_cw_20_address_for_token_factory_denom() { + assert_eq!( + get_cw20_address_from_denom( + &denom_parser(), + "factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h", + ) + .unwrap(), + "inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h", + "wrong cw20 address returned" + ) + } + + #[test] + fn it_returns_none_cw_20_address_for_non_token_factory_denom() { + assert!( + get_cw20_address_from_denom( + &denom_parser(), + "factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7", + ) + .is_none(), + "cw20 address returned" + ) + } + + #[test] + fn it_returns_denom() { + assert_eq!( + get_denom( + &Addr::unchecked("inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw".to_string()), + &Addr::unchecked("inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h".to_string()), + ), + "factory/inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw/inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h", + "wrong denom returned" + ) + } +} diff --git a/contracts/cw20-adapter/src/contract.rs b/contracts/cw20-adapter/src/contract.rs new file mode 100644 index 0000000..c4d8bb0 --- /dev/null +++ b/contracts/cw20-adapter/src/contract.rs @@ -0,0 +1,43 @@ +use cosmwasm_std::{entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use injective_cosmwasm::{InjectiveMsgWrapper, InjectiveQueryWrapper}; + +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::{error::ContractError, execute_metadata, execute_receive, execute_redeem, execute_register, query}; + +pub const CONTRACT_NAME: &str = "crates.io:inj-cw20-adapter"; +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: InstantiateMsg, +) -> StdResult> { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::new()) +} + +#[entry_point] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result, ContractError> { + match msg { + ExecuteMsg::RegisterCw20Contract { addr } => execute_register::handle_register_msg(deps, env, info, addr), + ExecuteMsg::Receive { sender, amount, msg: _ } => execute_receive::handle_on_received_cw20_funds_msg(deps, env, info, sender, amount), + ExecuteMsg::RedeemAndTransfer { recipient } => execute_redeem::handle_redeem_msg(deps, env, info, recipient, None), + ExecuteMsg::RedeemAndSend { recipient, submsg } => execute_redeem::handle_redeem_msg(deps, env, info, Some(recipient), Some(submsg)), + ExecuteMsg::UpdateMetadata { addr } => execute_metadata::handle_update_metadata(deps, env, addr), + } +} + +#[entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::RegisteredContracts {} => to_binary(&query::registered_contracts(deps)?), + QueryMsg::NewDenomFee {} => to_binary(&query::new_denom_fee(deps)?), + } +} diff --git a/contracts/cw20-adapter/src/error.rs b/contracts/cw20-adapter/src/error.rs new file mode 100644 index 0000000..7946b79 --- /dev/null +++ b/contracts/cw20-adapter/src/error.rs @@ -0,0 +1,25 @@ +use thiserror::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum ContractError { + #[error(transparent)] + Std(#[from] cosmwasm_std::StdError), + + #[error("No registered tokens provided")] + NoRegisteredTokensProvided, + + #[error("CW-20 contract with the same address was already registered")] + ContractAlreadyRegistered, + + #[error("CW-20 contract is not registered in adapter")] + ContractNotRegistered, + + #[error("Adapter is missing balance to create a new token-factory denom")] + NotEnoughBalanceToPayDenomCreationFee, + + #[error("Some of the provided funds are not required")] + SuperfluousFundsProvided, + + #[error("Address is not cw-20 contract")] + NotCw20Address, +} diff --git a/contracts/cw20-adapter/src/execute_metadata.rs b/contracts/cw20-adapter/src/execute_metadata.rs new file mode 100644 index 0000000..3a4a2c7 --- /dev/null +++ b/contracts/cw20-adapter/src/execute_metadata.rs @@ -0,0 +1,24 @@ +use cosmwasm_std::{Addr, DepsMut, Env, Response}; + +use injective_cosmwasm::{create_set_token_metadata_msg, InjectiveMsgWrapper, InjectiveQueryWrapper}; + +use crate::common::{fetch_cw20_metadata, get_denom}; +use crate::error::ContractError; +use crate::state::CW20_CONTRACTS; + +pub fn handle_update_metadata( + deps: DepsMut, + env: Env, + cw20_addr: Addr, +) -> Result, ContractError> { + let is_contract_registered = CW20_CONTRACTS.contains(deps.storage, cw20_addr.as_str()); + if !is_contract_registered { + return Err(ContractError::ContractNotRegistered); + } + let token_metadata = fetch_cw20_metadata(&deps, cw20_addr.as_str())?; + + let denom = get_denom(&env.contract.address, &cw20_addr); + let set_metadata_message = create_set_token_metadata_msg(denom, token_metadata.name, token_metadata.symbol, token_metadata.decimals); + + Ok(Response::new().add_message(set_metadata_message)) +} diff --git a/contracts/cw20-adapter/src/execute_receive.rs b/contracts/cw20-adapter/src/execute_receive.rs new file mode 100644 index 0000000..e05f757 --- /dev/null +++ b/contracts/cw20-adapter/src/execute_receive.rs @@ -0,0 +1,29 @@ +use crate::common::{ensure_sufficient_create_denom_balance, get_denom, is_contract_registered, register_contract_and_get_message}; +use crate::error::ContractError; +use cosmwasm_std::{Coin, DepsMut, Env, MessageInfo, Response, Uint128}; +use injective_cosmwasm::{create_mint_tokens_msg, InjectiveMsgWrapper, InjectiveQueryWrapper}; + +pub fn handle_on_received_cw20_funds_msg( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: String, + amount: Uint128, +) -> Result, ContractError> { + if !info.funds.is_empty() { + return Err(ContractError::SuperfluousFundsProvided); + } + let mut response = Response::new(); + let token_contract = info.sender; + if !is_contract_registered(&deps, &token_contract) { + ensure_sufficient_create_denom_balance(&deps, &env)?; + response = response.add_message(register_contract_and_get_message(deps, &env, &token_contract)?); + } + let master = env.contract.address; + + let denom = get_denom(&master, &token_contract); + let coins_to_mint = Coin::new(amount.u128(), denom); + let mint_tf_tokens_message = create_mint_tokens_msg(master, coins_to_mint, recipient); + + Ok(response.add_message(mint_tf_tokens_message)) +} diff --git a/contracts/cw20-adapter/src/execute_redeem.rs b/contracts/cw20-adapter/src/execute_redeem.rs new file mode 100644 index 0000000..9e6d42e --- /dev/null +++ b/contracts/cw20-adapter/src/execute_redeem.rs @@ -0,0 +1,62 @@ +use cosmwasm_std::{to_binary, Binary, Coin, DepsMut, Env, MessageInfo, Response, WasmMsg}; +use cw20::Cw20ExecuteMsg; +use injective_cosmwasm::{create_burn_tokens_msg, InjectiveMsgWrapper, InjectiveQueryWrapper}; + +use crate::common::{denom_parser, get_cw20_address_from_denom}; +use crate::error::ContractError; +use crate::state::CW20_CONTRACTS; + +pub fn handle_redeem_msg( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Option, + submessage: Option, +) -> Result, ContractError> { + let recipient = recipient.unwrap_or_else(|| info.sender.to_string()); + + if info.funds.len() > 1 { + return Err(ContractError::SuperfluousFundsProvided); + } + let denom_parser = denom_parser(); + let tokens_to_exchange = info + .funds + .iter() + .find_map(|c| -> Option { + if denom_parser.is_match(&c.denom) { + Some(c.clone()) + } else { + None + } + }) + .ok_or(ContractError::NoRegisteredTokensProvided)?; + + let cw20_addr = get_cw20_address_from_denom(&denom_parser, &tokens_to_exchange.denom).ok_or(ContractError::NoRegisteredTokensProvided)?; + let is_contract_registered = CW20_CONTRACTS.contains(deps.storage, cw20_addr.as_str()); + if !is_contract_registered { + return Err(ContractError::NoRegisteredTokensProvided); + } + + let burn_tf_tokens_message = create_burn_tokens_msg(env.contract.address, tokens_to_exchange.clone()); + + let cw20_message: WasmMsg = match submessage { + None => WasmMsg::Execute { + contract_addr: cw20_addr, + msg: to_binary(&Cw20ExecuteMsg::Transfer { + recipient, + amount: tokens_to_exchange.amount, + })?, + funds: vec![], + }, + Some(msg) => WasmMsg::Execute { + contract_addr: cw20_addr, + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: recipient, + amount: tokens_to_exchange.amount, + msg, + })?, + funds: vec![], + }, + }; + Ok(Response::new().add_message(cw20_message).add_message(burn_tf_tokens_message)) +} diff --git a/contracts/cw20-adapter/src/execute_register.rs b/contracts/cw20-adapter/src/execute_register.rs new file mode 100644 index 0000000..824c56d --- /dev/null +++ b/contracts/cw20-adapter/src/execute_register.rs @@ -0,0 +1,36 @@ +use crate::common::{is_contract_registered, query_denom_creation_fee, register_contract_and_get_message}; +use crate::error::ContractError; +use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response}; +use injective_cosmwasm::{InjectiveMsgWrapper, InjectiveQueryWrapper}; +use std::cmp::Ordering; + +pub fn handle_register_msg( + deps: DepsMut, + env: Env, + info: MessageInfo, + addr: Addr, +) -> Result, ContractError> { + if is_contract_registered(&deps, &addr) { + return Err(ContractError::ContractAlreadyRegistered); + } + let required_funds = query_denom_creation_fee(&deps.querier)?; + if info.funds.len() > required_funds.len() { + return Err(ContractError::SuperfluousFundsProvided); + } + + let mut provided_funds = info.funds.iter(); + for required_coin in required_funds { + let pf = provided_funds + .find(|c| -> bool { c.denom == required_coin.denom }) + .ok_or(ContractError::NotEnoughBalanceToPayDenomCreationFee)?; + + match pf.amount.cmp(&required_coin.amount) { + Ordering::Greater => return Err(ContractError::SuperfluousFundsProvided), + Ordering::Less => return Err(ContractError::NotEnoughBalanceToPayDenomCreationFee), + Ordering::Equal => {} + } + } + + let create_denom_msg = register_contract_and_get_message(deps, &env, &addr)?; + Ok(Response::new().add_message(create_denom_msg)) +} diff --git a/contracts/cw20-adapter/src/lib.rs b/contracts/cw20-adapter/src/lib.rs new file mode 100644 index 0000000..f1bc920 --- /dev/null +++ b/contracts/cw20-adapter/src/lib.rs @@ -0,0 +1,10 @@ +pub mod common; +pub mod contract; +pub mod error; +pub mod execute_metadata; +pub mod execute_receive; +pub mod execute_redeem; +pub mod execute_register; +pub mod msg; +pub mod query; +pub mod state; diff --git a/contracts/cw20-adapter/src/msg.rs b/contracts/cw20-adapter/src/msg.rs new file mode 100644 index 0000000..b723289 --- /dev/null +++ b/contracts/cw20-adapter/src/msg.rs @@ -0,0 +1,32 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Binary, Uint128}; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub struct ReceiveSubmsg { + pub(crate) recipient: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + /// Registers a new CW-20 contract that will be handled by the adapter + RegisterCw20Contract { addr: Addr }, + /// Impl of Receiver CW-20 interface. Should be called by CW-20 contract only!! (never directly). Msg is ignored + Receive { sender: String, amount: Uint128, msg: Binary }, + /// Called to redeem TF tokens. Will send CW-20 tokens to "recipient" address (or sender if not provided). Will use transfer method + RedeemAndTransfer { recipient: Option }, + /// Called to redeem TF tokens. Will call Send method of CW:20 to send CW-20 tokens to "recipient" address. Submessage will be passed to send method (can be empty) + RedeemAndSend { recipient: String, submsg: Binary }, + /// Updates stored metadata + UpdateMetadata { addr: Addr }, +} + +#[cw_serde] +pub enum QueryMsg { + /// Return a list of registered CW-20 contracts + RegisteredContracts {}, + /// Returns a fee required to register a new token-factory denom + NewDenomFee {}, +} diff --git a/contracts/cw20-adapter/src/query.rs b/contracts/cw20-adapter/src/query.rs new file mode 100644 index 0000000..48ecdbd --- /dev/null +++ b/contracts/cw20-adapter/src/query.rs @@ -0,0 +1,17 @@ +use crate::common::query_denom_creation_fee; +use cosmwasm_std::{Coin, Deps, Order, StdResult}; +use injective_cosmwasm::InjectiveQueryWrapper; + +use crate::state::CW20_CONTRACTS; + +pub fn registered_contracts(deps: Deps) -> StdResult> { + let contracts = CW20_CONTRACTS + .items(deps.storage, None, None, Order::Ascending) + .filter_map(|c| c.ok()) + .collect(); + Ok(contracts) +} + +pub fn new_denom_fee(deps: Deps) -> StdResult> { + query_denom_creation_fee(&deps.querier) +} diff --git a/contracts/cw20-adapter/src/state.rs b/contracts/cw20-adapter/src/state.rs new file mode 100644 index 0000000..d583de6 --- /dev/null +++ b/contracts/cw20-adapter/src/state.rs @@ -0,0 +1,3 @@ +use cw_item_set::Set; + +pub const CW20_CONTRACTS: Set<&str> = Set::new("contracts", "contracts__counter"); diff --git a/contracts/cw20-adapter/tests/common/mod.rs b/contracts/cw20-adapter/tests/common/mod.rs new file mode 100644 index 0000000..9e732a5 --- /dev/null +++ b/contracts/cw20-adapter/tests/common/mod.rs @@ -0,0 +1,82 @@ +#![allow(dead_code)] + +use cosmwasm_std::{ + to_binary, Addr, BalanceResponse, Binary, BlockInfo, Coin, ContractInfo, ContractResult, Env, QuerierResult, SystemError, SystemResult, + Timestamp, TransactionInfo, Uint128, +}; +use cw20::TokenInfoResponse; +use injective_cosmwasm::{HandlesBankBalanceQuery, HandlesFeeQuery, HandlesSmartQuery}; + +pub const CONTRACT_ADDRESS: &str = "inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw"; +pub const CW_20_ADDRESS: &str = "inj1pjcw9hhx8kf462qtgu37p7l7shyqgpfr82r6em"; +pub const SENDER: &str = "inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h"; + +pub fn mock_env(addr: &str) -> Env { + Env { + block: BlockInfo { + height: 12_345, + time: Timestamp::from_nanos(1_571_797_419_879_305_533), + chain_id: "inj-testnet-14002".to_string(), + }, + transaction: Some(TransactionInfo { index: 3 }), + contract: ContractInfo { + address: Addr::unchecked(addr), + }, + } +} + +pub fn create_cw20_info_query_handler() -> Option> { + struct A(); + impl HandlesSmartQuery for A { + fn handle(&self, _: &str, _: &Binary) -> QuerierResult { + let response = TokenInfoResponse { + name: "Solana".to_string(), + symbol: "SOL".to_string(), + decimals: 6, + total_supply: Uint128::new(1000), + }; + SystemResult::Ok(ContractResult::from(to_binary(&response))) + } + } + Some(Box::new(A())) +} + +pub fn create_cw20_failing_info_query_handler() -> Option> { + struct A(); + impl HandlesSmartQuery for A { + fn handle(&self, addr: &str, _: &Binary) -> QuerierResult { + SystemResult::Err(SystemError::NoSuchContract { addr: addr.to_string() }) + } + } + Some(Box::new(A())) +} + +pub fn create_denom_creation_fee_failing_handler() -> Option> { + struct Temp(); + impl HandlesFeeQuery for Temp { + fn handle(&self) -> QuerierResult { + SystemResult::Err(SystemError::UnsupportedRequest { + kind: "custom error".to_string(), + }) + } + } + Some(Box::new(Temp())) +} + +pub fn create_custom_bank_balance_query_handler(balance: Coin) -> Option> { + struct Temp { + balance: Coin, + } + impl HandlesBankBalanceQuery for Temp { + fn handle(&self, _: String, _: String) -> QuerierResult { + let response = BalanceResponse { + amount: Coin { + denom: self.balance.denom.clone(), + amount: self.balance.amount, + }, + }; + SystemResult::Ok(ContractResult::from(to_binary(&response))) + } + } + Some(Box::new(Temp { balance })) +} diff --git a/contracts/cw20-adapter/tests/execute_metadata_tests.rs b/contracts/cw20-adapter/tests/execute_metadata_tests.rs new file mode 100644 index 0000000..aef9d21 --- /dev/null +++ b/contracts/cw20-adapter/tests/execute_metadata_tests.rs @@ -0,0 +1,63 @@ +mod common; + +use cosmwasm_std::testing::mock_env; +use cosmwasm_std::{Addr, CosmosMsg, SubMsg}; +use cw20_adapter::common::get_denom; +use cw20_adapter::error::ContractError; +use cw20_adapter::execute_metadata::handle_update_metadata; +use cw20_adapter::state::CW20_CONTRACTS; +use injective_cosmwasm::{mock_dependencies, InjectiveMsg, InjectiveMsgWrapper, InjectiveRoute, WasmMockQuerier}; + +use crate::common::{create_cw20_info_query_handler, CONTRACT_ADDRESS, CW_20_ADDRESS}; + +#[test] +fn it_updates_metadata() { + let mut deps = mock_dependencies(); + deps.querier = WasmMockQuerier { + smart_query_handler: create_cw20_info_query_handler(), + ..Default::default() + }; + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + CW20_CONTRACTS.insert(&mut deps.storage, CW_20_ADDRESS).unwrap(); + + let response = handle_update_metadata(deps.as_mut(), env, Addr::unchecked(CW_20_ADDRESS)).unwrap(); + assert_eq!(response.messages.len(), 1, "incorrect number of messages returned"); + + if let SubMsg { + msg: CosmosMsg::Custom(InjectiveMsgWrapper { route, msg_data }), + .. + } = response.messages.get(0).unwrap() + { + assert_eq!(route, &InjectiveRoute::Tokenfactory, "submessage had wrong route"); + if let InjectiveMsg::SetTokenMetadata { + denom, + name, + symbol, + decimals, + } = msg_data + { + assert_eq!( + get_denom(&Addr::unchecked(CONTRACT_ADDRESS), &Addr::unchecked(CW_20_ADDRESS)), + denom.as_str(), + "incorrect denom in set metadata message" + ); + assert_eq!("SOL", symbol.as_str(), "incorrect symbol in set metadata message"); + assert_eq!("Solana", name.as_str(), "incorrect name in set metadata message"); + assert_eq!(6, *decimals, "incorrect decimals in set metadata message"); + } else { + panic!("incorrect injective message found") + } + } else { + panic!("incorrect submessage type found") + } +} + +#[test] +fn it_tries_to_update_not_registered_contract() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let err_response = handle_update_metadata(deps.as_mut(), env, Addr::unchecked(CW_20_ADDRESS)).unwrap_err(); + assert_eq!(err_response, ContractError::ContractNotRegistered, "incorrect error"); +} diff --git a/contracts/cw20-adapter/tests/execute_receive_tests.rs b/contracts/cw20-adapter/tests/execute_receive_tests.rs new file mode 100644 index 0000000..9879671 --- /dev/null +++ b/contracts/cw20-adapter/tests/execute_receive_tests.rs @@ -0,0 +1,152 @@ +mod common; + +use cosmwasm_std::{ + testing::{mock_env, mock_info}, + Addr, Coin, CosmosMsg, SubMsg, Uint128, +}; + +use cw20_adapter::{error::ContractError, execute_receive::handle_on_received_cw20_funds_msg, state::CW20_CONTRACTS}; +use injective_cosmwasm::{mock_dependencies, InjectiveMsg, InjectiveMsgWrapper, InjectiveRoute, WasmMockQuerier}; + +use crate::common::{create_custom_bank_balance_query_handler, create_cw20_info_query_handler, CONTRACT_ADDRESS, CW_20_ADDRESS, SENDER}; + +#[test] +fn it_handles_receive_correctly_if_not_already_registered() { + let mut deps = mock_dependencies(); + deps.querier = WasmMockQuerier { + balance_query_handler: create_custom_bank_balance_query_handler(Coin::new(10, "inj")), + smart_query_handler: create_cw20_info_query_handler(), + ..Default::default() + }; + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + let amount_to_send = Uint128::new(100); + let response = handle_on_received_cw20_funds_msg(deps.as_mut(), env, mock_info(CW_20_ADDRESS, &[]), SENDER.to_string(), amount_to_send).unwrap(); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, CW_20_ADDRESS); + assert!(contract_registered, "contract wasn't registered"); + + assert_eq!(response.messages.len(), 2, "incorrect number of messages returned"); + + if let SubMsg { + msg: CosmosMsg::Custom(InjectiveMsgWrapper { route, msg_data }), + .. + } = response.messages.first().unwrap() + { + assert_eq!(route, &InjectiveRoute::Tokenfactory, "submessage had wrong route"); + if let InjectiveMsg::CreateDenom { sender, subdenom } = msg_data { + assert_eq!(CONTRACT_ADDRESS, sender.as_str(), "incorrect sender in the create denom message"); + assert_eq!(CW_20_ADDRESS, subdenom.as_str(), "incorrect subdenom in the create denom message"); + } else { + panic!("incorrect injective message found") + } + } else { + panic!("incorrect submessage type found") + } + + if let SubMsg { + msg: CosmosMsg::Custom(InjectiveMsgWrapper { route, msg_data }), + .. + } = response.messages.get(1).unwrap() + { + assert_eq!(route, &InjectiveRoute::Tokenfactory, "submessage had wrong route"); + if let InjectiveMsg::Mint { sender, amount, mint_to } = msg_data { + assert_eq!(CONTRACT_ADDRESS, sender.as_str(), "incorrect sender in the mint message"); + assert_eq!(amount_to_send, amount.amount, "incorrect amount in the mint message"); + assert_eq!( + format!("factory/{}/{}", CONTRACT_ADDRESS, CW_20_ADDRESS), + amount.denom, + "incorrect amount in the mint message" + ); + assert_eq!(SENDER, mint_to, "incorrect mint_to in the mint message"); + } else { + panic!("incorrect injective message found") + } + } else { + panic!("incorrect submessage type found") + } +} + +#[test] +fn it_handles_receive_correctly_if_already_registered() { + let mut deps = mock_dependencies(); + deps.querier = WasmMockQuerier { + balance_query_handler: create_custom_bank_balance_query_handler(Coin::new(10, "inj")), + ..Default::default() + }; + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + CW20_CONTRACTS.insert(&mut deps.storage, CW_20_ADDRESS).unwrap(); + let amount_to_send = Uint128::new(100); + let response = handle_on_received_cw20_funds_msg(deps.as_mut(), env, mock_info(CW_20_ADDRESS, &[]), SENDER.to_string(), amount_to_send).unwrap(); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, CW_20_ADDRESS); + assert!(contract_registered, "contract wasn't registered"); + + assert_eq!(response.messages.len(), 1, "incorrect number of messages returned"); + + if let SubMsg { + msg: CosmosMsg::Custom(InjectiveMsgWrapper { route, msg_data }), + .. + } = response.messages.first().unwrap() + { + assert_eq!(route, &InjectiveRoute::Tokenfactory, "submessage had wrong route"); + if let InjectiveMsg::Mint { sender, amount, mint_to } = msg_data { + assert_eq!(CONTRACT_ADDRESS, sender.as_str(), "incorrect sender in the mint message"); + assert_eq!(amount_to_send, amount.amount, "incorrect amount in the mint message"); + assert_eq!( + format!("factory/{}/{}", CONTRACT_ADDRESS, CW_20_ADDRESS), + amount.denom, + "incorrect amount in the mint message" + ); + assert_eq!(SENDER, mint_to, "incorrect mint_to in the mint message"); + } else { + panic!("incorrect injective message found") + } + } else { + panic!("incorrect submessage type found") + } +} + +#[test] +fn it_returns_error_on_receive_if_contract_not_registered_and_contract_has_insufficient_balance() { + let mut deps = mock_dependencies(); + deps.querier = WasmMockQuerier { + balance_query_handler: create_custom_bank_balance_query_handler(Coin::new(9, "inj")), + ..Default::default() + }; + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + let amount_to_send = Uint128::new(100); + let response = + handle_on_received_cw20_funds_msg(deps.as_mut(), env, mock_info(CW_20_ADDRESS, &[]), SENDER.to_string(), amount_to_send).unwrap_err(); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, CW_20_ADDRESS); + assert!(!contract_registered, "contract was registered"); + + assert_eq!(response, ContractError::NotEnoughBalanceToPayDenomCreationFee, "incorrect error returned"); +} + +#[test] +fn it_returns_error_on_receive_if_additional_funds_are_provided() { + let mut deps = mock_dependencies(); + deps.querier = WasmMockQuerier { + balance_query_handler: create_custom_bank_balance_query_handler(Coin::new(10, "inj")), + ..Default::default() + }; + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + let amount_to_send = Uint128::new(100); + let response = handle_on_received_cw20_funds_msg( + deps.as_mut(), + env, + mock_info(CW_20_ADDRESS, &[Coin::new(1000, "usdt")]), + SENDER.to_string(), + amount_to_send, + ) + .unwrap_err(); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, CW_20_ADDRESS); + assert!(!contract_registered, "contract was registered"); + assert_eq!(response, ContractError::SuperfluousFundsProvided, "funds were provided"); +} diff --git a/contracts/cw20-adapter/tests/execute_redeem_tests.rs b/contracts/cw20-adapter/tests/execute_redeem_tests.rs new file mode 100644 index 0000000..b1784df --- /dev/null +++ b/contracts/cw20-adapter/tests/execute_redeem_tests.rs @@ -0,0 +1,170 @@ +use cosmwasm_std::{ + from_binary, + testing::{mock_env, mock_info}, + to_binary, Addr, Coin, CosmosMsg, SubMsg, WasmMsg, +}; +use cw20::Cw20ExecuteMsg; +use cw20_adapter::{error::ContractError, execute_redeem::handle_redeem_msg, state::CW20_CONTRACTS}; +use injective_cosmwasm::{mock_dependencies, InjectiveMsg, InjectiveMsgWrapper, InjectiveRoute}; + +use crate::common::{CONTRACT_ADDRESS, CW_20_ADDRESS, SENDER}; + +mod common; + +#[test] +fn it_handles_redeem_and_transfer_correctly() { + let mut deps = mock_dependencies(); + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + CW20_CONTRACTS.insert(&mut deps.storage, CW_20_ADDRESS).unwrap(); + + let coins_to_burn = Coin::new(10, format!("factory/{}/{}", CONTRACT_ADDRESS, CW_20_ADDRESS)); + let response = handle_redeem_msg( + deps.as_mut(), + env, + mock_info(CW_20_ADDRESS, &[coins_to_burn.clone()]), + Some(SENDER.to_string()), + None, + ) + .unwrap(); + + assert_eq!(response.messages.len(), 2, "incorrect number of messages returned"); + + if let SubMsg { + msg: CosmosMsg::Wasm(WasmMsg::Execute { contract_addr, msg, funds }), + .. + } = response.messages.first().unwrap() + { + let expected_coins: Vec = vec![]; + assert_eq!(&expected_coins, funds, "incorrect funds found in execute message"); + assert_eq!(CW_20_ADDRESS, contract_addr, "incorrect contact_addr in execute message"); + + let deserialised_msg: Cw20ExecuteMsg = from_binary(msg).unwrap(); + + if let Cw20ExecuteMsg::Transfer { recipient, amount } = deserialised_msg { + assert_eq!(SENDER, recipient.as_str(), "incorrect recipient in the transfer message"); + assert_eq!(coins_to_burn.amount, amount, "incorrect amount in the transfer message"); + } else { + panic!("incorrect injective message found") + } + } else { + panic!("incorrect submessage type found") + } + + if let SubMsg { + msg: CosmosMsg::Custom(InjectiveMsgWrapper { route, msg_data }), + .. + } = response.messages.get(1).unwrap() + { + assert_eq!(route, &InjectiveRoute::Tokenfactory, "submessage had wrong route"); + if let InjectiveMsg::Burn { sender, amount } = msg_data { + assert_eq!(CONTRACT_ADDRESS, sender.as_str(), "incorrect sender in the create denom message"); + assert_eq!(&coins_to_burn, amount, "incorrect amount in the burn message"); + } else { + panic!("incorrect injective message found") + } + } else { + panic!("incorrect submessage type found") + } +} + +#[test] +fn it_returns_error_if_redeeming_non_factory_token() { + let mut deps = mock_dependencies(); + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + + let response = handle_redeem_msg( + deps.as_mut(), + env, + mock_info(CW_20_ADDRESS, &[Coin::new(10, "usdt")]), + Some(SENDER.to_string()), + None, + ) + .unwrap_err(); + assert_eq!(response, ContractError::NoRegisteredTokensProvided, "incorrect error returned") +} + +#[test] +fn it_returns_error_if_redeeming_zero_tokens() { + let mut deps = mock_dependencies(); + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + + let response = handle_redeem_msg(deps.as_mut(), env, mock_info(CW_20_ADDRESS, &[]), Some(SENDER.to_string()), None).unwrap_err(); + assert_eq!(response, ContractError::NoRegisteredTokensProvided, "incorrect error returned") +} + +#[test] +fn it_returns_error_if_redeeming_unknown_factory_token() { + let mut deps = mock_dependencies(); + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + + let response = handle_redeem_msg( + deps.as_mut(), + env, + mock_info(CW_20_ADDRESS, &[Coin::new(10, format!("factory/{}/{}", CONTRACT_ADDRESS, CW_20_ADDRESS))]), + Some(SENDER.to_string()), + None, + ) + .unwrap_err(); + assert_eq!(response, ContractError::NoRegisteredTokensProvided, "incorrect error returned") +} + +#[test] +fn it_handles_redeem_and_send_correctly() { + let mut deps = mock_dependencies(); + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + CW20_CONTRACTS.insert(&mut deps.storage, CW_20_ADDRESS).unwrap(); + + let coins_to_burn = Coin::new(10, format!("factory/{}/{}", CONTRACT_ADDRESS, CW_20_ADDRESS)); + let response = handle_redeem_msg( + deps.as_mut(), + env, + mock_info(CW_20_ADDRESS, &[coins_to_burn.clone()]), + Some(CW_20_ADDRESS.to_string()), + Some(to_binary(&coins_to_burn).unwrap()), // doesn't matter what is the message + ) + .unwrap(); + + assert_eq!(response.messages.len(), 2, "incorrect number of messages returned"); + + if let SubMsg { + msg: CosmosMsg::Wasm(WasmMsg::Execute { contract_addr, msg, funds }), + .. + } = response.messages.first().unwrap() + { + let expected_coins: Vec = vec![]; + assert_eq!(&expected_coins, funds, "incorrect funds found in execute message"); + assert_eq!(CW_20_ADDRESS, contract_addr, "incorrect contact_addr in execute message"); + + let deserialised_msg: Cw20ExecuteMsg = from_binary(msg).unwrap(); + + if let Cw20ExecuteMsg::Send { contract, amount, .. } = deserialised_msg { + assert_eq!(CW_20_ADDRESS, contract.as_str(), "incorrect recipient in the transfer message"); + assert_eq!(coins_to_burn.amount, amount, "incorrect amount in the transfer message"); + } else { + panic!("incorrect injective message found") + } + } else { + panic!("incorrect submessage type found") + } + + if let SubMsg { + msg: CosmosMsg::Custom(InjectiveMsgWrapper { route, msg_data }), + .. + } = response.messages.get(1).unwrap() + { + assert_eq!(route, &InjectiveRoute::Tokenfactory, "submessage had wrong route"); + if let InjectiveMsg::Burn { sender, amount } = msg_data { + assert_eq!(CONTRACT_ADDRESS, sender.as_str(), "incorrect sender in the create denom message"); + assert_eq!(&coins_to_burn, amount, "incorrect amount in the burn message"); + } else { + panic!("incorrect injective message found") + } + } else { + panic!("incorrect submessage type found") + } +} diff --git a/contracts/cw20-adapter/tests/execute_register_tests.rs b/contracts/cw20-adapter/tests/execute_register_tests.rs new file mode 100644 index 0000000..23351fc --- /dev/null +++ b/contracts/cw20-adapter/tests/execute_register_tests.rs @@ -0,0 +1,201 @@ +use cosmwasm_std::{ + testing::{mock_env, mock_info}, + Addr, Coin, CosmosMsg, SubMsg, +}; + +use cw20_adapter::{error::ContractError, execute_register::handle_register_msg, state::CW20_CONTRACTS}; +use injective_cosmwasm::{mock_dependencies, InjectiveMsg, InjectiveMsgWrapper, InjectiveRoute, WasmMockQuerier}; + +use common::{create_cw20_failing_info_query_handler, create_cw20_info_query_handler, create_denom_creation_fee_failing_handler}; + +use crate::common::{CONTRACT_ADDRESS, CW_20_ADDRESS, SENDER}; + +mod common; + +// const CONTRACT_ADDRESS: &str = "inj1pvrwmjuusn9wh34j7y520g8gumuy9xtlt6xtzw"; +// const CW_20_ADDRESS: &str = "inj1pjcw9hhx8kf462qtgu37p7l7shyqgpfr82r6em"; +// const SENDER: &str = "inj1n0qvel0zfmsxu3q8q23xzjvuwfxn0ydlhgyh7h"; + +#[test] +fn it_handles_correct_register_msg_with_exact_funds() { + let mut deps = mock_dependencies(); + deps.querier = WasmMockQuerier { + smart_query_handler: create_cw20_info_query_handler(), + ..Default::default() + }; + + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + let response = handle_register_msg( + deps.as_mut(), + env, + mock_info(SENDER, &[Coin::new(10, "inj")]), + Addr::unchecked(CW_20_ADDRESS), + ) + .unwrap(); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, CW_20_ADDRESS); + assert!(contract_registered, "contract wasn't registered"); + + assert_eq!(response.messages.len(), 1, "incorrect number of messages returned"); + + if let SubMsg { + msg: CosmosMsg::Custom(InjectiveMsgWrapper { route, msg_data }), + .. + } = response.messages.first().unwrap() + { + assert_eq!(route, &InjectiveRoute::Tokenfactory, "submessage had wrong route"); + if let InjectiveMsg::CreateDenom { sender, subdenom } = msg_data { + assert_eq!(CONTRACT_ADDRESS, sender.as_str(), "incorrect sender in the create denom message"); + assert_eq!(CW_20_ADDRESS, subdenom.as_str(), "incorrect subdenom in the create denom message"); + } else { + panic!("incorrect injective message found") + } + } else { + panic!("incorrect submessage type found") + } +} + +#[test] +fn it_handles_correct_register_msg_with_extra_funds() { + let mut deps = mock_dependencies(); + let mut env = mock_env(); + env.contract.address = Addr::unchecked(CONTRACT_ADDRESS); + let response_err = handle_register_msg( + deps.as_mut(), + env, + mock_info(SENDER, &[Coin::new(100, "inj"), Coin::new(20, "usdt")]), + Addr::unchecked(CW_20_ADDRESS), + ) + .unwrap_err(); + assert_eq!(response_err, ContractError::SuperfluousFundsProvided); +} + +#[test] +fn it_handles_correct_register_msg_with_non_cannonical_cw20_address() { + let mut deps = mock_dependencies(); + deps.querier = WasmMockQuerier { + smart_query_handler: create_cw20_failing_info_query_handler(), + ..Default::default() + }; + + let non_cannonical_address = "stefan"; + let response = handle_register_msg( + deps.as_mut(), + mock_env(), + mock_info(SENDER, &[Coin::new(10, "inj")]), + Addr::unchecked(non_cannonical_address.to_string()), + ) + .unwrap_err(); + + assert_eq!(ContractError::NotCw20Address, response, "should fail with wrong cw-20 address"); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, non_cannonical_address); + assert!(!contract_registered, "contract was registered"); +} + +#[test] +fn it_returns_error_if_already_registered_register_msg() { + let mut deps = mock_dependencies(); + let storage = &mut deps.storage; + let contract_address = Addr::unchecked("amazing_address"); + CW20_CONTRACTS.insert(storage, contract_address.as_str()).unwrap(); + + let response = handle_register_msg(deps.as_mut(), mock_env(), mock_info("sender", &[]), contract_address); + + assert_eq!( + response.unwrap_err(), + ContractError::ContractAlreadyRegistered, + "incorrect error returned" + ) +} + +#[test] +fn it_returns_error_if_cannot_query_denom_creation_fee_register_msg() { + let mut deps = mock_dependencies(); + deps.querier = WasmMockQuerier { + token_factory_denom_creation_fee_handler: create_denom_creation_fee_failing_handler(), + ..Default::default() + }; + + let response = handle_register_msg( + deps.as_mut(), + mock_env(), + mock_info(SENDER, &[Coin::new(10, "inj")]), + Addr::unchecked(CW_20_ADDRESS), + ) + .unwrap_err(); + + assert!(response.to_string().contains("custom error"), "incorrect error returned"); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, CW_20_ADDRESS); + assert!(!contract_registered, "contract was registered"); +} + +#[test] +fn it_returns_error_if_mismatched_denom_is_passed_register_msg() { + let mut deps = mock_dependencies(); + let response = handle_register_msg( + deps.as_mut(), + mock_env(), + mock_info(SENDER, &[Coin::new(10, "usdt")]), + Addr::unchecked(CW_20_ADDRESS), + ) + .unwrap_err(); + + assert_eq!(response, ContractError::NotEnoughBalanceToPayDenomCreationFee, "incorrect error returned"); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, CW_20_ADDRESS); + assert!(!contract_registered, "contract was registered"); +} + +#[test] +fn it_returns_error_if_insufficient_coins_are_passed_register_msg() { + let mut deps = mock_dependencies(); + + let response = handle_register_msg( + deps.as_mut(), + mock_env(), + mock_info(SENDER, &[Coin::new(9, "inj")]), + Addr::unchecked(CW_20_ADDRESS), + ) + .unwrap_err(); + + assert_eq!(response, ContractError::NotEnoughBalanceToPayDenomCreationFee, "incorrect error returned"); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, CW_20_ADDRESS); + assert!(!contract_registered, "contract was registered"); +} + +#[test] +fn it_returns_error_if_no_coins_are_passed_register_msg() { + let mut deps = mock_dependencies(); + let response = handle_register_msg(deps.as_mut(), mock_env(), mock_info(SENDER, &[]), Addr::unchecked(CW_20_ADDRESS)).unwrap_err(); + + assert_eq!(response, ContractError::NotEnoughBalanceToPayDenomCreationFee, "incorrect error returned"); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, CW_20_ADDRESS); + assert!(!contract_registered, "contract was registered"); +} + +#[test] +fn it_returns_error_if_register_is_not_cw20_msg() { + let mut deps = mock_dependencies(); + deps.querier = WasmMockQuerier { + smart_query_handler: create_cw20_failing_info_query_handler(), + ..Default::default() + }; + + let response = handle_register_msg( + deps.as_mut(), + mock_env(), + mock_info(SENDER, &[Coin::new(10, "inj")]), + Addr::unchecked(CW_20_ADDRESS), + ) + .unwrap_err(); + + assert_eq!(response, ContractError::NotCw20Address, "incorrect error returned"); + + let contract_registered = CW20_CONTRACTS.contains(&deps.storage, CW_20_ADDRESS); + assert!(!contract_registered, "contract was registered"); +} diff --git a/contracts/cw20-adapter/tests/integration_test.rs b/contracts/cw20-adapter/tests/integration_test.rs new file mode 100644 index 0000000..d0b6b66 --- /dev/null +++ b/contracts/cw20-adapter/tests/integration_test.rs @@ -0,0 +1,48 @@ +mod common; + +use cosmwasm_std::testing::mock_info; +use cosmwasm_std::{Addr, Coin, Uint128}; +use cw20_adapter::common::get_denom; +use injective_cosmwasm::{create_simple_balance_bank_query_handler, mock_dependencies, WasmMockQuerier}; + +use cw20_adapter::contract::{execute, instantiate}; +use cw20_adapter::msg::{ExecuteMsg, InstantiateMsg}; + +use crate::common::{create_cw20_info_query_handler, mock_env}; + +pub const ADAPTER_CONTRACT: &str = "inj1zwv6feuzhy6a9wekh96cd57lsarmqlwxvdl4nk"; +pub const CW20_CONTRACT: &str = "inj1h0y3hssxf4vsdacfmjg720642cvpxwyqh35kpn"; +pub const ADMIN: &str = "inj1qg5ega6dykkxc307y25pecuufrjkxkag6xhp6y"; +pub const USER: &str = "inj1gfawuv6fslzjlfa4v7exv27mk6rpfeyv823eu2"; + +#[test] +fn it_can_perform_basic_operations() { + let mut deps = mock_dependencies(); + let mut wasm_querier = WasmMockQuerier::new(); + + wasm_querier.balance_query_handler = create_simple_balance_bank_query_handler(vec![Coin::new(10, "inj")]); + wasm_querier.smart_query_handler = create_cw20_info_query_handler(); + deps.querier = wasm_querier; + + let msg = InstantiateMsg {}; + + let info_inst = mock_info(ADMIN, &[]); + let _res_inst = instantiate(deps.as_mut(), mock_env(ADAPTER_CONTRACT), info_inst, msg).unwrap(); + + // send some tokens to a contract + let info_receive = mock_info(CW20_CONTRACT, &[]); + let msg = ExecuteMsg::Receive { + sender: USER.to_string(), + amount: Uint128::new(1000), + msg: Default::default(), + }; + let _res_receive = execute(deps.as_mut(), mock_env(ADAPTER_CONTRACT), info_receive, msg).unwrap(); + + let denom = get_denom(&Addr::unchecked(ADAPTER_CONTRACT), &Addr::unchecked(CW20_CONTRACT)); + // redeem some tokens to a contract + let info_redeem = mock_info(USER, &[Coin::new(800, denom)]); + let msg = ExecuteMsg::RedeemAndTransfer { recipient: None }; + let res_redeem = execute(deps.as_mut(), mock_env(ADAPTER_CONTRACT), info_redeem, msg); + + assert!(res_redeem.is_ok()); +} diff --git a/contracts/hub/Cargo.toml b/contracts/hub/Cargo.toml deleted file mode 100644 index 6a3857a..0000000 --- a/contracts/hub/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "steak-hub" -description = "Description of the contract" -version = { workspace = true } -authors = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -homepage = { workspace = true } -repository = { workspace = true } -documentation = { workspace = true } -keywords = { workspace = true } -rust-version = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw-storage-plus = { workspace = true } -steak = { workspace = true } -thiserror = { workspace = true } diff --git a/contracts/hub/README.md b/contracts/hub/README.md deleted file mode 100644 index e4cd15a..0000000 --- a/contracts/hub/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Steak Hub - -Description of the contract - -## License - -Contents of this crate are open source under [GNU Affero General Public License](../../LICENSE) v3 or later. diff --git a/contracts/hub/examples/schema.rs b/contracts/hub/examples/schema.rs deleted file mode 100644 index d77c5d2..0000000 --- a/contracts/hub/examples/schema.rs +++ /dev/null @@ -1,12 +0,0 @@ -use cosmwasm_schema::write_api; - -use steak::hub; - -fn main() { - write_api! { - instantiate: hub::InstantiateMsg, - sudo: hub::SudoMsg, - execute: hub::ExecuteMsg, - query: hub::QueryMsg, - } -} diff --git a/contracts/hub/src/contract.rs b/contracts/hub/src/contract.rs deleted file mode 100644 index f90205d..0000000 --- a/contracts/hub/src/contract.rs +++ /dev/null @@ -1,52 +0,0 @@ -use cosmwasm_std::{ - entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, -}; - -use steak::hub::{ExecuteMsg, InstantiateMsg, QueryMsg, SudoMsg}; - -use crate::{error::ContractError, execute, query}; - -pub const CONTRACT_NAME: &str = "crates.io:steak-hub"; -pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - execute::init(deps, msg.owner) -} - -#[entry_point] -pub fn sudo(deps: DepsMut, _env: Env, msg: SudoMsg) -> StdResult { - match msg { - SudoMsg::SetOwner { - new_owner, - } => execute::set_owner(deps, new_owner), - } -} - -#[entry_point] -pub fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::TransferOwnership { - new_owner, - } => execute::transfer_ownership(deps, info, new_owner), - ExecuteMsg::AcceptOwnership {} => execute::accept_ownership(deps, info), - } -} - -#[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query::config(deps)?), - } -} diff --git a/contracts/hub/src/error.rs b/contracts/hub/src/error.rs deleted file mode 100644 index a2fb335..0000000 --- a/contracts/hub/src/error.rs +++ /dev/null @@ -1,16 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error, PartialEq)] -pub enum ContractError { - #[error(transparent)] - Std(#[from] cosmwasm_std::StdError), - - #[error("sender is not owner")] - NotOwner, - - #[error("sender is not the pending owner")] - NotPendingOwner, - - #[error("there is no current pending ownership transfer")] - NoPendingOwnershipTransfer, -} diff --git a/contracts/hub/src/execute.rs b/contracts/hub/src/execute.rs deleted file mode 100644 index f2a490a..0000000 --- a/contracts/hub/src/execute.rs +++ /dev/null @@ -1,81 +0,0 @@ -use cosmwasm_std::{DepsMut, MessageInfo, Response, StdResult}; - -use steak::hub::Config; - -use crate::{error::ContractError, state::CONFIG}; - -pub fn init(deps: DepsMut, owner: String) -> StdResult { - CONFIG.save( - deps.storage, - &Config { - owner: deps.api.addr_validate(&owner)?, - pending_owner: None, - }, - )?; - - Ok(Response::new() - .add_attribute("action", "steak/hub/init") - .add_attribute("owner", owner)) -} - -pub fn set_owner(deps: DepsMut, new_owner: String) -> StdResult { - let cfg = CONFIG.load(deps.storage)?; - - CONFIG.save( - deps.storage, - &Config { - owner: deps.api.addr_validate(&new_owner)?, - pending_owner: None, - }, - )?; - - Ok(Response::new() - .add_attribute("action", "steak/hub/set_owner") - .add_attribute("previous_owner", cfg.owner) - .add_attribute("new_owner", new_owner)) -} - -pub fn transfer_ownership( - deps: DepsMut, - info: MessageInfo, - new_owner: String, -) -> Result { - let mut cfg = CONFIG.load(deps.storage)?; - - if info.sender != cfg.owner { - return Err(ContractError::NotOwner); - } - - cfg.pending_owner = Some(deps.api.addr_validate(&new_owner)?); - CONFIG.save(deps.storage, &cfg)?; - - Ok(Response::new() - .add_attribute("action", "steak/hub/transfer_ownership") - .add_attribute("current_owner", cfg.owner) - .add_attribute("proposed_owner", new_owner)) -} - -pub fn accept_ownership(deps: DepsMut, info: MessageInfo) -> Result { - let cfg = CONFIG.load(deps.storage)?; - - if let Some(pending_owner) = cfg.pending_owner { - if info.sender != pending_owner { - return Err(ContractError::NotPendingOwner); - } - } else { - return Err(ContractError::NoPendingOwnershipTransfer); - } - - CONFIG.save( - deps.storage, - &Config { - owner: info.sender.clone(), - pending_owner: None, - }, - )?; - - Ok(Response::new() - .add_attribute("action", "steak/hub/accept_ownership") - .add_attribute("previous_owner", cfg.owner) - .add_attribute("new_owner", info.sender)) -} diff --git a/contracts/hub/src/lib.rs b/contracts/hub/src/lib.rs deleted file mode 100644 index 55ce95e..0000000 --- a/contracts/hub/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[cfg(not(feature = "library"))] -pub mod contract; -pub mod error; -pub mod execute; -pub mod query; -pub mod state; diff --git a/contracts/hub/src/query.rs b/contracts/hub/src/query.rs deleted file mode 100644 index 2c52da1..0000000 --- a/contracts/hub/src/query.rs +++ /dev/null @@ -1,13 +0,0 @@ -use cosmwasm_std::{Deps, StdResult}; - -use steak::hub::Config; - -use crate::state::CONFIG; - -pub fn config(deps: Deps) -> StdResult> { - let cfg = CONFIG.load(deps.storage)?; - Ok(Config { - owner: cfg.owner.into(), - pending_owner: cfg.pending_owner.map(String::from), - }) -} diff --git a/contracts/hub/src/state.rs b/contracts/hub/src/state.rs deleted file mode 100644 index 5bd3da9..0000000 --- a/contracts/hub/src/state.rs +++ /dev/null @@ -1,6 +0,0 @@ -use cosmwasm_std::Addr; -use cw_storage_plus::Item; - -use steak::hub::Config; - -pub const CONFIG: Item> = Item::new("cfg"); diff --git a/contracts/hub/tests/tests.rs b/contracts/hub/tests/tests.rs deleted file mode 100644 index b5dbc46..0000000 --- a/contracts/hub/tests/tests.rs +++ /dev/null @@ -1,120 +0,0 @@ -use cosmwasm_std::{ - testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - Empty, OwnedDeps, -}; - -use steak::hub::{Config, InstantiateMsg}; -use steak_hub::{contract, execute, query, error::ContractError}; - -fn setup_test() -> OwnedDeps { - let mut deps = mock_dependencies(); - - contract::instantiate( - deps.as_mut(), - mock_env(), - mock_info("larry", &[]), - InstantiateMsg { - owner: "larry".into(), - }, - ) - .unwrap(); - - deps -} - -#[test] -fn initializing() { - let deps = setup_test(); - - let cfg = query::config(deps.as_ref()).unwrap(); - assert_eq!( - cfg, - Config { - owner: "larry".into(), - pending_owner: None, - }, - ); -} - -#[test] -fn setting_owner() { - let mut deps = setup_test(); - - execute::set_owner(deps.as_mut(), "jake".into()).unwrap(); - - let cfg = query::config(deps.as_ref()).unwrap(); - assert_eq!( - cfg, - Config { - owner: "jake".into(), - pending_owner: None, - }, - ); -} - -#[test] -fn transferring_ownership() { - let mut deps = setup_test(); - - // only owner can propose ownership transferrs - { - let err = execute::transfer_ownership( - deps.as_mut(), - mock_info("pumpkin", &[]), - "pumpkin".into(), - ) - .unwrap_err(); - assert_eq!(err, ContractError::NotOwner); - } - - // tne owner properly proposes an ownership transfer - { - execute::transfer_ownership( - deps.as_mut(), - mock_info("larry", &[]), - "jake".into(), - ) - .unwrap(); - - let cfg = query::config(deps.as_ref()).unwrap(); - assert_eq!(cfg.pending_owner, Some("jake".into())); - } -} - -#[test] -fn accepting_ownership() { - let mut deps = setup_test(); - - // attempt to accept ownership when there isn't a pending ownership transfer yet - { - let err = execute::accept_ownership(deps.as_mut(), mock_info("pumpkin", &[])).unwrap_err(); - assert_eq!(err, ContractError::NoPendingOwnershipTransfer); - } - - execute::transfer_ownership( - deps.as_mut(), - mock_info("larry", &[]), - "jake".into(), - ) - .unwrap(); - - // only the pending owner can accept ownership - { - let err = execute::accept_ownership(deps.as_mut(), mock_info("pumpkin", &[])).unwrap_err(); - assert_eq!(err, ContractError::NotPendingOwner); - } - - // the pending owner properly accepts ownership - { - execute::accept_ownership(deps.as_mut(), mock_info("jake", &[])).unwrap(); - - let cfg = query::config(deps.as_ref()).unwrap(); - assert_eq!( - cfg, - Config { - owner: "jake".into(), - pending_owner: None, - }, - ); - } -} diff --git a/packages/steak/Cargo.toml b/packages/steak/Cargo.toml deleted file mode 100644 index 1315024..0000000 --- a/packages/steak/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "steak" -description = "Description of the package" -version = { workspace = true } -authors = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -homepage = { workspace = true } -repository = { workspace = true } -documentation = { workspace = true } -keywords = { workspace = true } -rust-version = { workspace = true } - -[dependencies] -cosmwasm-schema = { workspace = true } diff --git a/packages/steak/README.md b/packages/steak/README.md deleted file mode 100644 index 210d367..0000000 --- a/packages/steak/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Steak - -Description of the package - -## License - -Contents of this crate are open source under [GNU Affero General Public License](../../LICENSE) v3 or later. diff --git a/packages/steak/src/hub.rs b/packages/steak/src/hub.rs deleted file mode 100644 index 943a946..0000000 --- a/packages/steak/src/hub.rs +++ /dev/null @@ -1,43 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; - -#[cw_serde] -pub struct Config { - /// Address of the current contract owner - pub owner: T, - /// If there is a pending ownership transfer, the proposed new owner - pub pending_owner: Option, -} - -#[cw_serde] -pub struct InstantiateMsg { - /// Address of the current contract owner - pub owner: String, -} - -#[cw_serde] -pub enum SudoMsg { - /// Forcibly reset the contract's owner. - /// Delete the pending ownership transfer if there is one. - SetOwner { - new_owner: String, - }, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Propose a transfer of the contract's ownership to another account - TransferOwnership { - new_owner: String, - }, - - /// Accept the proposed ownership transfer - AcceptOwnership {}, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Return contract's configurations - #[returns(Config)] - Config {}, -} diff --git a/packages/steak/src/lib.rs b/packages/steak/src/lib.rs deleted file mode 100644 index 2dbca3b..0000000 --- a/packages/steak/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod hub; diff --git a/rustfmt.toml b/rustfmt.toml index 980ec57..351fd5f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,5 @@ -match_block_trailing_comma = true -max_width = 100 -use_small_heuristics = "off" +# stable +newline_style = "unix" +hard_tabs = false +tab_spaces = 4 +max_width = 150 \ No newline at end of file