From 522c39f4cea0dc039b3a8e5d2387cee232d1aa1f Mon Sep 17 00:00:00 2001 From: Obie Kaku Date: Wed, 18 Dec 2024 16:33:47 -0600 Subject: [PATCH] cosmwasm: added cw20_wrapped cw-multi-tests --- cosmwasm/Cargo.lock | 327 +++- cosmwasm/Cargo.toml | 3 +- cosmwasm/contracts/cw20-wrapped/Cargo.toml | 6 + .../cw20-wrapped/tests/integration.rs | 1344 ++++++++++++++++- cosmwasm/contracts/mock-counter/Cargo.toml | 25 + .../contracts/mock-counter/src/contract.rs | 68 + .../contracts/mock-counter/src/interface.rs | 57 + cosmwasm/contracts/mock-counter/src/lib.rs | 9 + cosmwasm/contracts/mock-counter/src/msg.rs | 28 + cosmwasm/contracts/mock-counter/src/tests.rs | 95 ++ 10 files changed, 1883 insertions(+), 79 deletions(-) create mode 100644 cosmwasm/contracts/mock-counter/Cargo.toml create mode 100644 cosmwasm/contracts/mock-counter/src/contract.rs create mode 100644 cosmwasm/contracts/mock-counter/src/interface.rs create mode 100644 cosmwasm/contracts/mock-counter/src/lib.rs create mode 100644 cosmwasm/contracts/mock-counter/src/msg.rs create mode 100644 cosmwasm/contracts/mock-counter/src/tests.rs diff --git a/cosmwasm/Cargo.lock b/cosmwasm/Cargo.lock index 1799e32f53..7b8b673fcf 100644 --- a/cosmwasm/Cargo.lock +++ b/cosmwasm/Cargo.lock @@ -86,18 +86,36 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bitflags" version = "1.3.2" @@ -129,6 +147,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "bnum" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" + [[package]] name = "bs58" version = "0.4.0" @@ -226,31 +250,32 @@ dependencies = [ [[package]] name = "cosmwasm-crypto" -version = "1.2.7" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb64554a91d6a9231127f4355d351130a0b94e663d5d9dc8b3a54ca17d83de49" +checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" dependencies = [ "digest 0.10.6", + "ecdsa 0.16.7", "ed25519-zebra", - "k256", + "k256 0.13.1", "rand_core 0.6.4", "thiserror", ] [[package]] name = "cosmwasm-derive" -version = "1.2.7" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0fb2ce09f41a3dae1a234d56a9988f9aff4c76441cd50ef1ee9a4f20415b028" +checksum = "d67457e4acb04e738788d3489e343957455df2c4643f2b53050eb052ca631d19" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.2.7" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230e5d1cefae5331db8934763c81b9c871db6a2cd899056a5694fa71d292c815" +checksum = "13bf06bf1c7ea737f6b3d955d9cabeb8cbbe4dcb8dea392e30f6fab4493a4b7a" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -261,9 +286,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.7" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43dadf7c23406cb28079d69e6cb922c9c29b9157b0fe887e3b79c783b7d4bcb8" +checksum = "077fe378f16b54e3d0a57adb3f39a65bcf7bdbda6a5eade2f8ba7755c2fb1250" dependencies = [ "proc-macro2", "quote", @@ -272,11 +297,13 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.2.7" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4337eef8dfaf8572fe6b6b415d6ec25f9308c7bb09f2da63789209fb131363be" +checksum = "ad011ae7447188e26e4a7dbca2fcd0fc186aa21ae5c86df0503ea44c78f9e469" dependencies = [ - "base64", + "base64 0.21.7", + "bech32", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -286,8 +313,8 @@ dependencies = [ "serde", "serde-json-wasm 0.5.1", "sha2 0.10.6", + "static_assertions", "thiserror", - "uint", ] [[package]] @@ -324,6 +351,19 @@ dependencies = [ "wasmer-middlewares", ] +[[package]] +name = "counter" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus 1.1.0", + "cw20", + "serde", + "thiserror", +] + [[package]] name = "cpufeatures" version = "0.2.7" @@ -462,6 +502,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -648,9 +700,12 @@ dependencies = [ name = "cw20-wrapped-2" version = "0.1.0" dependencies = [ + "anyhow", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", + "counter", + "cw-multi-test", "cw2 0.13.4", "cw20", "cw20-base", @@ -691,7 +746,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.90", ] [[package]] @@ -702,7 +757,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core", "quote", - "syn 2.0.23", + "syn 2.0.90", ] [[package]] @@ -715,6 +770,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "derivative" version = "2.2.0" @@ -742,6 +807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -784,10 +850,24 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + +[[package]] +name = "ecdsa" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +dependencies = [ + "der 0.7.9", + "digest 0.10.6", + "elliptic-curve 0.13.7", + "rfc6979 0.4.0", + "signature 2.1.0", + "spki 0.7.3", ] [[package]] @@ -817,16 +897,35 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct", - "crypto-bigint", - "der", + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest 0.10.6", + "ff 0.12.1", + "generic-array", + "group 0.12.1", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "sec1 0.3.0", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9775b22bc152ad86a0cf23f0f348b884b26add12bf741e7ffc4d4ab2ab4d205" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", "digest 0.10.6", - "ff", + "ff 0.13.0", "generic-array", - "group", - "pkcs8", + "group 0.13.0", + "pkcs8 0.10.2", "rand_core 0.6.4", - "sec1", + "sec1 0.7.3", "subtle", "zeroize", ] @@ -869,7 +968,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.90", ] [[package]] @@ -918,6 +1017,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fnv" version = "1.0.7" @@ -938,6 +1047,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -974,7 +1084,7 @@ version = "0.1.0" dependencies = [ "accountant", "anyhow", - "base64", + "base64 0.13.1", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", @@ -998,7 +1108,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core 0.6.4", "subtle", ] @@ -1148,12 +1269,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", "sha2 0.10.6", "sha3 0.10.7", ] +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa 0.16.7", + "elliptic-curve 0.13.7", + "once_cell", + "sha2 0.10.6", + "signature 2.1.0", +] + [[package]] name = "keccak" version = "0.1.3" @@ -1305,7 +1440,7 @@ version = "0.1.0" dependencies = [ "accountant", "anyhow", - "base64", + "base64 0.13.1", "byteorder", "cosmwasm-schema", "cosmwasm-std", @@ -1395,8 +1530,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "spki", + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.9", + "spki 0.7.3", ] [[package]] @@ -1425,9 +1570,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1509,9 +1654,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1606,11 +1751,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.4.9", "hmac", "zeroize", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "rkyv" version = "0.7.41" @@ -1676,9 +1831,9 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "schemars" -version = "0.8.12" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -1688,14 +1843,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.12" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] @@ -1716,10 +1871,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct", - "der", + "base16ct 0.1.1", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.9", "generic-array", - "pkcs8", + "pkcs8 0.10.2", "subtle", "zeroize", ] @@ -1732,9 +1901,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -1768,24 +1937,24 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.90", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] @@ -1803,7 +1972,7 @@ dependencies = [ name = "serde_wormhole" version = "0.1.0" dependencies = [ - "base64", + "base64 0.13.1", "itoa", "serde", "serde_bytes", @@ -1880,6 +2049,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.6", + "rand_core 0.6.4", +] + [[package]] name = "simdutf8" version = "0.1.4" @@ -1899,7 +2078,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.9", ] [[package]] @@ -1933,9 +2122,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1992,7 +2181,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.90", ] [[package]] @@ -2061,7 +2250,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.90", ] [[package]] @@ -2079,18 +2268,6 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-ident" version = "1.0.8" @@ -2665,7 +2842,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "k256", + "k256 0.11.6", "schemars", "serde", "serde_wormhole", @@ -2682,7 +2859,7 @@ dependencies = [ "cw-multi-test", "generic-array", "hex", - "k256", + "k256 0.11.6", "schemars", "serde", "serde-json-wasm 0.4.1", diff --git a/cosmwasm/Cargo.toml b/cosmwasm/Cargo.toml index e2843522e9..6f3079e2ed 100644 --- a/cosmwasm/Cargo.toml +++ b/cosmwasm/Cargo.toml @@ -13,7 +13,8 @@ members = [ "packages/cw_transcode", "contracts/wormhole-ibc", "contracts/wormchain-ibc-receiver", - "contracts/ibc-translator" + "contracts/ibc-translator", + "contracts/mock-counter" ] # Needed to prevent unwanted feature unification between normal builds and dev builds. See diff --git a/cosmwasm/contracts/cw20-wrapped/Cargo.toml b/cosmwasm/contracts/cw20-wrapped/Cargo.toml index 13ec0b6321..499cb659f6 100644 --- a/cosmwasm/contracts/cw20-wrapped/Cargo.toml +++ b/cosmwasm/contracts/cw20-wrapped/Cargo.toml @@ -23,3 +23,9 @@ cw2 = { version = "0.13.2" } cw20 = { version = "0.13.2" } cw20-base = { version = "0.13.2", features = ["library"] } thiserror = { version = "1.0.31" } + +[dev-dependencies] +cw-multi-test = "0.14" +# this is just used for testing cw20-wrapped's hook logic +counter = { path = "../mock-counter", features = ["interface"] } +anyhow = "1.0.40" \ No newline at end of file diff --git a/cosmwasm/contracts/cw20-wrapped/tests/integration.rs b/cosmwasm/contracts/cw20-wrapped/tests/integration.rs index a0ee368542..5d32002a53 100644 --- a/cosmwasm/contracts/cw20-wrapped/tests/integration.rs +++ b/cosmwasm/contracts/cw20-wrapped/tests/integration.rs @@ -1,16 +1,20 @@ +use std::convert::TryInto; + use cosmwasm_std::{ from_slice, testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - Addr, Api, OwnedDeps, Response, Storage, Uint128, + to_binary, Addr, Api, Binary, OwnedDeps, Response, StdError, StdResult, Storage, Uint128, }; use cosmwasm_storage::to_length_prefixed; -use cw20::TokenInfoResponse; +use counter::msg as counter_msgs; +use cw20::{AllowanceResponse, BalanceResponse, Expiration, TokenInfoResponse}; use cw20_wrapped_2::{ contract::{execute, instantiate, query}, - msg::{ExecuteMsg, InstantiateMsg, QueryMsg, WrappedAssetInfoResponse}, + msg::{ExecuteMsg, InitHook, InitMint, InstantiateMsg, QueryMsg, WrappedAssetInfoResponse}, state::{WrappedAssetInfo, KEY_WRAPPED_ASSET}, ContractError, }; +use cw_multi_test::{App, AppBuilder, ContractWrapper, Executor}; static INITIALIZER: &str = "addr0000"; static RECIPIENT: &str = "addr2222"; @@ -175,3 +179,1337 @@ fn transfer_works() { check_balance(&deps, &sender, &Uint128::new(123)); check_balance(&deps, &recipient, &Uint128::new(123_123_000)); } + +struct Cw20App { + app: App, + admin: Addr, + user: Addr, + cw20_wrapped_contract: Addr, + cw20_code_id: u64, +} + +pub fn create_cw20_wrapped_app(instantiate_msg: Option) -> Cw20App { + let instantiate_msg = instantiate_msg.unwrap_or(InstantiateMsg { + name: "Integers".into(), + symbol: "INT".into(), + asset_chain: 1, + asset_address: vec![1; 32].into(), + decimals: 10, + mint: None, + init_hook: None, + }); + + let mut app = AppBuilder::new().build(|_, _, _| {}); + + let admin = Addr::unchecked("admin"); + let user = Addr::unchecked("user"); + + let cw20_wrapped_wrapper = ContractWrapper::new_with_empty(execute, instantiate, query); + + let code_id = app.store_code(Box::new(cw20_wrapped_wrapper)); + + let contract_addr = app + .instantiate_contract( + code_id, + admin.clone(), + &instantiate_msg, + &[], + "cw20_wrapped", + Some(admin.to_string()), + ) + .unwrap(); + + Cw20App { + app, + admin, + user, + cw20_wrapped_contract: contract_addr, + cw20_code_id: code_id, + } +} + +struct Cw20AndCounterApp { + app: App, + admin: Addr, + user: Addr, + cw20_wrapped_contract: Addr, + cw20_code_id: u64, + counter_address: Addr, +} + +pub fn create_cw20_and_counter() -> Cw20AndCounterApp { + let Cw20App { + mut app, + admin, + user, + cw20_code_id, + cw20_wrapped_contract, + } = create_cw20_wrapped_app(None); + + let counter_code_id = app.store_code(Box::new(ContractWrapper::new( + counter::contract::execute, + counter::contract::instantiate, + counter::contract::query, + ))); + + let counter_address = app + .instantiate_contract( + counter_code_id, + user.clone(), + &counter_msgs::InstantiateMsg {}, + &[], + "counter", + None, + ) + .unwrap(); + + let initial_count: counter_msgs::CountResponse = app + .wrap() + .query_wasm_smart( + counter_address.clone(), + &counter_msgs::QueryMsg::GetCount {}, + ) + .unwrap(); + assert_eq!(initial_count.count, 0, "Initial count should be 0"); + + Cw20AndCounterApp { + app, + admin, + user, + cw20_wrapped_contract, + cw20_code_id, + counter_address, + } +} + +#[test] +pub fn instantiate_works() -> Result<(), anyhow::Error> { + let Cw20App { + cw20_wrapped_contract, + app, + admin, + user: _user, + .. + } = create_cw20_wrapped_app(None); + + let asset_info: WrappedAssetInfoResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::WrappedAssetInfo {}, + )?; + + assert_eq!( + asset_info, + WrappedAssetInfoResponse { + asset_chain: 1, + asset_address: vec![1; 32].into(), + bridge: admin, + } + ); + + let token_info: TokenInfoResponse = app + .wrap() + .query_wasm_smart(cw20_wrapped_contract.clone(), &QueryMsg::TokenInfo {})?; + + assert_eq!( + token_info, + TokenInfoResponse { + name: "Integers (Wormhole)".into(), + symbol: "INT".into(), + decimals: 10, + total_supply: Uint128::zero(), + } + ); + + Ok(()) +} + +#[test] +pub fn instantiate_with_minter() -> Result<(), anyhow::Error> { + let initial_balance_user = Addr::unchecked("user_with_initial_balance"); + + let Cw20App { + cw20_wrapped_contract, + mut app, + admin, + user, + .. + } = create_cw20_wrapped_app(Some(InstantiateMsg { + name: "Integers".into(), + symbol: "INT".into(), + asset_chain: 1, + asset_address: vec![1; 32].into(), + decimals: 6, + mint: Some(InitMint { + recipient: initial_balance_user.to_string(), + amount: Uint128::from(1_000_000u128), + }), + init_hook: None, + })); + + let init_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Balance { + address: initial_balance_user.to_string(), + }, + )?; + assert_eq!( + init_balance.balance, + Uint128::from(1_000_000u128), + "User should have balance from tge" + ); + + let mint_info = ExecuteMsg::Mint { + recipient: user.to_string(), + amount: 750_000u128.into(), + }; + + let unauthorized_user_mint = + app.execute_contract(user.clone(), cw20_wrapped_contract.clone(), &mint_info, &[]); + assert!( + unauthorized_user_mint.is_err(), + "Only Admin should be able to mint" + ); + + // Admin mints to user + app.execute_contract( + admin.clone(), + cw20_wrapped_contract.clone(), + &mint_info, + &[], + )?; + + let user_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Balance { + address: user.to_string(), + }, + )?; + + assert_eq!( + user_balance.balance, + Uint128::from(750_000u128), + "User should have a balance since the admin minted to the user's address" + ); + + Ok(()) +} + +#[test] +fn instantiate_with_inithook() -> Result<(), anyhow::Error> { + let Cw20AndCounterApp { + mut app, + admin, + cw20_code_id, + counter_address, + .. + } = create_cw20_and_counter(); + + let instantiation_with_init_hook = InstantiateMsg { + name: "Floats".into(), + symbol: "FLT".into(), + asset_chain: 1, + asset_address: vec![2; 32].into(), + decimals: 6, + mint: None, + init_hook: Some(InitHook { + msg: to_binary(&counter_msgs::ExecuteMsg::Increment {})?, + contract_addr: counter_address.to_string(), + }), + }; + + app.instantiate_contract( + cw20_code_id, + admin.clone(), + &instantiation_with_init_hook, + &[], + "floats_cw20", + None, + )?; + + let incremented_count: counter_msgs::CountResponse = app.wrap().query_wasm_smart( + counter_address.clone(), + &counter_msgs::QueryMsg::GetCount {}, + )?; + assert_eq!( + incremented_count.count, 1, + "After FLT is instantiated, the count should be incremented" + ); + + Ok(()) +} + +#[test] +fn update_metadata_functionality() -> Result<(), anyhow::Error> { + let Cw20App { + cw20_wrapped_contract, + mut app, + admin, + user, + .. + } = create_cw20_wrapped_app(None); + + // Verify initial metadata + let initial_token_info: TokenInfoResponse = app + .wrap() + .query_wasm_smart(cw20_wrapped_contract.clone(), &QueryMsg::TokenInfo {})?; + + assert_eq!( + initial_token_info, + TokenInfoResponse { + name: "Integers (Wormhole)".into(), + symbol: "INT".into(), + decimals: 10, + total_supply: Uint128::zero(), + } + ); + + // Try to update metadata as non-admin (should fail) + let unauthorized_update = app.execute_contract( + user.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::UpdateMetadata { + name: "Updated Token".into(), + symbol: "UPDT".into(), + }, + &[], + ); + assert!( + unauthorized_update.is_err(), + "Non-admin should not be able to update metadata" + ); + + // Verify metadata hasn't changed after failed update + let unchanged_token_info: TokenInfoResponse = app + .wrap() + .query_wasm_smart(cw20_wrapped_contract.clone(), &QueryMsg::TokenInfo {})?; + assert_eq!(unchanged_token_info, initial_token_info); + + // Update metadata as admin + app.execute_contract( + admin.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::UpdateMetadata { + name: "Updated Integers".into(), + symbol: "UINT".into(), + }, + &[], + )?; + + // Verify metadata was updated + let updated_token_info: TokenInfoResponse = app + .wrap() + .query_wasm_smart(cw20_wrapped_contract.clone(), &QueryMsg::TokenInfo {})?; + + assert_eq!( + updated_token_info, + TokenInfoResponse { + name: "Updated Integers (Wormhole)".into(), + symbol: "UINT".into(), + decimals: 10, + total_supply: Uint128::zero(), + } + ); + + // Update metadata again to verify multiple updates work + app.execute_contract( + admin.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::UpdateMetadata { + name: "Final Name".into(), + symbol: "FINAL".into(), + }, + &[], + )?; + + // Verify final metadata update + let final_token_info: TokenInfoResponse = app + .wrap() + .query_wasm_smart(cw20_wrapped_contract.clone(), &QueryMsg::TokenInfo {})?; + + assert_eq!( + final_token_info, + TokenInfoResponse { + name: "Final Name (Wormhole)".into(), + symbol: "FINAL".into(), + decimals: 10, + total_supply: Uint128::zero(), + } + ); + + Ok(()) +} + +#[test] +fn send_tokens_functionality() -> Result<(), anyhow::Error> { + let Cw20AndCounterApp { + mut app, + admin, + user, + counter_address, + cw20_wrapped_contract: cw20_address, + .. + } = create_cw20_and_counter(); + + app.execute_contract( + admin.clone(), + cw20_address.clone(), + &ExecuteMsg::Mint { + recipient: user.to_string(), + amount: 1_000_000u128.into(), + }, + &[], + )?; + + let send_to_counter_app = ExecuteMsg::Send { + contract: counter_address.to_string(), + amount: Uint128::from(1_000_000u128), + msg: to_binary(&counter_msgs::ExecuteMsg::Increment {})?, + }; + + let failing_send = app.execute_contract( + admin.clone(), + cw20_address.clone(), + &send_to_counter_app, + &[], + ); + assert!( + failing_send.is_err(), + "Admin should not be able to send to the counter contract since it has no balance" + ); + + // Now send it as user who actually has a balance + let send_resp = app.execute_contract( + user.clone(), + cw20_address.clone(), + &send_to_counter_app, + &[], + ); + println!("send response {:?}", send_resp); + send_resp?; + + let incremented_count: counter_msgs::CountResponse = app.wrap().query_wasm_smart( + counter_address.clone(), + &counter_msgs::QueryMsg::GetCount {}, + )?; + assert_eq!( + incremented_count.count, 1, + "Post-send to counter contract, the count should be incremented" + ); + + // Try to send it again as User to double check that things fail properly + let failing_send = app.execute_contract( + user.clone(), + cw20_address.clone(), + &send_to_counter_app, + &[], + ); + assert!( + failing_send.is_err(), + "User should not be able to send to the counter contract since it has no balance" + ); + + let incremented_count: counter_msgs::CountResponse = app.wrap().query_wasm_smart( + counter_address.clone(), + &counter_msgs::QueryMsg::GetCount {}, + )?; + assert_eq!( + incremented_count.count, 1, + "Count should still be 1 since it hasn't been incremented again" + ); + + Ok(()) +} + +// Test that transfer works correctly +#[test] +fn transfer_tokens_functionality() -> Result<(), anyhow::Error> { + let Cw20App { + cw20_wrapped_contract, + mut app, + admin, + user, + .. + } = create_cw20_wrapped_app(None); + + // Mint some tokens to the user + app.execute_contract( + admin.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Mint { + recipient: user.to_string(), + amount: 1_000_000u128.into(), + }, + &[], + )?; + + // Validate the user has the correct balance + let user_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Balance { + address: user.to_string(), + }, + )?; + assert_eq!(user_balance.balance, Uint128::from(1_000_000u128)); + + // Attempt to transfer as the admin which should fail + let admin_transfer = app.execute_contract( + admin.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Transfer { + recipient: admin.to_string(), + amount: 1_000_000u128.into(), + }, + &[], + ); + assert!( + admin_transfer.is_err(), + "Admin should not be able to transfer tokens since it has no balance" + ); + + // Attempt to transfer too much as the user which should fail + let user_transfer = app.execute_contract( + user.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Transfer { + recipient: admin.to_string(), + amount: 1_000_001u128.into(), + }, + &[], + ); + assert!( + user_transfer.is_err(), + "User should not be able to transfer more tokens than they have" + ); + + // Transfer 500,000 tokens from the user to the admin + app.execute_contract( + user.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Transfer { + recipient: admin.to_string(), + amount: 500_000u128.into(), + }, + &[], + )?; + + // Validate the user has the correct balance + let user_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Balance { + address: user.to_string(), + }, + )?; + assert_eq!(user_balance.balance, Uint128::from(500_000u128)); + + // Validate the admin has the correct balance + let admin_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Balance { + address: admin.to_string(), + }, + )?; + assert_eq!(admin_balance.balance, Uint128::from(500_000u128)); + + Ok(()) +} + +#[test] +fn burn_functionality() -> Result<(), anyhow::Error> { + let Cw20App { + cw20_wrapped_contract, + mut app, + admin, + user, + .. + } = create_cw20_wrapped_app(None); + + let burn_user = Addr::unchecked("burner"); + let approved_burner = Addr::unchecked("approved_burner"); + + // Mint some tokens to the burn user + app.execute_contract( + admin.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Mint { + recipient: burn_user.to_string(), + amount: 1_000_000u128.into(), + }, + &[], + )?; + + // Verify initial balance + let initial_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Balance { + address: burn_user.to_string(), + }, + )?; + assert_eq!(initial_balance.balance, Uint128::from(1_000_000u128)); + + // Try to burn tokens without allowance (should fail) + let unauthorized_self_burn = app.execute_contract( + burn_user.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Burn { + account: burn_user.to_string(), + amount: 500_000u128.into(), + }, + &[], + ); + assert!( + unauthorized_self_burn.is_err(), + "Should not be able to burn tokens without allowance from another address" + ); + + // Try to set allowance for self (should fail) + let self_allowance = app.execute_contract( + burn_user.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: burn_user.to_string(), + amount: 500_000u128.into(), + expires: None, + }, + &[], + ); + assert!( + self_allowance.is_err(), + "Should not be able to set allowance for self" + ); + + // Set up allowance for approved burner + app.execute_contract( + burn_user.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: approved_burner.to_string(), + amount: 500_000u128.into(), + expires: None, + }, + &[], + )?; + + // Try to burn more tokens than allowance (should fail) + let excessive_burn = app.execute_contract( + approved_burner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Burn { + account: burn_user.to_string(), + amount: 750_000u128.into(), + }, + &[], + ); + assert!( + excessive_burn.is_err(), + "Should not be able to burn more tokens than allowed" + ); + + // Successfully burn tokens with proper allowance + app.execute_contract( + approved_burner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Burn { + account: burn_user.to_string(), + amount: 500_000u128.into(), + }, + &[], + )?; + + // Verify remaining balance + let remaining_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Balance { + address: burn_user.to_string(), + }, + )?; + assert_eq!(remaining_balance.balance, Uint128::from(500_000u128)); + + // Verify total supply has decreased + let token_info: TokenInfoResponse = app + .wrap() + .query_wasm_smart(cw20_wrapped_contract.clone(), &QueryMsg::TokenInfo {})?; + assert_eq!( + token_info.total_supply, + Uint128::from(500_000u128), + "Total supply should reflect burned tokens" + ); + + // Verify allowance was properly decreased + let allowance: AllowanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Allowance { + owner: burn_user.to_string(), + spender: approved_burner.to_string(), + }, + )?; + assert_eq!( + allowance.allowance, + Uint128::zero(), + "Allowance should be depleted after burn" + ); + + Ok(()) +} + +#[test] +/// BurnFrom uses identical logic to Burn +fn burn_from_functionality() -> Result<(), anyhow::Error> { + let Cw20App { + cw20_wrapped_contract, + mut app, + admin, + user, + .. + } = create_cw20_wrapped_app(None); + + let burn_user = Addr::unchecked("burner"); + let approved_burner = Addr::unchecked("approved_burner"); + + // Mint some tokens to the burn user + app.execute_contract( + admin.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Mint { + recipient: burn_user.to_string(), + amount: 1_000_000u128.into(), + }, + &[], + )?; + + // Verify initial balance + let initial_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Balance { + address: burn_user.to_string(), + }, + )?; + assert_eq!(initial_balance.balance, Uint128::from(1_000_000u128)); + + // Try to burn tokens without allowance (should fail) + let unauthorized_self_burn = app.execute_contract( + burn_user.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::BurnFrom { + owner: burn_user.to_string(), + amount: 500_000u128.into(), + }, + &[], + ); + assert!( + unauthorized_self_burn.is_err(), + "Should not be able to burn tokens without allowance from another address" + ); + + // Try to set allowance for self (should fail) + let self_allowance = app.execute_contract( + burn_user.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: burn_user.to_string(), + amount: 500_000u128.into(), + expires: None, + }, + &[], + ); + assert!( + self_allowance.is_err(), + "Should not be able to set allowance for self" + ); + + // Set up allowance for approved burner + app.execute_contract( + burn_user.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: approved_burner.to_string(), + amount: 500_000u128.into(), + expires: None, + }, + &[], + )?; + + // Try to burn more tokens than allowance (should fail) + let excessive_burn = app.execute_contract( + approved_burner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::BurnFrom { + owner: burn_user.to_string(), + amount: 750_000u128.into(), + }, + &[], + ); + assert!( + excessive_burn.is_err(), + "Should not be able to burn more tokens than allowed" + ); + + // Successfully burn tokens with proper allowance + app.execute_contract( + approved_burner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::BurnFrom { + owner: burn_user.to_string(), + amount: 500_000u128.into(), + }, + &[], + )?; + + // Verify remaining balance + let remaining_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Balance { + address: burn_user.to_string(), + }, + )?; + assert_eq!(remaining_balance.balance, Uint128::from(500_000u128)); + + // Verify total supply has decreased + let token_info: TokenInfoResponse = app + .wrap() + .query_wasm_smart(cw20_wrapped_contract.clone(), &QueryMsg::TokenInfo {})?; + assert_eq!( + token_info.total_supply, + Uint128::from(500_000u128), + "Total supply should reflect burned tokens" + ); + + // Verify allowance was properly decreased + let allowance: AllowanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Allowance { + owner: burn_user.to_string(), + spender: approved_burner.to_string(), + }, + )?; + assert_eq!( + allowance.allowance, + Uint128::zero(), + "Allowance should be depleted after burn" + ); + + Ok(()) +} + +#[test] +fn allowance_management() -> Result<(), anyhow::Error> { + let Cw20App { + cw20_wrapped_contract, + mut app, + admin, + user, + .. + } = create_cw20_wrapped_app(None); + + let token_owner = Addr::unchecked("token_owner"); + let spender = Addr::unchecked("spender"); + + // Mint tokens to the token owner + app.execute_contract( + admin.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Mint { + recipient: token_owner.to_string(), + amount: 1_000_000u128.into(), + }, + &[], + )?; + + // Test that setting allowance for self fails + let self_allowance_increase = app.execute_contract( + token_owner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: token_owner.to_string(), + amount: 500_000u128.into(), + expires: None, + }, + &[], + ); + assert!( + self_allowance_increase.is_err(), + "Should not be able to increase allowance for self" + ); + + // Test that decreasing allowance for self also fails + let self_allowance_decrease = app.execute_contract( + token_owner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::DecreaseAllowance { + spender: token_owner.to_string(), + amount: 500_000u128.into(), + expires: None, + }, + &[], + ); + assert!( + self_allowance_decrease.is_err(), + "Should not be able to decrease allowance for self" + ); + + // Test increasing allowance for another address + app.execute_contract( + token_owner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: spender.to_string(), + amount: 500_000u128.into(), + expires: None, + }, + &[], + )?; + + // Verify initial allowance + let allowance: AllowanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Allowance { + owner: token_owner.to_string(), + spender: spender.to_string(), + }, + )?; + assert_eq!(allowance.allowance, Uint128::from(500_000u128)); + + // Test increasing allowance again (should add to existing) + app.execute_contract( + token_owner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: spender.to_string(), + amount: 200_000u128.into(), + expires: None, + }, + &[], + )?; + + // Verify cumulative allowance + let increased_allowance: AllowanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Allowance { + owner: token_owner.to_string(), + spender: spender.to_string(), + }, + )?; + assert_eq!(increased_allowance.allowance, Uint128::from(700_000u128)); + + // Test decreasing allowance + app.execute_contract( + token_owner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::DecreaseAllowance { + spender: spender.to_string(), + amount: 300_000u128.into(), + expires: None, + }, + &[], + )?; + + // Verify decreased allowance + let decreased_allowance: AllowanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Allowance { + owner: token_owner.to_string(), + spender: spender.to_string(), + }, + )?; + assert_eq!(decreased_allowance.allowance, Uint128::from(400_000u128)); + + // Decreasing allowance bellow zero results in a zero allowance + let excessive_decrease = app.execute_contract( + token_owner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::DecreaseAllowance { + spender: spender.to_string(), + amount: 5_000_000u128.into(), + expires: None, + }, + &[], + )?; + + // Verify allowance is 0 + let zero_allowance: AllowanceResponse = app.wrap().query_wasm_smart( + cw20_wrapped_contract.clone(), + &QueryMsg::Allowance { + owner: token_owner.to_string(), + spender: spender.to_string(), + }, + )?; + assert_eq!(zero_allowance.allowance, Uint128::zero()); + + // Test expiration + let expiration = Expiration::AtHeight(app.block_info().height + 1); + + app.execute_contract( + token_owner.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: spender.to_string(), + amount: 100_000u128.into(), + expires: Some(expiration), + }, + &[], + )?; + + // Move to next block + app.update_block(|block| block.height += 2); + + // Try to use expired allowance (should fail) + let burn_with_expired = app.execute_contract( + spender.clone(), + cw20_wrapped_contract.clone(), + &ExecuteMsg::Burn { + account: token_owner.to_string(), + amount: 100_000u128.into(), + }, + &[], + ); + assert!( + burn_with_expired.is_err(), + "Should not be able to use expired allowance" + ); + + Ok(()) +} + +#[test] +fn send_from_functionality() -> Result<(), anyhow::Error> { + let Cw20AndCounterApp { + mut app, + admin, + user, + counter_address, + cw20_wrapped_contract: cw20_address, + .. + } = create_cw20_and_counter(); + + let token_owner = Addr::unchecked("token_owner"); + let spender = Addr::unchecked("spender"); + + // Mint tokens to the token owner + app.execute_contract( + admin.clone(), + cw20_address.clone(), + &ExecuteMsg::Mint { + recipient: token_owner.to_string(), + amount: 1_000_000u128.into(), + }, + &[], + )?; + + // Try to send_from without allowance (should fail) + let unauthorized_send = app.execute_contract( + spender.clone(), + cw20_address.clone(), + &ExecuteMsg::SendFrom { + owner: token_owner.to_string(), + contract: counter_address.to_string(), + amount: 500_000u128.into(), + msg: to_binary(&counter_msgs::ExecuteMsg::Increment {})?, + }, + &[], + ); + assert!( + unauthorized_send.is_err(), + "Should not be able to send_from without allowance" + ); + + // Set up allowance for spender + app.execute_contract( + token_owner.clone(), + cw20_address.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: spender.to_string(), + amount: 500_000u128.into(), + expires: None, + }, + &[], + )?; + + // Try to send_from more than allowance (should fail) + let excessive_send = app.execute_contract( + spender.clone(), + cw20_address.clone(), + &ExecuteMsg::SendFrom { + owner: token_owner.to_string(), + contract: counter_address.to_string(), + amount: 600_000u128.into(), + msg: to_binary(&counter_msgs::ExecuteMsg::Increment {})?, + }, + &[], + ); + assert!( + excessive_send.is_err(), + "Should not be able to send_from more than allowance" + ); + + // Successfully send tokens and increment counter + app.execute_contract( + spender.clone(), + cw20_address.clone(), + &ExecuteMsg::SendFrom { + owner: token_owner.to_string(), + contract: counter_address.to_string(), + amount: 300_000u128.into(), + msg: to_binary(&counter_msgs::ExecuteMsg::Increment {})?, + }, + &[], + )?; + + // Verify counter was incremented + let counter_response: counter_msgs::CountResponse = app.wrap().query_wasm_smart( + counter_address.clone(), + &counter_msgs::QueryMsg::GetCount {}, + )?; + assert_eq!(counter_response.count, 1, "Counter should be incremented"); + + // Verify token owner's balance was decreased + let owner_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_address.clone(), + &QueryMsg::Balance { + address: token_owner.to_string(), + }, + )?; + assert_eq!(owner_balance.balance, Uint128::from(700_000u128)); + + // Verify allowance was decreased + let updated_allowance: AllowanceResponse = app.wrap().query_wasm_smart( + cw20_address.clone(), + &QueryMsg::Allowance { + owner: token_owner.to_string(), + spender: spender.to_string(), + }, + )?; + assert_eq!(updated_allowance.allowance, Uint128::from(200_000u128)); + + // Test with expired allowance + let expiration = Expiration::AtHeight(app.block_info().height + 1); + + // Set new allowance with expiration + app.execute_contract( + token_owner.clone(), + cw20_address.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: spender.to_string(), + amount: 100_000u128.into(), + expires: Some(expiration), + }, + &[], + )?; + + // Move past expiration + app.update_block(|block| block.height += 2); + + // Try to send_from with expired allowance (should fail) + let expired_send = app.execute_contract( + spender.clone(), + cw20_address.clone(), + &ExecuteMsg::SendFrom { + owner: token_owner.to_string(), + contract: counter_address.to_string(), + amount: 50_000u128.into(), + msg: to_binary(&counter_msgs::ExecuteMsg::Increment {})?, + }, + &[], + ); + assert!( + expired_send.is_err(), + "Should not be able to send_from with expired allowance" + ); + + // Verify counter wasn't incremented again + let final_count: counter_msgs::CountResponse = app.wrap().query_wasm_smart( + counter_address.clone(), + &counter_msgs::QueryMsg::GetCount {}, + )?; + assert_eq!( + final_count.count, 1, + "Counter should not be incremented again" + ); + + Ok(()) +} + +#[test] +fn multiple_send_from_functionality() -> Result<(), anyhow::Error> { + let Cw20AndCounterApp { + mut app, + admin, + user, + counter_address, + cw20_wrapped_contract: cw20_address, + .. + } = create_cw20_and_counter(); + + let token_owner = Addr::unchecked("token_owner"); + let spender = Addr::unchecked("spender"); + + // Mint tokens to the token owner + app.execute_contract( + admin.clone(), + cw20_address.clone(), + &ExecuteMsg::Mint { + recipient: token_owner.to_string(), + amount: 1_000_000u128.into(), + }, + &[], + )?; + + // Set up allowance for spender + app.execute_contract( + token_owner.clone(), + cw20_address.clone(), + &ExecuteMsg::IncreaseAllowance { + spender: spender.to_string(), + amount: 750_000u128.into(), + expires: None, + }, + &[], + )?; + + // First send_from operation + app.execute_contract( + spender.clone(), + cw20_address.clone(), + &ExecuteMsg::SendFrom { + owner: token_owner.to_string(), + contract: counter_address.to_string(), + amount: 250_000u128.into(), + msg: to_binary(&counter_msgs::ExecuteMsg::Increment {})?, + }, + &[], + )?; + + // Verify first operation results + let count_after_first: counter_msgs::CountResponse = app.wrap().query_wasm_smart( + counter_address.clone(), + &counter_msgs::QueryMsg::GetCount {}, + )?; + assert_eq!( + count_after_first.count, 1, + "Counter should be incremented once" + ); + + let allowance_after_first: AllowanceResponse = app.wrap().query_wasm_smart( + cw20_address.clone(), + &QueryMsg::Allowance { + owner: token_owner.to_string(), + spender: spender.to_string(), + }, + )?; + assert_eq!( + allowance_after_first.allowance, + Uint128::from(500_000u128), + "Allowance should be decreased by first send" + ); + + // Second send_from operation + app.execute_contract( + spender.clone(), + cw20_address.clone(), + &ExecuteMsg::SendFrom { + owner: token_owner.to_string(), + contract: counter_address.to_string(), + amount: 200_000u128.into(), + msg: to_binary(&counter_msgs::ExecuteMsg::Increment {})?, + }, + &[], + )?; + + // Verify second operation results + let count_after_second: counter_msgs::CountResponse = app.wrap().query_wasm_smart( + counter_address.clone(), + &counter_msgs::QueryMsg::GetCount {}, + )?; + assert_eq!( + count_after_second.count, 2, + "Counter should be incremented twice" + ); + + let allowance_after_second: AllowanceResponse = app.wrap().query_wasm_smart( + cw20_address.clone(), + &QueryMsg::Allowance { + owner: token_owner.to_string(), + spender: spender.to_string(), + }, + )?; + assert_eq!( + allowance_after_second.allowance, + Uint128::from(300_000u128), + "Allowance should be decreased by second send" + ); + + // Third send_from operation + app.execute_contract( + spender.clone(), + cw20_address.clone(), + &ExecuteMsg::SendFrom { + owner: token_owner.to_string(), + contract: counter_address.to_string(), + amount: 300_000u128.into(), + msg: to_binary(&counter_msgs::ExecuteMsg::Increment {})?, + }, + &[], + )?; + + // Verify final state + let final_count: counter_msgs::CountResponse = app.wrap().query_wasm_smart( + counter_address.clone(), + &counter_msgs::QueryMsg::GetCount {}, + )?; + assert_eq!( + final_count.count, 3, + "Counter should be incremented three times" + ); + + let final_allowance: AllowanceResponse = app.wrap().query_wasm_smart( + cw20_address.clone(), + &QueryMsg::Allowance { + owner: token_owner.to_string(), + spender: spender.to_string(), + }, + )?; + assert_eq!( + final_allowance.allowance, + Uint128::zero(), + "Allowance should be fully spent" + ); + + let final_balance: BalanceResponse = app.wrap().query_wasm_smart( + cw20_address.clone(), + &QueryMsg::Balance { + address: token_owner.to_string(), + }, + )?; + assert_eq!( + final_balance.balance, + Uint128::from(250_000u128), + "Owner balance should reflect all three sends" + ); + + // Try one more send_from operation with depleted allowance (should fail) + let depleted_allowance_send = app.execute_contract( + spender.clone(), + cw20_address.clone(), + &ExecuteMsg::SendFrom { + owner: token_owner.to_string(), + contract: counter_address.to_string(), + amount: 100_000u128.into(), + msg: to_binary(&counter_msgs::ExecuteMsg::Increment {})?, + }, + &[], + ); + assert!( + depleted_allowance_send.is_err(), + "Should not be able to send_from after allowance is depleted" + ); + + // Verify counter wasn't incremented by failed transaction + let final_failed_count: counter_msgs::CountResponse = app.wrap().query_wasm_smart( + counter_address.clone(), + &counter_msgs::QueryMsg::GetCount {}, + )?; + assert_eq!( + final_failed_count.count, 3, + "Counter should not be incremented after failed send" + ); + + Ok(()) +} diff --git a/cosmwasm/contracts/mock-counter/Cargo.toml b/cosmwasm/contracts/mock-counter/Cargo.toml new file mode 100644 index 0000000000..cb2c75d42a --- /dev/null +++ b/cosmwasm/contracts/mock-counter/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "counter" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# Use library feature to disable all instantiate/execute/query exports +library = [] +# Use the interface feature to enable interface exports for integration testing +interface = ["cw-multi-test"] + +[dependencies] +cosmwasm-schema = "1.0.0" +cosmwasm-std = "1.0.0" +cw-storage-plus = "1.0.0" +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +thiserror = "1.0.31" +cw-multi-test = { version = "0.14", optional = true } +cw20 = { version = "0.13.2" } + +[dev-dependencies] +cw-multi-test = "0.14" \ No newline at end of file diff --git a/cosmwasm/contracts/mock-counter/src/contract.rs b/cosmwasm/contracts/mock-counter/src/contract.rs new file mode 100644 index 0000000000..b6f0bd1f32 --- /dev/null +++ b/cosmwasm/contracts/mock-counter/src/contract.rs @@ -0,0 +1,68 @@ +use crate::{msg::ReceiveMsg, CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; +use cosmwasm_std::{ + entry_point, from_binary, from_json, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, + Response, StdResult, +}; +use cw_storage_plus::Item; + +pub const COUNT: Item = Item::new("count"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: InstantiateMsg, +) -> StdResult { + COUNT.save(deps.storage, &0)?; + + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("count", 0.to_string())) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ExecuteMsg, +) -> StdResult { + match msg { + ExecuteMsg::Increment {} => increment(deps), + ExecuteMsg::Reset {} => reset(deps), + ExecuteMsg::Receive(receive_msg) => match from_json(receive_msg.msg)? { + ReceiveMsg::Increment {} => increment(deps), + ReceiveMsg::Reset {} => reset(deps), + }, + } +} + +pub fn increment(deps: DepsMut) -> StdResult { + COUNT.update(deps.storage, |mut count| -> StdResult<_> { + count += 1; + Ok(count) + })?; + + Ok(Response::new().add_attribute("method", "increment")) +} + +pub fn reset(deps: DepsMut) -> StdResult { + COUNT.save(deps.storage, &0)?; + + Ok(Response::new() + .add_attribute("method", "reset") + .add_attribute("count", 0u32.to_string())) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetCount {} => to_binary(&query_count(deps)?), + } +} + +fn query_count(deps: Deps) -> StdResult { + let count = COUNT.load(deps.storage)?; + Ok(CountResponse { count }) +} diff --git a/cosmwasm/contracts/mock-counter/src/interface.rs b/cosmwasm/contracts/mock-counter/src/interface.rs new file mode 100644 index 0000000000..f2261ffd73 --- /dev/null +++ b/cosmwasm/contracts/mock-counter/src/interface.rs @@ -0,0 +1,57 @@ +use cosmwasm_std::{Addr, StdError, StdResult}; +use cw_multi_test::{App, ContractWrapper, Executor}; + +use crate::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; + +pub struct CounterContract(Addr); + +impl CounterContract { + pub fn addr(&self) -> &Addr { + &self.0 + } + + pub fn store_code(app: &mut App) -> u64 { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + app.store_code(Box::new(contract)) + } + + #[track_caller] + pub fn instantiate(app: &mut App, code_id: u64, sender: &str, label: &str) -> StdResult { + let msg = InstantiateMsg {}; + let addr = app + .instantiate_contract(code_id, Addr::unchecked(sender), &msg, &[], label, None) + .unwrap(); + Ok(CounterContract(addr)) + } + + #[track_caller] + pub fn increment(&self, app: &mut App, sender: &str) -> StdResult<()> { + let msg = ExecuteMsg::Increment {}; + let _ = app + .execute_contract(Addr::unchecked(sender), self.0.clone(), &msg, &[]) + .map_err(|err| StdError::generic_err(err.to_string()))?; + + Ok(()) + } + + #[track_caller] + pub fn reset(&self, app: &mut App, sender: &str) -> StdResult<()> { + let msg = ExecuteMsg::Reset {}; + let _ = app + .execute_contract(Addr::unchecked(sender), self.0.clone(), &msg, &[]) + .map_err(|err| StdError::generic_err(err.to_string()))?; + + Ok(()) + } + + #[track_caller] + pub fn query_count(&self, app: &App) -> StdResult { + let msg = QueryMsg::GetCount {}; + let result: CountResponse = app.wrap().query_wasm_smart(self.0.clone(), &msg)?; + Ok(result.count) + } +} diff --git a/cosmwasm/contracts/mock-counter/src/lib.rs b/cosmwasm/contracts/mock-counter/src/lib.rs new file mode 100644 index 0000000000..8ed39e74b9 --- /dev/null +++ b/cosmwasm/contracts/mock-counter/src/lib.rs @@ -0,0 +1,9 @@ +pub use crate::msg::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg}; + +pub mod contract; +pub mod msg; +#[cfg(test)] +mod tests; + +#[cfg(any(test, feature = "interface"))] +pub mod interface; diff --git a/cosmwasm/contracts/mock-counter/src/msg.rs b/cosmwasm/contracts/mock-counter/src/msg.rs new file mode 100644 index 0000000000..7ec6e71a1b --- /dev/null +++ b/cosmwasm/contracts/mock-counter/src/msg.rs @@ -0,0 +1,28 @@ +use cosmwasm_schema::cw_serde; +use cw20::Cw20ReceiveMsg; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub struct CountResponse { + pub count: u32, +} + +#[cw_serde] +pub enum ExecuteMsg { + Increment {}, + Reset {}, + Receive(Cw20ReceiveMsg), +} + +#[cw_serde] +pub enum ReceiveMsg { + Increment {}, + Reset {}, +} + +#[cw_serde] +pub enum QueryMsg { + GetCount {}, +} diff --git a/cosmwasm/contracts/mock-counter/src/tests.rs b/cosmwasm/contracts/mock-counter/src/tests.rs new file mode 100644 index 0000000000..ddfdb11637 --- /dev/null +++ b/cosmwasm/contracts/mock-counter/src/tests.rs @@ -0,0 +1,95 @@ +use cw_multi_test::App; + +use crate::interface::CounterContract; + +#[test] +fn proper_initialization() { + let mut app = App::default(); + let code_id = CounterContract::store_code(&mut app); + + let contract = CounterContract::instantiate(&mut app, code_id, "owner", "Counter 1").unwrap(); + + let count = contract.query_count(&app).unwrap(); + assert_eq!(0, count); +} + +#[test] +fn increment_works() { + let mut app = App::default(); + let code_id = CounterContract::store_code(&mut app); + + // Instantiate with count 0 + let contract = CounterContract::instantiate(&mut app, code_id, "owner", "Counter 1").unwrap(); + + // Anyone can increment + contract.increment(&mut app, "anyone").unwrap(); + let count = contract.query_count(&app).unwrap(); + assert_eq!(1, count); + + // Multiple increments + contract.increment(&mut app, "anyone").unwrap(); + contract.increment(&mut app, "anyone").unwrap(); + let count = contract.query_count(&app).unwrap(); + assert_eq!(3, count); +} + +#[test] +fn reset_works() { + let mut app = App::default(); + let code_id = CounterContract::store_code(&mut app); + + // Instantiate with count 5 + let contract = CounterContract::instantiate(&mut app, code_id, "owner", "Counter 1").unwrap(); + + // Increment the counter + contract.increment(&mut app, "anyone").unwrap(); + contract.increment(&mut app, "anyone").unwrap(); + + let count = contract.query_count(&app).unwrap(); + assert_eq!(2, count); + + // Can reset to same value + contract.reset(&mut app, "anyone").unwrap(); + let count = contract.query_count(&app).unwrap(); + assert_eq!(0, count); + + // Can reset to lower value + contract.reset(&mut app, "anyone").unwrap(); + let count = contract.query_count(&app).unwrap(); + assert_eq!(0, count); +} + +#[test] +fn multiple_counters() { + let mut app = App::default(); + let code_id = CounterContract::store_code(&mut app); + + // Create two counters + let contract1 = CounterContract::instantiate(&mut app, code_id, "owner", "Counter 1").unwrap(); + + let contract2 = CounterContract::instantiate(&mut app, code_id, "owner", "Counter 2").unwrap(); + + // increment contract1 5 times + for _ in 0..5 { + contract1.increment(&mut app, "anyone").unwrap(); + } + + // increment contract2 10 times + for _ in 0..10 { + contract2.increment(&mut app, "anyone").unwrap(); + } + + // Check initial values + assert_eq!(5, contract1.query_count(&app).unwrap()); + assert_eq!(10, contract2.query_count(&app).unwrap()); + + // Increment first counter + contract1.increment(&mut app, "anyone").unwrap(); + assert_eq!(6, contract1.query_count(&app).unwrap()); + assert_eq!(10, contract2.query_count(&app).unwrap()); + + // Reset second counter + contract2.reset(&mut app, "anyone").unwrap(); + assert_eq!(6, contract1.query_count(&app).unwrap()); + assert_eq!(0, contract2.query_count(&app).unwrap()); +}