diff --git a/Cargo.lock b/Cargo.lock index 2f471560..36f954a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "base16ct" @@ -43,6 +43,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "block-buffer" version = "0.9.0" @@ -75,9 +81,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cfg-if" @@ -93,12 +99,11 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cosmwasm-crypto" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9934c79e58d9676edfd592557dee765d2a6ef54c09d5aa2edb06156b00148966" +checksum = "0f862b355f7e47711e0acfe6af92cb3fd8fd5936b66a9eaa338b51edabd1e77d" dependencies = [ "digest 0.10.7", - "ecdsa", "ed25519-zebra", "k256", "rand_core 0.6.4", @@ -107,18 +112,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5e72e330bd3bdab11c52b5ecbdeb6a8697a004c57964caeb5d876f0b088b3c" +checksum = "cd85de6467cd1073688c86b39833679ae6db18cf4771471edd9809f15f1679f1" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e3a2136e2a60e8b6582f5dffca5d1a683ed77bf38537d330bc1dfccd69010" +checksum = "5b4cd28147a66eba73720b47636a58097a979ad8c8bfdb4ed437ebcbfe362576" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -129,9 +134,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d803bea6bd9ed61bd1ee0b4a2eb09ee20dbb539cc6e0b8795614d20952ebb1" +checksum = "9acd45c63d41bc9b16bc6dc7f6bd604a8c2ad29ce96c8f3c96d7fc8ef384392e" dependencies = [ "proc-macro2", "quote", @@ -140,12 +145,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8666e572a3a2519010dde88c04d16e9339ae751b56b2bb35081fe3f7d6be74" +checksum = "2685c2182624b2e9e17f7596192de49a3f86b7a0c9a5f6b25c1df5e24592e836" dependencies = [ "base64", - "bech32", + "bech32 0.9.1", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -223,6 +228,14 @@ dependencies = [ "cosmwasm-std", ] +[[package]] +name = "cw-address-like" +version = "1.0.4" +source = "git+https://github.com/public-awesome/cw-plus-plus.git?rev=28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523#28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523" +dependencies = [ + "cosmwasm-std", +] + [[package]] name = "cw-cii" version = "0.1.0" @@ -326,17 +339,17 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fff029689ae89127cf6d7655809a68d712f3edbdb9686c70b018ba438b26ca" +checksum = "cc392a5cb7e778e3f90adbf7faa43c4db7f35b6623224b08886d796718edb875" dependencies = [ "anyhow", - "bech32", + "bech32 0.9.1", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "derivative", - "itertools 0.12.1", + "itertools", "prost", "schemars", "serde", @@ -352,8 +365,22 @@ checksum = "093dfb4520c48b5848274dd88ea99e280a04bc08729603341c7fb0d758c74321" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-address-like", - "cw-ownable-derive", + "cw-address-like 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-ownable-derive 0.5.1", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "thiserror", +] + +[[package]] +name = "cw-ownable" +version = "0.6.0" +source = "git+https://github.com/public-awesome/cw-plus-plus.git?rev=28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523#28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-address-like 1.0.4 (git+https://github.com/public-awesome/cw-plus-plus.git?rev=28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523)", + "cw-ownable-derive 0.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "thiserror", @@ -370,10 +397,20 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cw-ownable-derive" +version = "0.6.0" +source = "git+https://github.com/public-awesome/cw-plus-plus.git?rev=28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523#28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cw-paginate-storage" -version = "2.4.0" -source = "git+https://github.com/DA0-DA0/dao-contracts.git#43eccded799ea63ea19a81ed64054848e689ff73" +version = "2.5.0" +source = "git+https://github.com/DA0-DA0/dao-contracts.git#2cac217cdea0e47dc32e9c9363175980774585d6" dependencies = [ "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -497,6 +534,30 @@ dependencies = [ "serde", ] +[[package]] +name = "cw721" +version = "0.16.0" +source = "git+https://github.com/CosmWasm/cw-nfts?tag=v0.16.0#2cad1d3e15e0a34d466a0b51e02c58b82ebe5ecd" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "schemars", + "serde", +] + +[[package]] +name = "cw721" +version = "0.17.0" +source = "git+https://github.com/public-awesome/cw-nfts?branch=release/v0.17.0#c1ece555dded6cbcebba1d417ed2a18d47ca3c8a" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.3", + "schemars", + "serde", +] + [[package]] name = "cw721" version = "0.18.0" @@ -513,7 +574,7 @@ dependencies = [ [[package]] name = "cw721" version = "0.18.0" -source = "git+https://github.com/CosmWasm/cw-nfts?branch=main#e63a7bbb620e6ea39224bb5580967477066cb7ae" +source = "git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.0#177a993dfb5a1a3164be1baf274f43b1ca53da53" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -522,6 +583,44 @@ dependencies = [ "serde", ] +[[package]] +name = "cw721" +version = "0.19.0" +source = "git+https://github.com/CosmWasm/cw-nfts?branch=main#8009afbc1f147bcf0005a794286e1ed2018640e6" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable 0.6.0", + "cw-paginate-storage", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw721 0.16.0 (git+https://github.com/CosmWasm/cw-nfts?tag=v0.16.0)", + "schemars", + "serde", + "thiserror", + "url", +] + +[[package]] +name = "cw721" +version = "0.19.0" +source = "git+https://github.com/public-awesome/cw-nfts?branch=minor_changes#40c9b9e8037d94a3042e6f6a89ef5b5aa23c7267" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable 0.6.0", + "cw-paginate-storage", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw721 0.16.0 (git+https://github.com/CosmWasm/cw-nfts?tag=v0.16.0)", + "schemars", + "serde", + "thiserror", + "url", +] + [[package]] name = "cw721-base" version = "0.16.0" @@ -533,7 +632,25 @@ dependencies = [ "cw-storage-plus 0.16.0", "cw-utils 0.16.0", "cw2 0.16.0", - "cw721 0.16.0", + "cw721 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw721-base" +version = "0.17.0" +source = "git+https://github.com/public-awesome/cw-nfts?branch=release/v0.17.0#c1ece555dded6cbcebba1d417ed2a18d47ca3c8a" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable 0.5.1", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw721 0.17.0", + "cw721-base 0.16.0", "schemars", "serde", "thiserror", @@ -547,7 +664,7 @@ checksum = "da518d9f68bfda7d972cbaca2e8fcf04651d0edc3de72b04ae2bcd9289c81614" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-ownable", + "cw-ownable 0.5.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -561,21 +678,47 @@ dependencies = [ [[package]] name = "cw721-base" version = "0.18.0" -source = "git+https://github.com/CosmWasm/cw-nfts?branch=main#e63a7bbb620e6ea39224bb5580967477066cb7ae" +source = "git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.0#177a993dfb5a1a3164be1baf274f43b1ca53da53" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-ownable", + "cw-ownable 0.5.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "cw721 0.18.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", + "cw721 0.18.0 (git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.0)", "cw721-base 0.16.0", "schemars", "serde", "thiserror", ] +[[package]] +name = "cw721-base" +version = "0.19.0" +source = "git+https://github.com/public-awesome/cw-nfts?branch=minor_changes#40c9b9e8037d94a3042e6f6a89ef5b5aa23c7267" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable 0.6.0", + "cw2 1.1.2", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", + "serde", +] + +[[package]] +name = "cw721-metadata-onchain" +version = "0.19.0" +source = "git+https://github.com/public-awesome/cw-nfts?branch=minor_changes#40c9b9e8037d94a3042e6f6a89ef5b5aa23c7267" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 1.1.2", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", + "schemars", + "serde", +] + [[package]] name = "cw721-tester" version = "0.1.0" @@ -584,15 +727,16 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.2.0", "cw2 1.1.2", - "cw721-base 0.18.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", + "cw721-metadata-onchain", "thiserror", ] [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "zeroize", @@ -632,9 +776,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" @@ -667,9 +811,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" @@ -728,9 +872,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -777,23 +921,28 @@ name = "ics721" version = "0.1.0" dependencies = [ "anyhow", - "bech32", + "bech32 0.11.0", "cosmwasm-schema", "cosmwasm-std", "cw-cii", "cw-ics721-incoming-proxy-base", "cw-ics721-outgoing-proxy-rate-limit", "cw-multi-test", - "cw-ownable", + "cw-ownable 0.5.1", "cw-paginate-storage", "cw-pause-once", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "cw721 0.16.0", - "cw721 0.18.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", + "cw721 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw721 0.17.0", + "cw721 0.18.0 (git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.0)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", "cw721-base 0.16.0", - "cw721-base 0.18.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", + "cw721-base 0.17.0", + "cw721-base 0.18.0 (git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.0)", + "cw721-base 0.19.0", + "cw721-metadata-onchain", "ics721-types 0.1.0", "serde", "sha2 0.10.8", @@ -819,7 +968,7 @@ dependencies = [ "cosmwasm-storage", "cw-storage-plus 1.2.0", "cw2 1.1.2", - "cw721 0.18.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", "ics721", "ics721-types 0.1.0", "thiserror", @@ -832,7 +981,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", - "cw721 0.18.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", "serde", "thiserror", ] @@ -845,7 +994,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", - "cw721 0.18.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", + "cw721 0.19.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", "serde", "thiserror", ] @@ -860,15 +1009,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.12.1" @@ -880,15 +1020,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "k256" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if", "ecdsa", @@ -900,9 +1040,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" @@ -912,9 +1058,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "percent-encoding" @@ -934,18 +1080,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", "prost-derive", @@ -953,22 +1099,22 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.74", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1000,15 +1146,15 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schemars" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -1018,14 +1164,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.74", ] [[package]] @@ -1044,15 +1190,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" dependencies = [ "serde_derive", ] @@ -1068,33 +1214,34 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.206" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.74", ] [[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.74", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "a11b3b6ce5cfd25b9759a24c3ed4bf24e23893866863547de4655518c951bcd4" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1104,7 +1251,7 @@ name = "sg-ics721" version = "0.1.0" dependencies = [ "anyhow", - "bech32", + "bech32 0.11.0", "cosmwasm-schema", "cosmwasm-std", "cw-cii", @@ -1114,8 +1261,9 @@ dependencies = [ "cw-pause-once", "cw-storage-plus 1.2.0", "cw2 1.1.2", - "cw721 0.18.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", - "cw721-base 0.18.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", + "cw721 0.18.0 (git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.0)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", + "cw721-base 0.18.0 (git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.0)", "ics721", "ics721-types 0.1.0", "sg-std", @@ -1141,13 +1289,13 @@ dependencies = [ [[package]] name = "sg721" -version = "3.5.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f59f52a646afc7e20dd55a873df667c6c995deb7495c6cf9b0f3d8f340dd227" +checksum = "05327a3a8a44de4c4c97e02fd98dd87917c051b76b7a64f8c47865b5a80cd47c" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-ownable", + "cw-ownable 0.5.1", "cw-utils 1.0.3", "cw721-base 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde", @@ -1156,13 +1304,13 @@ dependencies = [ [[package]] name = "sg721-base" -version = "3.5.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e903e3e9bd2f8641d03a7ef3e9e40a7188f655d9e1cdfd220ba7c01e8d0b35b" +checksum = "f97deda0edefd15494052003a1f79fd9b06c26d8b7448a3174deb7529a2251f0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-ownable", + "cw-ownable 0.5.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -1227,9 +1375,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -1244,9 +1392,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" dependencies = [ "proc-macro2", "quote", @@ -1255,29 +1403,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.74", ] [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -1308,18 +1456,18 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -1328,9 +1476,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -1340,9 +1488,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zip-optional" diff --git a/Cargo.toml b/Cargo.toml index b4b09abd..15be9969 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [workspace.dependencies] # common libs anyhow = "^1.0" -bech32 = "^0.9" +bech32 = "^0.11" cosmwasm-std = "^1.5" cosmwasm-schema = "^1.5" cosmwasm-storage = "^1.5" @@ -16,10 +16,15 @@ cw-ownable = "^0.5" cw-paginate-storage = { version = "^2.4", git = "https://github.com/DA0-DA0/dao-contracts.git" } cw-storage-plus = "1.1" cw2 = "1.1" -cw721 = { git = "https://github.com/CosmWasm/cw-nfts", branch = "main"} # TODO switch to version 0.18.1/0.19.0, once released +cw721 = { git = "https://github.com/public-awesome/cw-nfts", branch = "minor_changes"} # TODO switch to version 0.18.1/0.19.0, once released cw721-016 = { version = "0.16.0", package = "cw721" } -cw721-base = { git = "https://github.com/CosmWasm/cw-nfts", branch = "main"} # TODO switch to version 0.18.1/0.19.0, once released +cw721-017 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.17.0", package = "cw721"} # TODO switch to version 0.18.1/0.19.0, once released +cw721-018 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.18.0", package = "cw721"} # TODO switch to version 0.18.1/0.19.0, once released +cw721-metadata-onchain = { git = "https://github.com/public-awesome/cw-nfts", branch = "minor_changes", package = "cw721-metadata-onchain"} # TODO switch to version 0.18.1/0.19.0, once released +cw721-base = { git = "https://github.com/public-awesome/cw-nfts", branch = "minor_changes"} # TODO switch to version 0.18.1/0.19.0, once released cw721-base-016 = { version = "0.16.0", package = "cw721-base" } +cw721-base-017 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.17.0", package = "cw721-base"} # TODO switch to version 0.18.1/0.19.0, once released +cw721-base-018 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.18.0", package = "cw721-base"} # TODO switch to version 0.18.1/0.19.0, once released cw-ics721-incoming-proxy = { git = "https://github.com/arkprotocol/cw-ics721-proxy.git", tag = "v0.1.0" } cw-ics721-incoming-proxy-base = { git = "https://github.com/arkprotocol/cw-ics721-proxy.git", tag = "v0.1.0" } cw-ics721-outgoing-proxy-rate-limit = { git = "https://github.com/arkprotocol/cw-ics721-proxy.git", tag = "v0.1.0" } diff --git a/contracts/cw721-tester/Cargo.toml b/contracts/cw721-tester/Cargo.toml index d162c7c1..3f32e850 100644 --- a/contracts/cw721-tester/Cargo.toml +++ b/contracts/cw721-tester/Cargo.toml @@ -17,4 +17,5 @@ cosmwasm-schema = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } thiserror = { workspace = true } -cw721-base = { workspace = true, features = [ "library" ] } +cw721-metadata-onchain = { workspace = true, features = [ "library" ] } +cw721 = { workspace = true} diff --git a/contracts/cw721-tester/src/lib.rs b/contracts/cw721-tester/src/lib.rs index d7b74e5a..c4274d75 100644 --- a/contracts/cw721-tester/src/lib.rs +++ b/contracts/cw721-tester/src/lib.rs @@ -1,19 +1,38 @@ use cosmwasm_schema::cw_serde; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response}; use cw2::set_contract_version; -use cw721_base::{msg, ContractError, Extension}; +use cw721::{ + error::Cw721ContractError, + msg::Cw721InstantiateMsg, + traits::{Cw721Execute, Cw721Query}, + DefaultOptionalCollectionExtensionMsg, +}; +use cw721_metadata_onchain::Cw721MetadataContract; use cw_storage_plus::Item; -pub type ExecuteMsg = msg::ExecuteMsg; -pub type QueryMsg = msg::QueryMsg; +pub type ExecuteMsg = cw721_metadata_onchain::msg::ExecuteMsg; +pub type QueryMsg = cw721_metadata_onchain::msg::QueryMsg; #[cw_serde] pub struct InstantiateMsg { + /// Name of the NFT contract pub name: String, + /// Symbol of the NFT contract pub symbol: String, - pub minter: String, + /// Optional extension of the collection metadata + pub collection_info_extension: DefaultOptionalCollectionExtensionMsg, + + /// The minter is the only one who can create new NFTs. + /// This is designed for a base NFT that is controlled by an external program + /// or contract. You will likely replace this with custom logic in custom NFTs + pub minter: Option, + + /// Sets the creator of collection. The creator is the only one eligible to update `CollectionInfo`. + pub creator: Option, + + pub withdraw_address: Option, /// An address which will be unable receive NFT on `TransferNft` message /// If `TransferNft` message attempts sending to banned recipient /// it will fail with an out-of-gas error. @@ -31,17 +50,21 @@ pub fn instantiate( env: Env, info: MessageInfo, msg: InstantiateMsg, -) -> Result { - let response = cw721_base::entry::instantiate( +) -> Result { + let response = Cw721MetadataContract::default().instantiate_with_version( deps.branch(), - env, - info, - msg::InstantiateMsg { + &env, + &info, + Cw721InstantiateMsg { name: msg.name, symbol: msg.symbol, - minter: Some(msg.minter), + minter: msg.minter, withdraw_address: None, + collection_info_extension: msg.collection_info_extension, + creator: msg.creator, }, + CONTRACT_NAME, + CONTRACT_VERSION, )?; BANNED_RECIPIENT.save(deps.storage, &msg.banned_recipient)?; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -55,7 +78,7 @@ pub fn execute( env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { +) -> Result { match msg.clone() { ExecuteMsg::TransferNft { recipient, .. } => { if recipient == BANNED_RECIPIENT.load(deps.storage)? { @@ -64,13 +87,13 @@ pub fn execute( panic!("gotem") // loop {} } - cw721_base::entry::execute(deps, env, info, msg) + Cw721MetadataContract::default().execute(deps, &env, &info, msg) } - _ => cw721_base::entry::execute(deps, env, info, msg), + _ => Cw721MetadataContract::default().execute(deps, &env, &info, msg), } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - cw721_base::entry::query(deps, env, msg) +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { + Cw721MetadataContract::default().query(deps, &env, msg) } diff --git a/contracts/ics721-base-tester/src/contract.rs b/contracts/ics721-base-tester/src/contract.rs index 15e3a988..8c9fecee 100644 --- a/contracts/ics721-base-tester/src/contract.rs +++ b/contracts/ics721-base-tester/src/contract.rs @@ -1,10 +1,11 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Binary, Deps, DepsMut, Env, IbcMsg, IbcTimeout, MessageInfo, Response, + to_json_binary, Binary, Deps, DepsMut, Empty, Env, IbcMsg, IbcTimeout, MessageInfo, Response, StdResult, WasmMsg, }; use cw2::set_contract_version; +use cw721::{DefaultOptionalCollectionExtensionMsg, DefaultOptionalNftExtensionMsg}; use ics721_types::ibc_types::{IbcOutgoingMsg, NonFungibleTokenPacketData}; use crate::{ @@ -63,7 +64,8 @@ pub fn execute( } mod receive_callbacks { - use cosmwasm_std::{ensure_eq, from_json, DepsMut, MessageInfo, Response}; + use cosmwasm_std::{ensure_eq, from_json, DepsMut, Empty, MessageInfo, Response}; + use cw721::{DefaultOptionalCollectionExtension, DefaultOptionalNftExtension}; use ics721_types::{ ibc_types::NonFungibleTokenPacketData, types::{Ics721AckCallbackMsg, Ics721ReceiveCallbackMsg, Ics721Status}, @@ -77,7 +79,7 @@ mod receive_callbacks { pub(crate) fn handle_receive_cw_callback( deps: DepsMut, - _msg: cw721::Cw721ReceiveMsg, + _msg: cw721::receiver::Cw721ReceiveMsg, ) -> Result { // We got the callback, so its working CW721_RECEIVE.save(deps.storage, &"success".to_string())?; @@ -128,11 +130,15 @@ mod receive_callbacks { NFT_CONTRACT.save(deps.storage, &deps.api.addr_validate(&nft_contract)?)?; - let owner: Option = deps + let owner: Option = deps .querier - .query_wasm_smart::( + .query_wasm_smart::( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721::msg::Cw721QueryMsg::< + DefaultOptionalNftExtension, + DefaultOptionalCollectionExtension, + Empty, + >::OwnerOf { token_id: packet.token_ids[0].clone().into(), include_expired: None, }, @@ -173,9 +179,13 @@ mod receive_callbacks { let owner = deps .querier - .query_wasm_smart::( + .query_wasm_smart::( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721::msg::Cw721QueryMsg::< + DefaultOptionalNftExtension, + DefaultOptionalCollectionExtension, + Empty, + >::OwnerOf { token_id: packet.token_ids[0].clone().into(), include_expired: None, }, @@ -217,7 +227,11 @@ fn execute_send_nft( // Send send msg to cw721, send it to ics721 with the correct msg. let msg = WasmMsg::Execute { contract_addr: cw721, - msg: to_json_binary(&cw721::Cw721ExecuteMsg::SendNft { + msg: to_json_binary(&cw721::msg::Cw721ExecuteMsg::< + DefaultOptionalNftExtensionMsg, + DefaultOptionalCollectionExtensionMsg, + Empty, + >::SendNft { contract: ics721, token_id, msg: to_json_binary(&IbcOutgoingMsg { diff --git a/contracts/ics721-base-tester/src/msg.rs b/contracts/ics721-base-tester/src/msg.rs index 75a0bd3a..c86291eb 100644 --- a/contracts/ics721-base-tester/src/msg.rs +++ b/contracts/ics721-base-tester/src/msg.rs @@ -42,7 +42,7 @@ pub struct InstantiateMsg { #[allow(clippy::large_enum_variant)] // `data` field is a bit large // for clippy's taste. pub enum ExecuteMsg { - ReceiveNft(cw721::Cw721ReceiveMsg), + ReceiveNft(cw721::receiver::Cw721ReceiveMsg), Ics721ReceiveCallback(ics721_types::types::Ics721ReceiveCallbackMsg), Ics721AckCallback(ics721_types::types::Ics721AckCallbackMsg), SendNft { diff --git a/contracts/ics721-base-tester/src/state.rs b/contracts/ics721-base-tester/src/state.rs index 4ad7df15..6eb17fc4 100644 --- a/contracts/ics721-base-tester/src/state.rs +++ b/contracts/ics721-base-tester/src/state.rs @@ -7,7 +7,7 @@ pub const ACK_MODE: Item = Item::new("ack_mode"); pub const LAST_ACK: Item = Item::new("ack_mode"); pub const ICS721: Item = Item::new("ics721"); -pub const SENT_CALLBACK: Item> = Item::new("sent"); -pub const RECEIVED_CALLBACK: Item> = Item::new("received"); +pub const SENT_CALLBACK: Item> = Item::new("sent"); +pub const RECEIVED_CALLBACK: Item> = Item::new("received"); pub const NFT_CONTRACT: Item = Item::new("nft_contract"); pub const CW721_RECEIVE: Item = Item::new("cw721_received"); diff --git a/contracts/sg-ics721/Cargo.toml b/contracts/sg-ics721/Cargo.toml index 63ac889c..409bf537 100644 --- a/contracts/sg-ics721/Cargo.toml +++ b/contracts/sg-ics721/Cargo.toml @@ -16,6 +16,7 @@ library = [] cosmwasm-std = { workspace = true, features = ["ibc3"] } cosmwasm-schema = { workspace = true } cw2 = { workspace = true } +cw721 = { workspace = true } ics721 = { workspace = true } ics721-types = { workspace = true } sg-std = { workspace = true} @@ -31,7 +32,8 @@ cw-multi-test = { workspace = true } cw-pause-once = { workspace = true } cw-storage-plus = { workspace = true } cw721 = { workspace = true} +cw721-018 = { workspace = true} cw-ics721-incoming-proxy-base = { workspace = true } cw-ics721-outgoing-proxy-rate-limit = { workspace = true } -cw721-base = { workspace = true} +cw721-base-018 = { workspace = true} sha2 = { workspace = true } diff --git a/contracts/sg-ics721/src/execute.rs b/contracts/sg-ics721/src/execute.rs index 2bcfba51..ff4f90c0 100644 --- a/contracts/sg-ics721/src/execute.rs +++ b/contracts/sg-ics721/src/execute.rs @@ -1,13 +1,17 @@ -use cosmwasm_std::{from_json, to_json_binary, Addr, Binary, Deps, DepsMut, Env, StdResult}; +use cosmwasm_std::{ + from_json, to_json_binary, Addr, Binary, ContractInfoResponse, Deps, DepsMut, Env, StdResult, +}; +use cw721::{CollectionExtension, RoyaltyInfo}; use ics721::{execute::Ics721Execute, state::CollectionData, utils::get_collection_data}; use ics721_types::token_types::Class; +use sg721::RoyaltyInfoResponse; use sg721_base::msg::{CollectionInfoResponse, QueryMsg}; -use crate::state::{SgCollectionData, SgIcs721Contract, STARGAZE_ICON_PLACEHOLDER}; +use crate::state::{SgIcs721Contract, STARGAZE_ICON_PLACEHOLDER}; impl Ics721Execute for SgIcs721Contract { - type ClassData = SgCollectionData; + type ClassData = CollectionData; /// sg-ics721 sends custom SgCollectionData, basically it extends ics721-base::state::CollectionData with additional collection_info. fn get_class_data(&self, deps: &DepsMut, sender: &Addr) -> StdResult> { @@ -16,28 +20,52 @@ impl Ics721Execute for SgIcs721Contract { contract_info, name, symbol, + extension: _, // ignore extension coming from standard cw721, since sg721 has its own extension (collection info) num_tokens, } = get_collection_data(deps, sender)?; let collection_info: CollectionInfoResponse = deps .querier .query_wasm_smart(sender, &QueryMsg::CollectionInfo {})?; + let royalty_info = collection_info.royalty_info.map(|r| RoyaltyInfo { + payment_address: Addr::unchecked(r.payment_address), + share: r.share, + }); + let extension = Some(CollectionExtension { + description: collection_info.description, + image: collection_info.image, + external_link: collection_info.external_link, + explicit_content: collection_info.explicit_content, + start_trading_time: collection_info.start_trading_time, + royalty_info, + }); - Ok(Some(SgCollectionData { + Ok(Some(CollectionData { owner, contract_info, name, symbol, num_tokens, - collection_info: Some(collection_info), + extension, })) } - fn init_msg(&self, deps: Deps, env: &Env, class: &Class) -> StdResult { + fn init_msg( + &self, + deps: Deps, + env: &Env, + class: &Class, + cw721_admin: Option, + ) -> StdResult { // ics721 creator is used, in case no source owner in class data is provided (e.g. due to nft-transfer module). - let ics721_contract_info = deps + let ContractInfoResponse { creator, admin, .. } = deps .querier .query_wasm_contract_info(env.contract.address.to_string())?; // use by default ClassId, in case there's no class data with name and symbol + let cw721_admin_or_ics721_admin_or_ics721_creator = cw721_admin + .clone() + .or_else(|| admin.clone()) + .or_else(|| Some(creator.clone())) + .unwrap(); let mut instantiate_msg = sg721::InstantiateMsg { name: class.id.clone().into(), symbol: class.id.clone().into(), @@ -46,10 +74,10 @@ impl Ics721Execute for SgIcs721Contract { // source owner could be: 1. regular wallet, 2. contract, or 3. multisig // bech32 calculation for 2. and 3. leads to unknown address // therefore, we use ics721 creator as owner - creator: ics721_contract_info.creator, + creator: cw721_admin_or_ics721_admin_or_ics721_creator.clone(), description: "".to_string(), - // use Stargaze icon as placeholder - image: STARGAZE_ICON_PLACEHOLDER.to_string(), + // remaining props is set below, in case there's collection data + image: STARGAZE_ICON_PLACEHOLDER.to_string(), // use Stargaze icon as placeholder external_link: None, explicit_content: None, start_trading_time: None, @@ -65,6 +93,22 @@ impl Ics721Execute for SgIcs721Contract { if let Some(collection_data) = collection_data { instantiate_msg.name = collection_data.name; instantiate_msg.symbol = collection_data.symbol; + if let Some(collection_info_extension_msg) = + collection_data.extension.map(|ext| sg721::CollectionInfo { + creator: cw721_admin_or_ics721_admin_or_ics721_creator.clone(), + description: ext.description, + image: ext.image, + external_link: ext.external_link, + explicit_content: ext.explicit_content, + start_trading_time: ext.start_trading_time, + royalty_info: ext.royalty_info.map(|r| RoyaltyInfoResponse { + payment_address: cw721_admin_or_ics721_admin_or_ics721_creator, // r.payment_address cant be used, since it is from another chain + share: r.share, + }), + }) + { + instantiate_msg.collection_info = collection_info_extension_msg; + } } to_json_binary(&instantiate_msg) diff --git a/contracts/sg-ics721/src/state.rs b/contracts/sg-ics721/src/state.rs index e2f7c731..98c4ca6a 100644 --- a/contracts/sg-ics721/src/state.rs +++ b/contracts/sg-ics721/src/state.rs @@ -1,23 +1,5 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::ContractInfoResponse; -use sg721_base::msg::CollectionInfoResponse; - pub const STARGAZE_ICON_PLACEHOLDER: &str = "ipfs://bafkreie5vwrm5zts4wiq6ebtopmztgl5qzyl4uszyllgwpaizyc5w2uycm"; -/// Collection data provided by the (source) cw721 contract. This is pass as optional class data during interchain transfer to target chain. -/// ICS721 on target chain is free to use this data or not. Lik in case of `sg721-base` it uses owner for defining creator in collection info. -#[cw_serde] -pub struct SgCollectionData { - // CW721 specific props, copied from ics721::state::CollectionData - pub owner: Option, - pub contract_info: Option, - pub name: String, - pub symbol: String, - pub num_tokens: Option, - /// SG721 specific collection info - pub collection_info: Option, -} - #[derive(Default)] pub struct SgIcs721Contract {} diff --git a/contracts/sg-ics721/src/testing/integration_tests.rs b/contracts/sg-ics721/src/testing/integration_tests.rs index 49cf3b00..73e18800 100644 --- a/contracts/sg-ics721/src/testing/integration_tests.rs +++ b/contracts/sg-ics721/src/testing/integration_tests.rs @@ -1,13 +1,15 @@ use anyhow::Result; -use bech32::{decode, encode, FromBase32, ToBase32, Variant}; +use bech32::{decode, encode, Hrp}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - from_json, instantiate2_address, to_json_binary, Addr, Api, Binary, CanonicalAddr, Deps, - DepsMut, Empty, Env, GovMsg, IbcTimeout, IbcTimeoutBlock, MemoryStorage, MessageInfo, - RecoverPubkeyError, Reply, Response, StdError, StdResult, Storage, VerificationError, WasmMsg, + from_json, instantiate2_address, to_json_binary, Addr, Api, Binary, CanonicalAddr, Decimal, + Deps, DepsMut, Empty, Env, GovMsg, IbcTimeout, IbcTimeoutBlock, MemoryStorage, MessageInfo, + RecoverPubkeyError, Reply, Response, StdError, StdResult, Storage, Timestamp, + VerificationError, WasmMsg, }; use cw2::set_contract_version; -use cw721_base::msg::QueryMsg as Cw721QueryMsg; +use cw721::{CollectionExtension, RoyaltyInfo}; +use cw721_base_018::msg::QueryMsg as Cw721QueryMsg; use cw_cii::{Admin, ContractInstantiateInfo}; use cw_multi_test::{ AddressGenerator, App, AppBuilder, BankKeeper, Contract, ContractWrapper, DistributionKeeper, @@ -26,14 +28,11 @@ use ics721_types::{ ibc_types::{IbcOutgoingMsg, IbcOutgoingProxyMsg}, token_types::{Class, ClassId, Token, TokenId}, }; -use sg721::InstantiateMsg as Sg721InstantiateMsg; +use sg721::{InstantiateMsg as Sg721InstantiateMsg, RoyaltyInfoResponse}; use sg721_base::msg::{CollectionInfoResponse, QueryMsg as Sg721QueryMsg}; use sha2::{digest::Update, Digest, Sha256}; -use crate::{ - state::{SgCollectionData, STARGAZE_ICON_PLACEHOLDER}, - ContractError, SgIcs721Contract, -}; +use crate::{state::STARGAZE_ICON_PLACEHOLDER, ContractError, SgIcs721Contract}; const ICS721_CREATOR: &str = "ics721-creator"; const CONTRACT_NAME: &str = "crates.io:sg-ics721"; @@ -161,12 +160,14 @@ impl MockAddressGenerator { } } pub struct MockApiBech32 { - prefix: &'static str, + prefix: Hrp, } impl MockApiBech32 { pub fn new(prefix: &'static str) -> Self { - Self { prefix } + Self { + prefix: Hrp::parse(prefix).unwrap(), + } } } @@ -184,22 +185,18 @@ impl Api for MockApiBech32 { } fn addr_canonicalize(&self, input: &str) -> StdResult { - if let Ok((prefix, decoded, Variant::Bech32)) = decode(input) { + if let Ok((prefix, decoded)) = decode(input) { if prefix == self.prefix { - if let Ok(bytes) = Vec::::from_base32(&decoded) { - return Ok(bytes.into()); - } + return Ok(decoded.into()); } } Err(StdError::generic_err(format!("Invalid input: {input}"))) } fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { - if let Ok(encoded) = encode( - self.prefix, - canonical.as_slice().to_base32(), - Variant::Bech32, - ) { + let hrp = self.prefix; + let data = canonical.as_slice(); + if let Ok(encoded) = encode::(hrp, data) { Ok(Addr::unchecked(encoded)) } else { Err(StdError::generic_err("Invalid canonical address")) @@ -250,7 +247,7 @@ impl Api for MockApiBech32 { impl MockApiBech32 { pub fn addr_make(&self, input: &str) -> Addr { let digest = Sha256::digest(input).to_vec(); - match encode(self.prefix, digest.to_base32(), Variant::Bech32) { + match encode::(self.prefix, &digest) { Ok(address) => Addr::unchecked(address), Err(reason) => panic!("Generating address failed with reason: {reason}"), } @@ -277,6 +274,7 @@ pub struct PartialCustomCollectionData { struct Test { app: MockApp, + admin_and_pauser: Option, // origin cw721 contract on source chain for interchain transfers to other target chains source_cw721_owner: Addr, source_cw721_id: u64, @@ -288,6 +286,7 @@ struct Test { } impl Test { + /// Test setup with optional pauser and proxy contracts. fn new( outgoing_proxy: bool, incoming_proxy: bool, @@ -353,7 +352,7 @@ impl Test { incoming_proxy, outgoing_proxy, pauser: admin.clone(), - cw721_admin: admin, + cw721_admin: admin.clone(), contract_addr_length: None, }, &[], @@ -374,12 +373,15 @@ impl Test { minter: source_cw721_owner.to_string(), collection_info: sg721::CollectionInfo { creator: source_cw721_owner.to_string(), - description: "".to_string(), + description: "description".to_string(), image: STARGAZE_ICON_PLACEHOLDER.to_string(), - external_link: None, - explicit_content: None, - start_trading_time: None, - royalty_info: None, + explicit_content: Some(false), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + start_trading_time: Some(Timestamp::from_seconds(42)), + royalty_info: Some(RoyaltyInfoResponse { + payment_address: source_cw721_owner.to_string(), + share: Decimal::bps(1000), + }), }, }, &[], @@ -390,6 +392,7 @@ impl Test { Self { app, + admin_and_pauser: admin, source_cw721_owner, source_cw721_id, source_cw721, @@ -518,7 +521,7 @@ impl Test { .execute_contract( self.source_cw721_owner.clone(), self.source_cw721.clone(), - &cw721_base::msg::ExecuteMsg::::Mint { + &cw721_base_018::msg::ExecuteMsg::::Mint { token_id: self.nfts_minted.to_string(), owner: owner.to_string(), token_uri: None, @@ -635,6 +638,21 @@ fn test_do_instantiate_and_mint_weird_data() { contract_info: Default::default(), name: "name".to_string(), symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some( + "https://interchain.arkprotocol.io".to_string(), + ), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: Addr::unchecked( + "payment_address".to_string(), + ), + share: Decimal::one(), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), num_tokens: Some(1), }) .unwrap(), @@ -710,7 +728,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // check name and symbol contains class id for instantiated nft contract - let contract_info: cw721::ContractInfoResponse = test + let contract_info: cw721_018::ContractInfoResponse = test .app .wrap() .query_wasm_smart( @@ -720,7 +738,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); assert_eq!( contract_info, - cw721::ContractInfoResponse { + cw721_018::ContractInfoResponse { name: class_id.to_string(), // name is set to class_id symbol: class_id.to_string() // symbol is set to class_id } @@ -748,12 +766,12 @@ fn test_do_instantiate_and_mint() { ); // Check that token_uri was set properly. - let token_info: cw721::NftInfoResponse = test + let token_info: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -762,12 +780,12 @@ fn test_do_instantiate_and_mint() { token_info.token_uri, Some("https://moonphase.is/image.svg".to_string()) ); - let token_info: cw721::NftInfoResponse = test + let token_info: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -779,7 +797,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_base_018::msg::ExecuteMsg::::TransferNft { recipient: nft_contract.to_string(), token_id: "1".to_string(), }, @@ -788,7 +806,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner: cw721::OwnerOfResponse = test + let owner: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -802,12 +820,12 @@ fn test_do_instantiate_and_mint() { assert_eq!(owner.owner, nft_contract.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner: cw721::OwnerOfResponse = test + let base_owner: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_018::Cw721QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -817,7 +835,13 @@ fn test_do_instantiate_and_mint() { } // test case: instantiate cw721 with ClassData containing owner, name, and symbol { - let mut test = Test::new(false, false, None, None, sg721_base_contract()); + let mut test = Test::new( + false, + false, + None, + Some(COLLECTION_OWNER_SOURCE_CHAIN.to_string()), // admin is used for royalty payment address! + sg721_base_contract(), + ); let collection_contract_source_chain = ClassId::new(test.app.api().addr_make(COLLECTION_CONTRACT_SOURCE_CHAIN)); let class_id = format!( @@ -847,6 +871,21 @@ fn test_do_instantiate_and_mint() { contract_info: Default::default(), name: "ark".to_string(), symbol: "protocol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some( + "https://interchain.arkprotocol.io".to_string(), + ), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: Addr::unchecked( + "payment_address".to_string(), + ), + share: Decimal::one(), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), num_tokens: Some(1), }) .unwrap(), @@ -886,7 +925,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // check name and symbol is using class data for instantiated nft contract - let contract_info: cw721::ContractInfoResponse = test + let contract_info: cw721_018::ContractInfoResponse = test .app .wrap() .query_wasm_smart( @@ -896,7 +935,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); assert_eq!( contract_info, - cw721::ContractInfoResponse { + cw721_018::ContractInfoResponse { name: "ark".to_string(), symbol: "protocol".to_string() } @@ -912,24 +951,26 @@ fn test_do_instantiate_and_mint() { assert_eq!( collection_info, CollectionInfoResponse { - // creator based on owner from collection in soure chain - creator: test.app.api().addr_make(ICS721_CREATOR).to_string(), - description: "".to_string(), - image: STARGAZE_ICON_PLACEHOLDER.to_string(), - external_link: None, - explicit_content: None, - start_trading_time: None, - royalty_info: None, + creator: test.admin_and_pauser.clone().unwrap(), + description: "description".to_string(), + image: "https://ark.pass/image.png".to_string(), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + explicit_content: Some(false), + start_trading_time: Some(Timestamp::from_seconds(42)), + royalty_info: Some(RoyaltyInfoResponse { + payment_address: test.admin_and_pauser.unwrap(), + share: Decimal::one(), + }), } ); // Check that token_uri was set properly. - let token_info: cw721::NftInfoResponse = test + let token_info: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -938,12 +979,12 @@ fn test_do_instantiate_and_mint() { token_info.token_uri, Some("https://moonphase.is/image.svg".to_string()) ); - let token_info: cw721::NftInfoResponse = test + let token_info: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -955,7 +996,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), // new recipient - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_base_018::msg::ExecuteMsg::::TransferNft { recipient: nft_contract.to_string(), token_id: "1".to_string(), }, @@ -964,7 +1005,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner: cw721::OwnerOfResponse = test + let owner: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -978,12 +1019,12 @@ fn test_do_instantiate_and_mint() { assert_eq!(owner.owner, nft_contract.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner: cw721::OwnerOfResponse = test + let base_owner: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_018::Cw721QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1060,7 +1101,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // check name and symbol contains class id for instantiated nft contract - let contract_info: cw721::ContractInfoResponse = test + let contract_info: cw721_018::ContractInfoResponse = test .app .wrap() .query_wasm_smart( @@ -1070,7 +1111,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); assert_eq!( contract_info, - cw721::ContractInfoResponse { + cw721_018::ContractInfoResponse { name: class_id.to_string(), symbol: class_id.to_string() } @@ -1098,12 +1139,12 @@ fn test_do_instantiate_and_mint() { ); // Check that token_uri was set properly. - let token_info: cw721::NftInfoResponse = test + let token_info: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1112,12 +1153,12 @@ fn test_do_instantiate_and_mint() { token_info.token_uri, Some("https://moonphase.is/image.svg".to_string()) ); - let token_info: cw721::NftInfoResponse = test + let token_info: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1129,7 +1170,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_base_018::msg::ExecuteMsg::::TransferNft { recipient: nft_contract.to_string(), // new owner token_id: "1".to_string(), }, @@ -1138,7 +1179,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner: cw721::OwnerOfResponse = test + let owner: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -1153,12 +1194,12 @@ fn test_do_instantiate_and_mint() { assert_eq!(owner.owner, nft_contract.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner: cw721::OwnerOfResponse = test + let base_owner: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_018::Cw721QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1238,7 +1279,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // check name and symbol contains class id for instantiated nft contract - let contract_info: cw721::ContractInfoResponse = test + let contract_info: cw721_018::ContractInfoResponse = test .app .wrap() .query_wasm_smart( @@ -1248,7 +1289,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); assert_eq!( contract_info, - cw721::ContractInfoResponse { + cw721_018::ContractInfoResponse { name: "collection-name".to_string(), symbol: "collection-symbol".to_string() } @@ -1276,12 +1317,12 @@ fn test_do_instantiate_and_mint() { ); // Check that token_uri was set properly. - let token_info: cw721::NftInfoResponse = test + let token_info: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1290,12 +1331,12 @@ fn test_do_instantiate_and_mint() { token_info.token_uri, Some("https://moonphase.is/image.svg".to_string()) ); - let token_info: cw721::NftInfoResponse = test + let token_info: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1307,7 +1348,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_base_018::msg::ExecuteMsg::::TransferNft { recipient: nft_contract.to_string(), // new owner token_id: "1".to_string(), }, @@ -1316,7 +1357,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner: cw721::OwnerOfResponse = test + let owner: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -1331,12 +1372,12 @@ fn test_do_instantiate_and_mint() { assert_eq!(owner.owner, nft_contract.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner: cw721::OwnerOfResponse = test + let base_owner: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_018::Cw721QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1416,7 +1457,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // check name and symbol contains class id for instantiated nft contract - let contract_info: cw721::ContractInfoResponse = test + let contract_info: cw721_018::ContractInfoResponse = test .app .wrap() .query_wasm_smart( @@ -1426,7 +1467,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); assert_eq!( contract_info, - cw721::ContractInfoResponse { + cw721_018::ContractInfoResponse { name: "collection-name".to_string(), symbol: "collection-symbol".to_string() } @@ -1454,12 +1495,12 @@ fn test_do_instantiate_and_mint() { ); // Check that token_uri was set properly. - let token_info: cw721::NftInfoResponse = test + let token_info: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1468,12 +1509,12 @@ fn test_do_instantiate_and_mint() { token_info.token_uri, Some("https://moonphase.is/image.svg".to_string()) ); - let token_info: cw721::NftInfoResponse = test + let token_info: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1485,7 +1526,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_base_018::msg::ExecuteMsg::::TransferNft { recipient: nft_contract.to_string(), // new owner token_id: "1".to_string(), }, @@ -1494,7 +1535,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner: cw721::OwnerOfResponse = test + let owner: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -1509,12 +1550,12 @@ fn test_do_instantiate_and_mint() { assert_eq!(owner.owner, nft_contract.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner: cw721::OwnerOfResponse = test + let base_owner: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_018::Cw721QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1629,7 +1670,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .unwrap(); // check name and symbol contains class id for instantiated nft contract - let contract_info_1: cw721::ContractInfoResponse = test + let contract_info_1: cw721_018::ContractInfoResponse = test .app .wrap() .query_wasm_smart( @@ -1637,7 +1678,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { &Cw721QueryMsg::::ContractInfo {}, ) .unwrap(); - let contract_info_2: cw721::ContractInfoResponse = test + let contract_info_2: cw721_018::ContractInfoResponse = test .app .wrap() .query_wasm_smart( @@ -1647,14 +1688,14 @@ fn test_do_instantiate_and_mint_2_different_collections() { .unwrap(); assert_eq!( contract_info_1, - cw721::ContractInfoResponse { + cw721_018::ContractInfoResponse { name: class_id_1.to_string(), // name is set to class_id symbol: class_id_1.to_string() // symbol is set to class_id } ); assert_eq!( contract_info_2, - cw721::ContractInfoResponse { + cw721_018::ContractInfoResponse { name: class_id_2.to_string(), // name is set to class_id symbol: class_id_2.to_string() // symbol is set to class_id } @@ -1699,22 +1740,22 @@ fn test_do_instantiate_and_mint_2_different_collections() { ); // Check that token_uri was set properly. - let token_info_1_1: cw721::NftInfoResponse = test + let token_info_1_1: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract_1.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "1".to_string(), }, ) .unwrap(); - let token_info_2_1: cw721::NftInfoResponse = test + let token_info_2_1: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract_2.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1724,22 +1765,22 @@ fn test_do_instantiate_and_mint_2_different_collections() { Some("https://moonphase.is/image.svg".to_string()) ); assert_eq!(token_info_2_1.token_uri, Some("https://mr.t".to_string())); - let token_info_1_2: cw721::NftInfoResponse = test + let token_info_1_2: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract_1.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "2".to_string(), }, ) .unwrap(); - let token_info_2_2: cw721::NftInfoResponse = test + let token_info_2_2: cw721_018::NftInfoResponse> = test .app .wrap() .query_wasm_smart( nft_contract_2.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_018::Cw721QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1758,7 +1799,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract_1.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_base_018::msg::ExecuteMsg::::TransferNft { recipient: nft_contract_1.to_string(), token_id: "1".to_string(), }, @@ -1769,7 +1810,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract_2.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_base_018::msg::ExecuteMsg::::TransferNft { recipient: nft_contract_2.to_string(), token_id: "1".to_string(), }, @@ -1778,7 +1819,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner_1: cw721::OwnerOfResponse = test + let owner_1: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -1789,7 +1830,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { }, ) .unwrap(); - let owner_2: cw721::OwnerOfResponse = test + let owner_2: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -1804,23 +1845,23 @@ fn test_do_instantiate_and_mint_2_different_collections() { assert_eq!(owner_2.owner, nft_contract_2.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner_1: cw721::OwnerOfResponse = test + let base_owner_1: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract_1, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_018::Cw721QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, ) .unwrap(); - let base_owner_2: cw721::OwnerOfResponse = test + let base_owner_2: cw721_018::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract_2, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_018::Cw721QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1833,7 +1874,13 @@ fn test_do_instantiate_and_mint_2_different_collections() { #[test] fn test_do_instantiate_and_mint_no_instantiate() { - let mut test = Test::new(false, false, None, None, sg721_base_contract()); + let mut test = Test::new( + false, + false, + None, + Some(COLLECTION_OWNER_SOURCE_CHAIN.to_string()), // admin is used for royalty payment address! + sg721_base_contract(), + ); let collection_contract_source_chain = ClassId::new(test.app.api().addr_make(COLLECTION_CONTRACT_SOURCE_CHAIN)); let class_id = format!( @@ -1867,6 +1914,21 @@ fn test_do_instantiate_and_mint_no_instantiate() { contract_info: Default::default(), name: "name".to_string(), symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some( + "https://interchain.arkprotocol.io".to_string(), + ), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: Addr::unchecked( + "payment_address".to_string(), + ), + share: Decimal::one(), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), num_tokens: Some(1), }) .unwrap(), @@ -1935,23 +1997,26 @@ fn test_do_instantiate_and_mint_no_instantiate() { assert_eq!( collection_info, CollectionInfoResponse { - creator: test.app.api().addr_make(ICS721_CREATOR).to_string(), - description: "".to_string(), - image: STARGAZE_ICON_PLACEHOLDER.to_string(), - external_link: None, - explicit_content: None, - start_trading_time: None, - royalty_info: None, + creator: test.admin_and_pauser.clone().unwrap(), + description: "description".to_string(), + image: "https://ark.pass/image.png".to_string(), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + explicit_content: Some(false), + start_trading_time: Some(Timestamp::from_seconds(42)), + royalty_info: Some(RoyaltyInfoResponse { + payment_address: test.admin_and_pauser.unwrap(), + share: Decimal::one(), + }), } ); // Make sure we have our tokens. - let tokens: cw721::TokensResponse = test + let tokens: cw721_018::TokensResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::AllTokens { + &cw721_018::Cw721QueryMsg::AllTokens { start_after: None, limit: None, }, @@ -1993,6 +2058,21 @@ fn test_do_instantiate_and_mint_permissions() { contract_info: Default::default(), name: "name".to_string(), symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some( + "https://interchain.arkprotocol.io".to_string(), + ), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: Addr::unchecked( + "payment_address".to_string(), + ), + share: Decimal::one(), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), num_tokens: Some(1), }) .unwrap(), @@ -2035,7 +2115,7 @@ fn test_no_proxy_unknown_msg() { .execute_contract( test.app.api().addr_make("proxy"), test.ics721, - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN).to_string(), token_id: "1".to_string(), msg: to_json_binary(&msg).unwrap(), @@ -2073,7 +2153,7 @@ fn test_no_proxy_unauthorized() { .execute_contract( test.app.api().addr_make("foo"), test.ics721, - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN).to_string(), token_id: "1".to_string(), msg: to_json_binary(&msg).unwrap(), @@ -2137,7 +2217,7 @@ fn test_proxy_authorized() { .execute_contract( test.app.api().addr_make(COLLECTION_OWNER_SOURCE_CHAIN), source_cw721.clone(), - &cw721_base::ExecuteMsg::::Mint { + &cw721_base_018::ExecuteMsg::::Mint { token_id: "1".to_string(), owner: test.ics721.to_string(), token_uri: None, @@ -2154,7 +2234,7 @@ fn test_proxy_authorized() { .execute_contract( proxy_address, test.ics721, - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test .app .api() @@ -2194,7 +2274,7 @@ fn test_receive_nft() { .execute_contract( test.source_cw721.clone(), test.ics721.clone(), - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test.source_cw721_owner.to_string(), token_id, msg: to_json_binary(&IbcOutgoingMsg { @@ -2233,7 +2313,7 @@ fn test_receive_nft() { .unwrap(), ) .unwrap(); - let expected_collection_data = to_json_binary(&SgCollectionData { + let expected_collection_data = to_json_binary(&CollectionData { owner: Some( // collection data from source chain test.source_cw721_owner.to_string(), @@ -2242,14 +2322,16 @@ fn test_receive_nft() { name: "name".to_string(), symbol: "symbol".to_string(), num_tokens: Some(1), - collection_info: Some(CollectionInfoResponse { - creator: test.ics721.to_string(), - description: "".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some("https://interchain.arkprotocol.io".to_string()), image: STARGAZE_ICON_PLACEHOLDER.to_string(), - external_link: None, - explicit_content: None, - start_trading_time: None, - royalty_info: None, + royalty_info: Some(RoyaltyInfo { + payment_address: test.source_cw721_owner, + share: Decimal::bps(1000), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), }), }) .unwrap(); @@ -2269,7 +2351,7 @@ fn test_no_receive_with_proxy() { .execute_contract( test.app.api().addr_make("cw721"), test.ics721, - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN).to_string(), token_id: "1".to_string(), msg: to_json_binary(&IbcOutgoingMsg { diff --git a/e2e/adversarial_test.go b/e2e/adversarial_test.go index 7ae3b433..beeb2a01 100644 --- a/e2e/adversarial_test.go +++ b/e2e/adversarial_test.go @@ -3,10 +3,11 @@ package e2e import ( "encoding/json" "fmt" - "github.com/public-awesome/ics721/e2e/test_suite" "testing" "time" + "github.com/public-awesome/ics721/e2e/test_suite" + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -68,7 +69,7 @@ func (suite *AdversarialTestSuite) SetupTest() { resp := chain.StoreCodeFile("../artifacts/ics721_base.wasm") require.Equal(suite.T(), uint64(1), resp.CodeID) - resp = chain.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + resp = chain.StoreCodeFile("../external-wasms/cw721_metadata_onchain_v0.19.0.wasm") require.Equal(suite.T(), uint64(2), resp.CodeID) resp = chain.StoreCodeFile("../artifacts/ics721_base_tester.wasm") @@ -246,7 +247,7 @@ func (suite *AdversarialTestSuite) TestInvalidOnMineValidOnTheirs() { test_suite.Ics721TransferNft(suite.T(), suite.chainA, suite.pathAC, suite.coordinator, chainACw721, "bad kid 1", suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainC.SenderAccount.GetAddress(), "") err = suite.chainA.SmartQuery(chainACw721, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: suite.tokenIdA}}, &test_suite.OwnerOfResponse{}) - require.ErrorContains(suite.T(), err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") + require.ErrorContains(suite.T(), err, "cw721::state::NftInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // Send the NFT back, this time setting new metadata for the // class ID. @@ -479,7 +480,7 @@ func (suite *AdversarialTestSuite) TestMetadataForwarding() { require.ErrorContains( suite.T(), err, - "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found", + "cw721::state::NftInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found", ) // Check that token metadata was cleared. diff --git a/e2e/basic_test.go b/e2e/basic_test.go index 3a542783..7a4425cb 100644 --- a/e2e/basic_test.go +++ b/e2e/basic_test.go @@ -37,7 +37,7 @@ func (suite *BasicTestSuite) TestStoreCodes() { require.Equal(suite.T(), uint64(1), chainAStoreResp.CodeID) // Store the cw721 contract. - chainAStoreResp = suite.chainA.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + chainAStoreResp = suite.chainA.StoreCodeFile("../external-wasms/cw721_metadata_onchain_v0.19.0.wasm") require.Equal(suite.T(), uint64(2), chainAStoreResp.CodeID) } diff --git a/e2e/callback_test.go b/e2e/callback_test.go index e7846b39..57a928c3 100644 --- a/e2e/callback_test.go +++ b/e2e/callback_test.go @@ -58,7 +58,7 @@ func (suite *CallbackTestSuite) SetupTest() { resp := chain.StoreCodeFile("../artifacts/ics721_base.wasm") require.Equal(suite.T(), uint64(1), resp.CodeID) - resp = chain.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + resp = chain.StoreCodeFile("../external-wasms/cw721_metadata_onchain_v0.19.0.wasm") require.Equal(suite.T(), uint64(2), resp.CodeID) resp = chain.StoreCodeFile("../artifacts/ics721_base_tester.wasm") diff --git a/e2e/migrate_update_test.go b/e2e/migrate_update_test.go index 55124815..4c915d33 100644 --- a/e2e/migrate_update_test.go +++ b/e2e/migrate_update_test.go @@ -3,9 +3,10 @@ package e2e import ( "encoding/json" "fmt" - "github.com/public-awesome/ics721/e2e/test_suite" "testing" + "github.com/public-awesome/ics721/e2e/test_suite" + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -50,7 +51,7 @@ func (suite *MigrateUpdateTestSuite) SetupTest() { resp := chain.StoreCodeFile("../artifacts/ics721_base.wasm") require.Equal(suite.T(), uint64(1), resp.CodeID) - resp = chain.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + resp = chain.StoreCodeFile("../external-wasms/cw721_metadata_onchain_v0.19.0.wasm") require.Equal(suite.T(), uint64(2), resp.CodeID) resp = chain.StoreCodeFile("../artifacts/ics721_base_tester.wasm") diff --git a/e2e/test_suite/actions.go b/e2e/test_suite/actions.go index b05db322..1a845260 100644 --- a/e2e/test_suite/actions.go +++ b/e2e/test_suite/actions.go @@ -22,7 +22,7 @@ func StoreCodes(t *testing.T, chain *wasmibctesting.TestChain, bridge *sdk.AccAd resp := chain.StoreCodeFile("../artifacts/ics721_base.wasm") require.Equal(t, uint64(1), resp.CodeID) - resp = chain.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + resp = chain.StoreCodeFile("../external-wasms/cw721_metadata_onchain_v0.19.0.wasm") require.Equal(t, uint64(2), resp.CodeID) resp = chain.StoreCodeFile("../artifacts/ics721_base_tester.wasm") @@ -45,7 +45,7 @@ func StoreCodes(t *testing.T, chain *wasmibctesting.TestChain, bridge *sdk.AccAd func InstantiateBridge(t *testing.T, chain *wasmibctesting.TestChain) sdk.AccAddress { // Store the contracts. bridgeresp := chain.StoreCodeFile("../artifacts/ics721_base.wasm") - cw721resp := chain.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + cw721resp := chain.StoreCodeFile("../external-wasms/cw721_metadata_onchain_v0.19.0.wasm") // Instantiate the ICS721 contract. instantiateICS721 := InstantiateICS721Bridge{ diff --git a/e2e/transfer_test.go b/e2e/transfer_test.go index af9a89b5..86363e58 100644 --- a/e2e/transfer_test.go +++ b/e2e/transfer_test.go @@ -49,9 +49,9 @@ func (suite *TransferTestSuite) SetupTest() { require.Equal(suite.T(), uint64(1), chainBStoreResp.CodeID) // Store the cw721 contract. - chainAStoreResp = suite.chainA.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + chainAStoreResp = suite.chainA.StoreCodeFile("../external-wasms/cw721_metadata_onchain_v0.19.0.wasm") require.Equal(suite.T(), uint64(2), chainAStoreResp.CodeID) - chainBStoreResp = suite.chainB.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + chainBStoreResp = suite.chainB.StoreCodeFile("../external-wasms/cw721_metadata_onchain_v0.19.0.wasm") require.Equal(suite.T(), uint64(2), chainBStoreResp.CodeID) instantiateICS721 := test_suite.InstantiateICS721Bridge{ @@ -391,7 +391,7 @@ func runTestSendBetweenThreeIdenticalChains(t *testing.T, version int) { // contract "is this burned" so we just query and make sure it // now errors with a storage load failure. err := chainA.SmartQuery(chainANftDerivative, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &test_suite.OwnerOfResponse{}) - require.ErrorContains(t, err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") + require.ErrorContains(t, err, "cw721::state::NftInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // NFT should belong to chainC sender on chain C. ownerC = test_suite.QueryGetOwnerOf(t, chainC, chainCNft, "bad kid 1") @@ -407,7 +407,7 @@ func runTestSendBetweenThreeIdenticalChains(t *testing.T, version int) { // Burned on C. err = chainC.SmartQuery(chainCNft, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &test_suite.OwnerOfResponse{}) - require.ErrorContains(t, err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") + require.ErrorContains(t, err, "cw721::state::NftInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // B -> A path = getPath(1, 0) @@ -419,7 +419,7 @@ func runTestSendBetweenThreeIdenticalChains(t *testing.T, version int) { // Burned on chain B. err = chainB.SmartQuery(chainBNft, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &test_suite.OwnerOfResponse{}) - require.ErrorContains(t, err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") + require.ErrorContains(t, err, "cw721::state::NftInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // Hooray! We have completed the journey between three // identical blockchains using our ICS721 contract. @@ -475,7 +475,7 @@ func (suite *TransferTestSuite) TestMultipleAddressesInvolved() { // Make sure the NFT was burned on chain B err = suite.chainB.SmartQuery(chainBNft, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &test_suite.OwnerOfResponse{}) - require.ErrorContains(suite.T(), err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") + require.ErrorContains(suite.T(), err, "cw721::state::NftInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // Make another account on chain B and transfer to the new account. anotherAcount := test_suite.CreateAndFundAccount(suite.T(), suite.chainB, 19) @@ -493,7 +493,7 @@ func (suite *TransferTestSuite) TestMultipleAddressesInvolved() { // Make sure it was burned on B. err = suite.chainB.SmartQuery(chainBNft, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &test_suite.OwnerOfResponse{}) - require.ErrorContains(suite.T(), err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") + require.ErrorContains(suite.T(), err, "cw721::state::NftInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // Make sure it is owned by the correct address on A. resp := test_suite.OwnerOfResponse{} @@ -514,9 +514,9 @@ func TestCloseRejected(t *testing.T) { require.Equal(t, uint64(1), chainBStoreResp.CodeID) // Store the cw721 contract. - chainAStoreResp = chainA.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + chainAStoreResp = chainA.StoreCodeFile("../external-wasms/cw721_metadata_onchain_v0.19.0.wasm") require.Equal(t, uint64(2), chainAStoreResp.CodeID) - chainBStoreResp = chainB.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + chainBStoreResp = chainB.StoreCodeFile("../external-wasms/cw721_metadata_onchain_v0.19.0.wasm") require.Equal(t, uint64(2), chainBStoreResp.CodeID) // Store the cw721_base contract. diff --git a/external-wasms/cw721_metadata_onchain_v0.16.0.wasm b/external-wasms/cw721_metadata_onchain_v0.16.0.wasm new file mode 100644 index 00000000..5e87da2c Binary files /dev/null and b/external-wasms/cw721_metadata_onchain_v0.16.0.wasm differ diff --git a/external-wasms/cw721_metadata_onchain_v0.19.0.wasm b/external-wasms/cw721_metadata_onchain_v0.19.0.wasm new file mode 100644 index 00000000..9a3dd6f2 Binary files /dev/null and b/external-wasms/cw721_metadata_onchain_v0.19.0.wasm differ diff --git a/justfile b/justfile index 2ba8609b..314a065a 100644 --- a/justfile +++ b/justfile @@ -1,7 +1,7 @@ set dotenv-load platform := if arch() =~ "aarch64" {"linux/arm64"} else {"linux/amd64"} -image := if arch() =~ "aarch64" {"cosmwasm/workspace-optimizer-arm64:0.15.1"} else {"cosmwasm/workspace-optimizer:0.15.1"} +image := if arch() =~ "aarch64" {"cosmwasm/workspace-optimizer-arm64:0.16.0"} else {"cosmwasm/workspace-optimizer:0.16.0"} alias log := optimize-watch diff --git a/packages/ics721-types/src/token_types.rs b/packages/ics721-types/src/token_types.rs index 01535c5d..1b9d10eb 100644 --- a/packages/ics721-types/src/token_types.rs +++ b/packages/ics721-types/src/token_types.rs @@ -17,7 +17,7 @@ pub struct Token { pub id: TokenId, /// Optional URI pointing to off-chain metadata about the token. pub uri: Option, - /// Optional base64 encoded metadata about the token. + /// Optional base64 encoded onchain metadata about the token. pub data: Option, } diff --git a/packages/ics721/Cargo.toml b/packages/ics721/Cargo.toml index e850a9fb..eb631aa3 100644 --- a/packages/ics721/Cargo.toml +++ b/packages/ics721/Cargo.toml @@ -14,7 +14,10 @@ cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true, features = ["library"] } +cw721-metadata-onchain = { workspace = true, features = ["library"] } cw721-base-016 = { workspace = true, features = ["library"] } +cw721-base-017 = { workspace = true, features = ["library"] } +cw721-base-018 = { workspace = true, features = ["library"] } ics721-types = { workspace = true } thiserror = { workspace = true } serde = { workspace = true } @@ -31,3 +34,5 @@ cw-ics721-outgoing-proxy-rate-limit = { workspace = true } cw-multi-test = { workspace = true } cw2 = { workspace = true } cw721-016 = { workspace = true } +cw721-017 = { workspace = true } +cw721-018 = { workspace = true } diff --git a/packages/ics721/src/execute.rs b/packages/ics721/src/execute.rs index 67d4297d..3b5dadfa 100644 --- a/packages/ics721/src/execute.rs +++ b/packages/ics721/src/execute.rs @@ -4,6 +4,10 @@ use cosmwasm_std::{ from_json, to_json_binary, Addr, Binary, ContractInfoResponse, Deps, DepsMut, Empty, Env, Event, IbcMsg, MessageInfo, Order, Response, StdResult, SubMsg, WasmMsg, }; +use cw721::{ + msg::{NftExtensionMsg, RoyaltyInfoResponse}, + NftExtension, +}; use cw_storage_plus::Map; use ics721_types::{ ibc_types::{IbcOutgoingMsg, IbcOutgoingProxyMsg, NonFungibleTokenPacketData}, @@ -24,10 +28,10 @@ use crate::{ query_nft_contract_for_class_id, query_nft_contracts, }, state::{ - ClassIdInfo, CollectionData, UniversalAllNftInfoResponse, ADMIN_USED_FOR_CW721, - CLASS_ID_AND_NFT_CONTRACT_INFO, CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, CW721_CODE_ID, - INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, - OUTGOING_PROXY, PO, TOKEN_METADATA, + ClassIdInfo, CollectionData, UniversalAllNftInfoResponse, CLASS_ID_AND_NFT_CONTRACT_INFO, + CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, CW721_ADMIN, CW721_CODE_ID, + IBC_RECEIVE_TOKEN_METADATA, INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, + OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, }, token_types::{VoucherCreation, VoucherRedemption}, ContractError, @@ -68,7 +72,7 @@ where )); } - ADMIN_USED_FOR_CW721.save( + CW721_ADMIN.save( deps.storage, &msg.cw721_admin .as_ref() @@ -107,7 +111,7 @@ where ) -> Result, ContractError> { PO.error_if_paused(deps.storage)?; match msg { - ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender, token_id, msg, @@ -178,14 +182,14 @@ where // remove incoming channel entry and metadata INCOMING_CLASS_TOKEN_TO_CHANNEL .remove(deps.storage, (child_class_id.clone(), token_id.clone())); - TOKEN_METADATA.remove(deps.storage, (child_class_id.clone(), token_id.clone())); + IBC_RECEIVE_TOKEN_METADATA.remove(deps.storage, (child_class_id.clone(), token_id.clone())); // check NFT on child collection owned by recipient let maybe_nft_info: Option = deps .querier .query_wasm_smart( child_collection.clone(), - &cw721::Cw721QueryMsg::AllNftInfo { + &cw721_metadata_onchain::msg::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -206,7 +210,7 @@ where // note: this requires approval from recipient, or recipient burns it himself let burn_msg = WasmMsg::Execute { contract_addr: child_collection.to_string(), - msg: to_json_binary(&cw721::Cw721ExecuteMsg::Burn { + msg: to_json_binary(&cw721_metadata_onchain::msg::ExecuteMsg::Burn { token_id: token_id.clone().into(), })?, funds: vec![], @@ -268,7 +272,7 @@ where .querier .query_wasm_smart( home_collection.clone(), - &cw721::Cw721QueryMsg::AllNftInfo { + &cw721_metadata_onchain::msg::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -284,7 +288,7 @@ where // transfer NFT let transfer_msg = WasmMsg::Execute { contract_addr: home_collection.to_string(), - msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { + msg: to_json_binary(&cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: recipient.to_string(), token_id: token_id.clone().into(), })?, @@ -401,7 +405,7 @@ where // make sure NFT is escrowed by ics721 let UniversalAllNftInfoResponse { access, info } = deps.querier.query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::AllNftInfo { + &cw721_metadata_onchain::msg::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -410,12 +414,17 @@ where return Err(ContractError::NotEscrowedByIcs721(access.owner)); } - // cw721 doesn't support on-chain metadata yet - // here NFT is transferred to another chain, NFT itself may have been transferred to his chain before + // here NFT was transferred before, in this case it is stored in the storage, otherwise this is the home chain, + // and the NFT is transferred for the first time and onchain data comes from the cw721 contract // in this case ICS721 may have metadata stored - let token_metadata = TOKEN_METADATA + let token_metadata = match IBC_RECEIVE_TOKEN_METADATA .may_load(deps.storage, (class.id.clone(), token_id.clone()))? - .flatten(); + .flatten() + { + Some(metadata) => Some(metadata), + // incase there is none in the storage, this is the 'home' chain, so metadata is retrieved from the cw721 contract + None => info.extension.map(|ext| to_json_binary(&ext)).transpose()?, + }; let ibc_message = NonFungibleTokenPacketData { class_id: class.id.clone(), @@ -579,14 +588,12 @@ where }; CLASS_ID_AND_NFT_CONTRACT_INFO.save(deps.storage, &class.id, &class_id_info)?; - let admin = ADMIN_USED_FOR_CW721 - .load(deps.storage)? - .map(|a| a.to_string()); + let cw721_admin = CW721_ADMIN.load(deps.storage)?.map(|a| a.to_string()); let message = SubMsg::::reply_on_success( WasmMsg::Instantiate2 { - admin, + admin: cw721_admin.clone(), code_id: cw721_code_id, - msg: self.init_msg(deps.as_ref(), env, &class)?, + msg: self.init_msg(deps.as_ref(), env, &class, cw721_admin)?, funds: vec![], // Attempting to fit the class ID in the label field // can make this field too long which causes data @@ -601,18 +608,31 @@ where } /// Default implementation using `cw721_base::msg::InstantiateMsg` - fn init_msg(&self, deps: Deps, env: &Env, class: &Class) -> StdResult { + fn init_msg( + &self, + deps: Deps, + env: &Env, + class: &Class, + cw721_admin: Option, + ) -> StdResult { // use ics721 creator for withdraw address - let ContractInfoResponse { creator, .. } = deps + let ContractInfoResponse { creator, admin, .. } = deps .querier .query_wasm_contract_info(env.contract.address.to_string())?; // use by default ClassId, in case there's no class data with name and symbol - let mut instantiate_msg = cw721_base::msg::InstantiateMsg { + let cw721_admin_or_ics721_admin_or_ics721_creator = cw721_admin + .clone() + .or_else(|| admin.clone()) + .or_else(|| Some(creator.clone())) + .unwrap(); + let mut instantiate_msg = cw721_metadata_onchain::msg::InstantiateMsg { name: class.id.clone().into(), symbol: class.id.clone().into(), + collection_info_extension: None, // extension is set below, in case there's collection data + creator: Some(cw721_admin_or_ics721_admin_or_ics721_creator.clone()), // TODO maybe better using cw721 admin? minter: Some(env.contract.address.to_string()), - withdraw_address: Some(creator), + withdraw_address: Some(creator.clone()), }; // use collection data for setting name and symbol @@ -623,6 +643,21 @@ where if let Some(collection_data) = collection_data { instantiate_msg.name = collection_data.name; instantiate_msg.symbol = collection_data.symbol; + let collection_info_extension_msg = + collection_data + .extension + .map(|ext| cw721::msg::CollectionExtensionMsg { + description: Some(ext.description), + image: Some(ext.image), + external_link: ext.external_link, + explicit_content: ext.explicit_content, + start_trading_time: ext.start_trading_time, + royalty_info: ext.royalty_info.map(|r| RoyaltyInfoResponse { + payment_address: cw721_admin_or_ics721_admin_or_ics721_creator, // r.payment_address cant be used, since it is from another chain + share: r.share, + }), + }); + instantiate_msg.collection_info_extension = collection_info_extension_msg; } to_json_binary(&instantiate_msg) @@ -655,10 +690,12 @@ where .map(|token_id| { Ok(WasmMsg::Execute { contract_addr: nft_contract.to_string(), - msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { - recipient: receiver.to_string(), - token_id: token_id.into(), - })?, + msg: to_json_binary( + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { + recipient: receiver.to_string(), + token_id: token_id.into(), + }, + )?, funds: vec![], }) }) @@ -684,13 +721,35 @@ where // Note, once cw721 doesn't support on-chain metadata yet - but this is where we will set // that value on the debt-voucher token once it is supported. // Also note that this is set for every token, regardless of if data is None. - TOKEN_METADATA.save(deps.storage, (class_id.clone(), id.clone()), &data)?; + IBC_RECEIVE_TOKEN_METADATA.save( + deps.storage, + (class_id.clone(), id.clone()), + &data, + )?; + + // parse token data and check whether it is of type NftExtension + let extension: Option = match data { + Some(data) => from_json::(data) + .ok() + .map(|ext| NftExtensionMsg { + animation_url: ext.animation_url, + attributes: ext.attributes, + background_color: ext.background_color, + description: ext.description, + external_url: ext.external_url, + image: ext.image, + image_data: ext.image_data, + youtube_url: ext.youtube_url, + name: ext.name, + }), + None => None, + }; - let msg = cw721_base::msg::ExecuteMsg::::Mint { + let msg = cw721_metadata_onchain::msg::ExecuteMsg::Mint { token_id: id.into(), token_uri: uri, owner: receiver.to_string(), - extension: Empty::default(), + extension, }; Ok(WasmMsg::Execute { contract_addr: nft_contract.to_string(), @@ -764,9 +823,9 @@ where } if let Some(cw721_admin) = cw721_admin.clone() { if cw721_admin.is_empty() { - ADMIN_USED_FOR_CW721.save(deps.storage, &None)?; + CW721_ADMIN.save(deps.storage, &None)?; } else { - ADMIN_USED_FOR_CW721 + CW721_ADMIN .save(deps.storage, &Some(deps.api.addr_validate(&cw721_admin)?))?; } } diff --git a/packages/ics721/src/ibc.rs b/packages/ics721/src/ibc.rs index fa96eef6..3029ac9c 100644 --- a/packages/ics721/src/ibc.rs +++ b/packages/ics721/src/ibc.rs @@ -14,8 +14,8 @@ use crate::{ ibc_packet_receive::receive_ibc_packet, query::{load_class_id_for_nft_contract, load_nft_contract_for_class_id}, state::{ - INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, - OUTGOING_PROXY, TOKEN_METADATA, + IBC_RECEIVE_TOKEN_METADATA, INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, + OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, }, ContractError, }; @@ -138,11 +138,12 @@ where if returning_to_source { // This token's journey is complete, for now. INCOMING_CLASS_TOKEN_TO_CHANNEL.remove(deps.storage, key); - TOKEN_METADATA.remove(deps.storage, (msg.class_id.clone(), token.clone())); + IBC_RECEIVE_TOKEN_METADATA + .remove(deps.storage, (msg.class_id.clone(), token.clone())); messages.push(WasmMsg::Execute { contract_addr: nft_contract.to_string(), - msg: to_json_binary(&cw721::Cw721ExecuteMsg::Burn { + msg: to_json_binary(&cw721_metadata_onchain::msg::ExecuteMsg::Burn { token_id: token.into(), })?, funds: vec![], @@ -212,7 +213,7 @@ where .remove(deps.storage, (message.class_id.clone(), token_id.clone())); Ok(WasmMsg::Execute { contract_addr: nft_contract.to_string(), - msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { + msg: to_json_binary(&cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: sender.to_string(), token_id: token_id.into(), })?, diff --git a/packages/ics721/src/msg.rs b/packages/ics721/src/msg.rs index 4d2563a3..700b53d5 100644 --- a/packages/ics721/src/msg.rs +++ b/packages/ics721/src/msg.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, WasmMsg}; +use cw721::receiver::Cw721ReceiveMsg; use cw_cii::ContractInstantiateInfo; use crate::token_types::{VoucherCreation, VoucherRedemption}; @@ -39,7 +40,7 @@ pub struct InstantiateMsg { pub enum ExecuteMsg { /// Receives a NFT to be IBC transfered away. The `msg` field must /// be a binary encoded `IbcOutgoingMsg`. - ReceiveNft(cw721::Cw721ReceiveMsg), + ReceiveNft(Cw721ReceiveMsg), /// Pauses the ICS721 contract. Only the pauser may call this. In pausing /// the contract, the pauser burns the right to do so again. @@ -145,7 +146,7 @@ pub enum QueryMsg { /// Gets the owner of the NFT identified by CLASS_ID and /// TOKEN_ID. Errors if no such NFT exists. Returns /// `cw721::OwnerOfResonse`. - #[returns(::cw721::OwnerOfResponse)] + #[returns(::cw721::msg::OwnerOfResponse)] Owner { class_id: String, token_id: String }, /// Gets the address that may pause this contract if one is set. diff --git a/packages/ics721/src/query.rs b/packages/ics721/src/query.rs index 4f756cd3..06bf2395 100644 --- a/packages/ics721/src/query.rs +++ b/packages/ics721/src/query.rs @@ -6,9 +6,10 @@ use crate::{ helpers::get_instantiate2_address, msg::QueryMsg, state::{ - UniversalAllNftInfoResponse, ADMIN_USED_FOR_CW721, CLASS_ID_AND_NFT_CONTRACT_INFO, - CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, CW721_CODE_ID, INCOMING_CLASS_TOKEN_TO_CHANNEL, - INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, TOKEN_METADATA, + UniversalAllNftInfoResponse, CLASS_ID_AND_NFT_CONTRACT_INFO, CLASS_ID_TO_CLASS, + CONTRACT_ADDR_LENGTH, CW721_ADMIN, CW721_CODE_ID, IBC_RECEIVE_TOKEN_METADATA, + INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, + OUTGOING_PROXY, PO, }, ContractError, }; @@ -46,9 +47,7 @@ pub trait Ics721Query { QueryMsg::OutgoingProxy {} => Ok(to_json_binary(&OUTGOING_PROXY.load(deps.storage)?)?), QueryMsg::IncomingProxy {} => Ok(to_json_binary(&INCOMING_PROXY.load(deps.storage)?)?), QueryMsg::Cw721CodeId {} => Ok(to_json_binary(&query_cw721_code_id(deps)?)?), - QueryMsg::Cw721Admin {} => { - Ok(to_json_binary(&ADMIN_USED_FOR_CW721.load(deps.storage)?)?) - } + QueryMsg::Cw721Admin {} => Ok(to_json_binary(&CW721_ADMIN.load(deps.storage)?)?), QueryMsg::ContractAddrLength {} => Ok(to_json_binary( &CONTRACT_ADDR_LENGTH.may_load(deps.storage)?, )?), @@ -138,7 +137,7 @@ pub fn query_token_metadata( let class_id = ClassId::new(class_id); let Some(token_metadata) = - TOKEN_METADATA.may_load(deps.storage, (class_id.clone(), token_id.clone()))? + IBC_RECEIVE_TOKEN_METADATA.may_load(deps.storage, (class_id.clone(), token_id.clone()))? else { // Token metadata is set unconditionaly on mint. If we have no // metadata entry, we have no entry for this token at all. @@ -150,7 +149,7 @@ pub fn query_token_metadata( }; let UniversalAllNftInfoResponse { info, .. } = deps.querier.query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::AllNftInfo { + &cw721_metadata_onchain::msg::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -166,11 +165,11 @@ pub fn query_owner( deps: Deps, class_id: String, token_id: String, -) -> StdResult { +) -> StdResult { let nft_contract = load_nft_contract_for_class_id(deps.storage, class_id)?; - let resp: cw721::OwnerOfResponse = deps.querier.query_wasm_smart( + let resp: cw721::msg::OwnerOfResponse = deps.querier.query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id, include_expired: None, }, diff --git a/packages/ics721/src/state.rs b/packages/ics721/src/state.rs index 0c43b0bc..21cc0495 100644 --- a/packages/ics721/src/state.rs +++ b/packages/ics721/src/state.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::{cw_serde, schemars::JsonSchema}; -use cosmwasm_std::{Addr, Binary, ContractInfoResponse, Empty}; +use cosmwasm_std::{Addr, Binary, ContractInfoResponse, Empty, Timestamp}; +use cw721::{DefaultOptionalCollectionExtension, DefaultOptionalNftExtension}; use cw_pause_once::PauseOrchestrator; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, UniqueIndex}; use serde::{Deserialize, Serialize}; @@ -39,41 +40,46 @@ pub const OUTGOING_CLASS_TOKEN_TO_CHANNEL: Map<(ClassId, TokenId), String> = Map /// Same as above, but for NFTs arriving at this contract. pub const INCOMING_CLASS_TOKEN_TO_CHANNEL: Map<(ClassId, TokenId), String> = Map::new("i"); +/// IMPORTANT: collections can either come from (a) smart contracts or (b) nft module. +/// This map is the truth of source. Only for smart contracts and in case of `receive_nft` +/// onchain data is retrieved directly from cw721 contract and stored in this map during ibc receive. /// Maps (class ID, token ID) -> token metadata. Used to store /// on-chain metadata for tokens that have arrived from other /// chains. When a token arrives, it's metadata (regardless of if it /// is `None`) is stored in this map. When the token is returned to /// it's source chain, the metadata is removed from the map. -pub const TOKEN_METADATA: Map<(ClassId, TokenId), Option> = Map::new("j"); +pub const IBC_RECEIVE_TOKEN_METADATA: Map<(ClassId, TokenId), Option> = Map::new("j"); /// The admin address for instantiating new cw721 contracts. In case of None, contract is immutable. -pub const ADMIN_USED_FOR_CW721: Item> = Item::new("l"); +pub const CW721_ADMIN: Item> = Item::new("l"); /// The optional contract address length being used for instantiate2. In case of None, default length is 32 (standard in cosmwasm). /// So length must be shorter than 32. For example, Injective has 20 length address. /// Bug: https://github.com/CosmWasm/cosmwasm/issues/2155 pub const CONTRACT_ADDR_LENGTH: Item = Item::new("n"); -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct UniversalAllNftInfoResponse { pub access: UniversalOwnerOfResponse, pub info: UniversalNftInfoResponse, } /// Based on `cw721::ContractInfoResponse v0.18` -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct UniversalCollectionInfoResponse { pub name: String, pub symbol: String, + // new props from cw721 v19 + pub extension: DefaultOptionalCollectionExtension, + /// In original response `updated_at`` is a timestamp, here we make it optional, since older versions don't have it. + pub updated_at: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct UniversalNftInfoResponse { pub token_uri: Option, - #[serde(skip_deserializing)] - #[allow(dead_code)] - extension: Empty, + pub extension: DefaultOptionalNftExtension, } /// Collection data send by ICS721 on source chain. It is an optional class data for interchain transfer to target chain. @@ -88,12 +94,13 @@ pub struct UniversalNftInfoResponse { pub struct CollectionData { pub owner: Option, pub contract_info: Option, + pub num_tokens: Option, pub name: String, pub symbol: String, - pub num_tokens: Option, + pub extension: DefaultOptionalCollectionExtension, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct UniversalOwnerOfResponse { pub owner: String, @@ -125,20 +132,23 @@ impl<'a> IndexList for ClassIdInfoIndexes<'a> { #[cfg(test)] mod tests { - use cosmwasm_std::{from_json, to_json_binary, Coin, Empty}; + use cosmwasm_std::{from_json, to_json_binary}; + use cw721::{DefaultOptionalNftExtension, NftExtension}; use super::UniversalAllNftInfoResponse; #[test] fn test_universal_deserialize() { - let start = cw721::AllNftInfoResponse:: { - access: cw721::OwnerOfResponse { + let start = cw721::msg::AllNftInfoResponse:: { + access: cw721::msg::OwnerOfResponse { owner: "foo".to_string(), approvals: vec![], }, - info: cw721::NftInfoResponse { + info: cw721::msg::NftInfoResponse { token_uri: None, - extension: Coin::new(100, "ujuno"), + extension: Some(NftExtension { + ..Default::default() + }), }, }; let start = to_json_binary(&start).unwrap(); @@ -146,6 +156,11 @@ mod tests { assert_eq!(end.access.owner, "foo".to_string()); assert_eq!(end.access.approvals, vec![]); assert_eq!(end.info.token_uri, None); - assert_eq!(end.info.extension, Empty::default()) + assert_eq!( + end.info.extension, + Some(NftExtension { + ..Default::default() + }) + ) } } diff --git a/packages/ics721/src/testing/contract.rs b/packages/ics721/src/testing/contract.rs deleted file mode 100644 index 6980a4f3..00000000 --- a/packages/ics721/src/testing/contract.rs +++ /dev/null @@ -1,666 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{ - from_json, - testing::{mock_dependencies, mock_env, mock_info, MockQuerier, MOCK_CONTRACT_ADDR}, - to_json_binary, Addr, ContractResult, CosmosMsg, DepsMut, Empty, IbcMsg, IbcTimeout, Order, - QuerierResult, Response, StdResult, SubMsg, Timestamp, WasmQuery, -}; -use cw721::{AllNftInfoResponse, NftInfoResponse, NumTokensResponse}; -use cw721_base::QueryMsg; -use cw_cii::ContractInstantiateInfo; -use cw_ownable::Ownership; -use cw_storage_plus::Map; - -use crate::{ - execute::Ics721Execute, - ibc::{Ics721Ibc, INSTANTIATE_INCOMING_PROXY_REPLY_ID, INSTANTIATE_OUTGOING_PROXY_REPLY_ID}, - msg::{InstantiateMsg, MigrateMsg}, - query::{ - query_class_id_for_nft_contract, query_nft_contract_for_class_id, query_nft_contracts, - Ics721Query, - }, - state::{ - CollectionData, ADMIN_USED_FOR_CW721, CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, - CW721_CODE_ID, INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, - }, - utils::get_collection_data, -}; -use ics721_types::{ - ibc_types::{IbcOutgoingMsg, NonFungibleTokenPacketData}, - token_types::{ClassId, TokenId}, -}; - -const NFT_CONTRACT_1: &str = "nft1"; -const NFT_CONTRACT_2: &str = "nft2"; -const CLASS_ID_1: &str = "some/class/id1"; -const CLASS_ID_2: &str = "some/class/id2"; -const OWNER_ADDR: &str = "owner"; -const ADMIN_ADDR: &str = "admin"; -const PAUSER_ADDR: &str = "pauser"; - -#[derive(Default)] -pub struct Ics721Contract {} -impl Ics721Execute for Ics721Contract { - type ClassData = CollectionData; - - fn get_class_data(&self, deps: &DepsMut, sender: &Addr) -> StdResult> { - get_collection_data(deps, sender).map(Option::Some) - } -} -impl Ics721Ibc for Ics721Contract {} -impl Ics721Query for Ics721Contract {} - -#[derive(Default)] -pub struct Ics721ContractNoClassData {} -impl Ics721Execute for Ics721ContractNoClassData { - type ClassData = CollectionData; - - fn get_class_data( - &self, - _deps: &DepsMut, - _sender: &Addr, - ) -> StdResult> { - Ok(None) - } -} -impl Ics721Ibc for Ics721ContractNoClassData {} -impl Ics721Query for Ics721ContractNoClassData {} - -// copy of cosmwasm_std::ContractInfoResponse (marked as non-exhaustive) -#[cw_serde] -pub struct ContractInfoResponse { - pub code_id: u64, - /// address that instantiated this contract - pub creator: String, - /// admin who can run migrations (if any) - pub admin: Option, - /// if set, the contract is pinned to the cache, and thus uses less gas when called - pub pinned: bool, - /// set if this contract has bound an IBC port - pub ibc_port: Option, -} - -fn mock_querier(query: &WasmQuery) -> QuerierResult { - match query { - cosmwasm_std::WasmQuery::Smart { - contract_addr: _, - msg, - } => match from_json::>(&msg).unwrap() { - QueryMsg::Ownership {} => QuerierResult::Ok(ContractResult::Ok( - to_json_binary(&Ownership:: { - owner: Some(Addr::unchecked(OWNER_ADDR)), - pending_owner: None, - pending_expiry: None, - }) - .unwrap(), - )), - QueryMsg::AllNftInfo { .. } => QuerierResult::Ok(ContractResult::Ok( - to_json_binary(&AllNftInfoResponse::> { - access: cw721::OwnerOfResponse { - owner: MOCK_CONTRACT_ADDR.to_string(), - approvals: vec![], - }, - info: NftInfoResponse { - token_uri: Some("https://moonphase.is/image.svg".to_string()), - extension: None, - }, - }) - .unwrap(), - )), - QueryMsg::ContractInfo {} => QuerierResult::Ok(ContractResult::Ok( - to_json_binary(&cw721::ContractInfoResponse { - name: "name".to_string(), - symbol: "symbol".to_string(), - }) - .unwrap(), - )), - QueryMsg::NumTokens {} => QuerierResult::Ok(ContractResult::Ok( - to_json_binary(&NumTokensResponse { count: 1 }).unwrap(), - )), - _ => unimplemented!(), - }, - cosmwasm_std::WasmQuery::ContractInfo { .. } => QuerierResult::Ok(ContractResult::Ok( - to_json_binary(&ContractInfoResponse { - code_id: 0, - creator: "creator".to_string(), - admin: None, - pinned: false, - ibc_port: None, - }) - .unwrap(), - )), - _ => unimplemented!(), - } -} - -fn mock_querier_v016(query: &WasmQuery) -> QuerierResult { - match query { - cosmwasm_std::WasmQuery::Smart { - contract_addr: _, - msg, - } => match from_json::>(&msg).unwrap() { - // unwrap using latest (not old) cw721-base, since it is backwards compatible - cw721_base::msg::QueryMsg::Minter {} => QuerierResult::Ok(ContractResult::Ok( - to_json_binary( - // return v016 response - &cw721_base_016::msg::MinterResponse { - minter: OWNER_ADDR.to_string(), - }, - ) - .unwrap(), - )), - cw721_base::msg::QueryMsg::AllNftInfo { .. } => QuerierResult::Ok(ContractResult::Ok( - to_json_binary( - // return v016 response - &cw721_016::AllNftInfoResponse::> { - access: cw721_016::OwnerOfResponse { - owner: MOCK_CONTRACT_ADDR.to_string(), - approvals: vec![], - }, - info: cw721_016::NftInfoResponse { - token_uri: Some("https://moonphase.is/image.svg".to_string()), - extension: None, - }, - }, - ) - .unwrap(), - )), - QueryMsg::ContractInfo {} => QuerierResult::Ok(ContractResult::Ok( - to_json_binary(&cw721_016::ContractInfoResponse { - name: "name".to_string(), - symbol: "symbol".to_string(), - }) - .unwrap(), - )), - QueryMsg::NumTokens {} => QuerierResult::Ok(ContractResult::Ok( - to_json_binary(&cw721_016::NumTokensResponse { count: 1 }).unwrap(), - )), - _ => QuerierResult::Err(cosmwasm_std::SystemError::Unknown {}), // throws error for Ownership query - }, - cosmwasm_std::WasmQuery::ContractInfo { .. } => QuerierResult::Ok(ContractResult::Ok( - to_json_binary(&ContractInfoResponse { - code_id: 0, - creator: "creator".to_string(), - admin: None, - pinned: false, - ibc_port: None, - }) - .unwrap(), - )), - _ => unimplemented!(), - } -} - -#[test] -fn test_receive_nft() { - // test case: receive nft from cw721-base - let expected_contract_info: cosmwasm_std::ContractInfoResponse = from_json( - to_json_binary(&ContractInfoResponse { - code_id: 0, - creator: "creator".to_string(), - admin: None, - pinned: false, - ibc_port: None, - }) - .unwrap(), - ) - .unwrap(); - { - let mut querier = MockQuerier::default(); - querier.update_wasm(mock_querier); - - let mut deps = mock_dependencies(); - deps.querier = querier; - let env = mock_env(); - - let info = mock_info(NFT_CONTRACT_1, &[]); - let token_id = "1"; - let sender = "ekez".to_string(); - let msg = to_json_binary(&IbcOutgoingMsg { - receiver: "callum".to_string(), - channel_id: "channel-1".to_string(), - timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), - memo: None, - }) - .unwrap(); - - let res: cosmwasm_std::Response<_> = Ics721Contract::default() - .receive_nft( - deps.as_mut(), - env, - &info.sender, - TokenId::new(token_id), - sender.clone(), - msg, - ) - .unwrap(); - assert_eq!(res.messages.len(), 1); - - let channel_id = "channel-1".to_string(); - assert_eq!( - res.messages[0], - SubMsg::new(CosmosMsg::::Ibc(IbcMsg::SendPacket { - channel_id: channel_id.clone(), - timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), - data: to_json_binary(&NonFungibleTokenPacketData { - class_id: ClassId::new(NFT_CONTRACT_1), - class_uri: None, - class_data: Some( - to_json_binary(&CollectionData { - owner: Some(OWNER_ADDR.to_string()), - contract_info: Some(expected_contract_info.clone()), - name: "name".to_string(), - symbol: "symbol".to_string(), - num_tokens: Some(1), - }) - .unwrap() - ), - token_data: None, - token_ids: vec![TokenId::new(token_id)], - token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), - sender, - receiver: "callum".to_string(), - memo: None, - }) - .unwrap() - })) - ); - - // check outgoing classID and tokenID - let keys = OUTGOING_CLASS_TOKEN_TO_CHANNEL - .keys(deps.as_mut().storage, None, None, Order::Ascending) - .collect::>>() - .unwrap(); - assert_eq!(keys, [(NFT_CONTRACT_1.to_string(), token_id.to_string())]); - - // check channel - let key = ( - ClassId::new(keys[0].clone().0), - TokenId::new(keys[0].clone().1), - ); - assert_eq!( - OUTGOING_CLASS_TOKEN_TO_CHANNEL - .load(deps.as_mut().storage, key) - .unwrap(), - channel_id - ) - } - // test case: receive nft from old/v016 cw721-base - { - let mut querier = MockQuerier::default(); - querier.update_wasm(mock_querier_v016); - - let mut deps = mock_dependencies(); - deps.querier = querier; - let env = mock_env(); - - let info = mock_info(NFT_CONTRACT_1, &[]); - let token_id = "1"; - let sender = "ekez".to_string(); - let msg = to_json_binary(&IbcOutgoingMsg { - receiver: "callum".to_string(), - channel_id: "channel-1".to_string(), - timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), - memo: None, - }) - .unwrap(); - - let res: cosmwasm_std::Response<_> = Ics721Contract::default() - .receive_nft( - deps.as_mut(), - env, - &info.sender, - TokenId::new(token_id), - sender.clone(), - msg, - ) - .unwrap(); - assert_eq!(res.messages.len(), 1); - - let channel_id = "channel-1".to_string(); - assert_eq!( - res.messages[0], - SubMsg::new(CosmosMsg::::Ibc(IbcMsg::SendPacket { - channel_id: channel_id.clone(), - timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), - data: to_json_binary(&NonFungibleTokenPacketData { - class_id: ClassId::new(NFT_CONTRACT_1), - class_uri: None, - class_data: Some( - to_json_binary(&CollectionData { - owner: Some(OWNER_ADDR.to_string()), - contract_info: Some(expected_contract_info), - name: "name".to_string(), - symbol: "symbol".to_string(), - num_tokens: Some(1), - }) - .unwrap() - ), - token_data: None, - token_ids: vec![TokenId::new(token_id)], - token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), - sender, - receiver: "callum".to_string(), - memo: None, - }) - .unwrap() - })) - ); - - // check outgoing classID and tokenID - let keys = OUTGOING_CLASS_TOKEN_TO_CHANNEL - .keys(deps.as_mut().storage, None, None, Order::Ascending) - .collect::>>() - .unwrap(); - assert_eq!(keys, [(NFT_CONTRACT_1.to_string(), token_id.to_string())]); - - // check channel - let key = ( - ClassId::new(keys[0].clone().0), - TokenId::new(keys[0].clone().1), - ); - assert_eq!( - OUTGOING_CLASS_TOKEN_TO_CHANNEL - .load(deps.as_mut().storage, key) - .unwrap(), - channel_id - ) - } - // test case: receive nft with no class data - { - let mut querier = MockQuerier::default(); - querier.update_wasm(mock_querier_v016); - - let mut deps = mock_dependencies(); - deps.querier = querier; - let env = mock_env(); - - let info = mock_info(NFT_CONTRACT_1, &[]); - let token_id = "1"; - let sender = "ekez".to_string(); - let msg = to_json_binary(&IbcOutgoingMsg { - receiver: "callum".to_string(), - channel_id: "channel-1".to_string(), - timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), - memo: None, - }) - .unwrap(); - - let res: cosmwasm_std::Response<_> = Ics721ContractNoClassData::default() - .receive_nft( - deps.as_mut(), - env, - &info.sender, - TokenId::new(token_id), - sender.clone(), - msg, - ) - .unwrap(); - assert_eq!(res.messages.len(), 1); - - let channel_id = "channel-1".to_string(); - assert_eq!( - res.messages[0], - SubMsg::new(CosmosMsg::::Ibc(IbcMsg::SendPacket { - channel_id: channel_id.clone(), - timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), - data: to_json_binary(&NonFungibleTokenPacketData { - class_id: ClassId::new(NFT_CONTRACT_1), - class_uri: None, - class_data: None, - token_data: None, - token_ids: vec![TokenId::new(token_id)], - token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), - sender, - receiver: "callum".to_string(), - memo: None, - }) - .unwrap() - })) - ); - - // check outgoing classID and tokenID - let keys = OUTGOING_CLASS_TOKEN_TO_CHANNEL - .keys(deps.as_mut().storage, None, None, Order::Ascending) - .collect::>>() - .unwrap(); - assert_eq!(keys, [(NFT_CONTRACT_1.to_string(), token_id.to_string())]); - - // check channel - let key = ( - ClassId::new(keys[0].clone().0), - TokenId::new(keys[0].clone().1), - ); - assert_eq!( - OUTGOING_CLASS_TOKEN_TO_CHANNEL - .load(deps.as_mut().storage, key) - .unwrap(), - channel_id - ) - } -} - -#[test] -fn test_receive_sets_uri() { - let mut querier = MockQuerier::default(); - querier.update_wasm(mock_querier); - - let mut deps = mock_dependencies(); - deps.querier = querier; - let env = mock_env(); - - let info = mock_info(NFT_CONTRACT_1, &[]); - let token_id = TokenId::new("1"); - let sender = "ekez".to_string(); - let msg = to_json_binary(&IbcOutgoingMsg { - receiver: "ekez".to_string(), - channel_id: "channel-1".to_string(), - timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(42)), - memo: None, - }) - .unwrap(); - - Ics721Contract {} - .receive_nft(deps.as_mut(), env, &info.sender, token_id, sender, msg) - .unwrap(); - - let class = CLASS_ID_TO_CLASS - .load(deps.as_ref().storage, ClassId::new(NFT_CONTRACT_1)) - .unwrap(); - assert_eq!(class.uri, None); - let expected_contract_info: cosmwasm_std::ContractInfoResponse = from_json( - to_json_binary(&ContractInfoResponse { - code_id: 0, - creator: "creator".to_string(), - admin: None, - pinned: false, - ibc_port: None, - }) - .unwrap(), - ) - .unwrap(); - assert_eq!( - class.data, - Some( - to_json_binary(&CollectionData { - owner: Some(OWNER_ADDR.to_string()), - contract_info: Some(expected_contract_info), - name: "name".to_string(), - symbol: "symbol".to_string(), - num_tokens: Some(1), - }) - .unwrap() - ), - ); -} - -fn instantiate_msg( - incoming_proxy: Option, - outgoing_proxy: Option, -) -> InstantiateMsg { - InstantiateMsg { - cw721_base_code_id: 0, - incoming_proxy, - outgoing_proxy, - pauser: Some(PAUSER_ADDR.to_string()), - cw721_admin: Some(ADMIN_ADDR.to_string()), - contract_addr_length: None, - } -} - -#[test] -fn test_instantiate() { - let mut deps = mock_dependencies(); - let env = mock_env(); - let info = mock_info(OWNER_ADDR, &[]); - let incoming_proxy_init_msg = ContractInstantiateInfo { - code_id: 0, - msg: to_json_binary("incoming").unwrap(), - admin: Some(cw_cii::Admin::Address { - addr: ADMIN_ADDR.to_string(), - }), - label: "incoming".to_string(), - }; - let outgoing_proxy_init_msg = ContractInstantiateInfo { - code_id: 0, - msg: to_json_binary("outgoing").unwrap(), - admin: Some(cw_cii::Admin::Address { - addr: ADMIN_ADDR.to_string(), - }), - label: "outgoing".to_string(), - }; - let mut msg = instantiate_msg( - Some(incoming_proxy_init_msg.clone()), - Some(outgoing_proxy_init_msg.clone()), - ); - msg.contract_addr_length = Some(20); - let response = Ics721Contract {} - .instantiate(deps.as_mut(), env.clone(), info, msg.clone()) - .unwrap(); - - let expected_incoming_proxy_msg = - incoming_proxy_init_msg.into_wasm_msg(env.clone().contract.address); - let expected_outgoing_proxy_msg = outgoing_proxy_init_msg.into_wasm_msg(env.contract.address); - let expected_response = Response::::default() - .add_submessage(SubMsg::reply_on_success( - expected_incoming_proxy_msg, - INSTANTIATE_INCOMING_PROXY_REPLY_ID, - )) - .add_submessage(SubMsg::reply_on_success( - expected_outgoing_proxy_msg, - INSTANTIATE_OUTGOING_PROXY_REPLY_ID, - )) - .add_attribute("method", "instantiate") - .add_attribute("cw721_code_id", msg.cw721_base_code_id.to_string()) - .add_attribute("cw721_admin", ADMIN_ADDR) - .add_attribute("contract_addr_length", "20"); - assert_eq!(response, expected_response); - assert_eq!(CW721_CODE_ID.load(&deps.storage).unwrap(), 0); - // incoming and outgoing proxy initially set to None and set later in sub msg - assert_eq!(OUTGOING_PROXY.load(&deps.storage).unwrap(), None); - assert_eq!(INCOMING_PROXY.load(&deps.storage).unwrap(), None); - assert_eq!( - PO.pauser.load(&deps.storage).unwrap(), - Some(Addr::unchecked(PAUSER_ADDR)) - ); - assert!(!PO.paused.load(&deps.storage).unwrap()); - assert_eq!( - ADMIN_USED_FOR_CW721.load(&deps.storage).unwrap(), - Some(Addr::unchecked(ADMIN_ADDR.to_string())) - ); - assert_eq!(CONTRACT_ADDR_LENGTH.load(&deps.storage).unwrap(), 20); -} - -#[test] -fn test_migrate() { - let mut deps = mock_dependencies(); - let env = mock_env(); - let info = mock_info(OWNER_ADDR, &[]); - let msg = instantiate_msg(None, None); - Ics721Contract {} - .instantiate(deps.as_mut(), env.clone(), info, msg.clone()) - .unwrap(); - let msg = MigrateMsg::WithUpdate { - pauser: Some("some_other_pauser".to_string()), - outgoing_proxy: Some("outgoing".to_string()), - incoming_proxy: Some("incoming".to_string()), - cw721_base_code_id: Some(1), - cw721_admin: Some("some_other_admin".to_string()), - contract_addr_length: Some(20), - }; - - // before migrate, populate legacy - let class_id_to_nft_contract: Map = Map::new("e"); - class_id_to_nft_contract - .save( - deps.as_mut().storage, - ClassId::new(CLASS_ID_1), - &Addr::unchecked(NFT_CONTRACT_1), - ) - .unwrap(); - class_id_to_nft_contract - .save( - deps.as_mut().storage, - ClassId::new(CLASS_ID_2), - &Addr::unchecked(NFT_CONTRACT_2), - ) - .unwrap(); - let nft_contract_to_class_id: Map = Map::new("f"); - nft_contract_to_class_id - .save( - deps.as_mut().storage, - Addr::unchecked(NFT_CONTRACT_1), - &ClassId::new(CLASS_ID_1), - ) - .unwrap(); - nft_contract_to_class_id - .save( - deps.as_mut().storage, - Addr::unchecked(NFT_CONTRACT_2), - &ClassId::new(CLASS_ID_2), - ) - .unwrap(); - - // migrate - Ics721Contract {} - .migrate(deps.as_mut(), env.clone(), msg) - .unwrap(); - - assert_eq!( - PO.pauser.load(&deps.storage).unwrap(), - Some(Addr::unchecked("some_other_pauser")) - ); - assert_eq!( - OUTGOING_PROXY.load(&deps.storage).unwrap(), - Some(Addr::unchecked("outgoing")) - ); - assert_eq!( - INCOMING_PROXY.load(&deps.storage).unwrap(), - Some(Addr::unchecked("incoming")) - ); - assert_eq!(CW721_CODE_ID.load(&deps.storage).unwrap(), 1); - assert_eq!( - ADMIN_USED_FOR_CW721.load(&deps.storage).unwrap(), - Some(Addr::unchecked("some_other_admin")) - ); - assert_eq!(CONTRACT_ADDR_LENGTH.load(&deps.storage).unwrap(), 20); - let nft_contract_and_class_id_list = query_nft_contracts(deps.as_ref(), None, None).unwrap(); - assert_eq!(nft_contract_and_class_id_list.len(), 2); - assert_eq!(nft_contract_and_class_id_list[0].0, CLASS_ID_1); - assert_eq!(nft_contract_and_class_id_list[0].1, NFT_CONTRACT_1); - assert_eq!(nft_contract_and_class_id_list[1].0, CLASS_ID_2); - assert_eq!(nft_contract_and_class_id_list[1].1, NFT_CONTRACT_2); - // test query and indexers for class id and addr are working - let nft_contract_1 = - query_nft_contract_for_class_id(&deps.storage, CLASS_ID_1.to_string().into()).unwrap(); - assert_eq!(nft_contract_1, Some(Addr::unchecked(NFT_CONTRACT_1))); - let nft_contract_2 = - query_nft_contract_for_class_id(&deps.storage, CLASS_ID_2.to_string().into()).unwrap(); - assert_eq!(nft_contract_2, Some(Addr::unchecked(NFT_CONTRACT_2))); - let class_id_1 = - query_class_id_for_nft_contract(deps.as_ref(), NFT_CONTRACT_1.to_string()).unwrap(); - assert_eq!(class_id_1, Some(ClassId::new(CLASS_ID_1))); - let class_id_2 = - query_class_id_for_nft_contract(deps.as_ref(), NFT_CONTRACT_2.to_string()).unwrap(); - assert_eq!(class_id_2, Some(ClassId::new(CLASS_ID_2))); -} diff --git a/packages/ics721/src/testing/ibc_tests.rs b/packages/ics721/src/testing/ibc_tests.rs index 32313df0..4dba5829 100644 --- a/packages/ics721/src/testing/ibc_tests.rs +++ b/packages/ics721/src/testing/ibc_tests.rs @@ -588,7 +588,6 @@ fn test_ibc_packet_receive_emits_memo() { let res = Ics721Contract::default() .ibc_packet_receive(deps.as_mut(), env, packet) .unwrap(); - println!(">>>>>>>>>>> memo: {:?}", res.attributes); assert!(res.attributes.contains(&Attribute { key: "ics721_memo".to_string(), value: "memo".to_string() diff --git a/packages/ics721/src/testing/integration_tests.rs b/packages/ics721/src/testing/integration_tests.rs index bda6f06b..5a784336 100644 --- a/packages/ics721/src/testing/integration_tests.rs +++ b/packages/ics721/src/testing/integration_tests.rs @@ -1,13 +1,21 @@ use anyhow::Result; -use bech32::{decode, encode, FromBase32, ToBase32, Variant}; +use bech32::{decode, encode, Hrp}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - from_json, instantiate2_address, to_json_binary, Addr, Api, Binary, CanonicalAddr, Deps, - DepsMut, Empty, Env, GovMsg, IbcTimeout, IbcTimeoutBlock, MemoryStorage, MessageInfo, - RecoverPubkeyError, Reply, Response, StdError, StdResult, Storage, VerificationError, WasmMsg, + from_json, instantiate2_address, to_json_binary, Addr, Api, Binary, CanonicalAddr, Decimal, + Deps, DepsMut, Empty, Env, GovMsg, IbcTimeout, IbcTimeoutBlock, MemoryStorage, MessageInfo, + RecoverPubkeyError, Reply, Response, StdError, StdResult, Storage, Timestamp, + VerificationError, WasmMsg, }; use cw2::set_contract_version; -use cw721_base::msg::{InstantiateMsg as Cw721InstantiateMsg, QueryMsg as Cw721QueryMsg}; +use cw721::{ + msg::{CollectionExtensionMsg, CollectionInfoAndExtensionResponse, RoyaltyInfoResponse}, + CollectionExtension, DefaultOptionalCollectionExtension, DefaultOptionalNftExtension, + RoyaltyInfo, +}; +use cw721_metadata_onchain::msg::{ + InstantiateMsg as Cw721InstantiateMsg, QueryMsg as Cw721QueryMsg, +}; use cw_cii::{Admin, ContractInstantiateInfo}; use cw_multi_test::{ AddressGenerator, App, AppBuilder, BankKeeper, Contract, ContractWrapper, DistributionKeeper, @@ -30,7 +38,7 @@ use ics721_types::{ token_types::{Class, ClassId, Token, TokenId}, }; -use super::contract::Ics721Contract; +use super::unit_tests::Ics721Contract; const ICS721_CREATOR: &str = "ics721-creator"; const ICS721_ADMIN: &str = "ics721-admin"; @@ -159,12 +167,14 @@ impl MockAddressGenerator { } } pub struct MockApiBech32 { - prefix: &'static str, + prefix: Hrp, } impl MockApiBech32 { pub fn new(prefix: &'static str) -> Self { - Self { prefix } + Self { + prefix: Hrp::parse(prefix).unwrap(), + } } } @@ -182,22 +192,18 @@ impl Api for MockApiBech32 { } fn addr_canonicalize(&self, input: &str) -> StdResult { - if let Ok((prefix, decoded, Variant::Bech32)) = decode(input) { + if let Ok((prefix, decoded)) = decode(input) { if prefix == self.prefix { - if let Ok(bytes) = Vec::::from_base32(&decoded) { - return Ok(bytes.into()); - } + return Ok(decoded.into()); } } Err(StdError::generic_err(format!("Invalid input: {input}"))) } fn addr_humanize(&self, canonical: &CanonicalAddr) -> StdResult { - if let Ok(encoded) = encode( - self.prefix, - canonical.as_slice().to_base32(), - Variant::Bech32, - ) { + let hrp = self.prefix; + let data = canonical.as_slice(); + if let Ok(encoded) = encode::(hrp, data) { Ok(Addr::unchecked(encoded)) } else { Err(StdError::generic_err("Invalid canonical address")) @@ -248,7 +254,7 @@ impl Api for MockApiBech32 { impl MockApiBech32 { pub fn addr_make(&self, input: &str) -> Addr { let digest = Sha256::digest(input).to_vec(); - match encode(self.prefix, digest.to_base32(), Variant::Bech32) { + match encode::(self.prefix, &digest) { Ok(address) => Addr::unchecked(address), Err(reason) => panic!("Generating address failed with reason: {reason}"), } @@ -275,6 +281,7 @@ pub struct PartialCustomCollectionData { struct Test { app: MockApp, + admin_and_pauser: Option, // origin cw721 contract on source chain for interchain transfers to other target chains source_cw721_owner: Addr, source_cw721_id: u64, @@ -371,6 +378,18 @@ impl Test { &Cw721InstantiateMsg { name: "name".to_string(), symbol: "symbol".to_string(), + collection_info_extension: Some(CollectionExtensionMsg { + description: Some("description".to_string()), + explicit_content: Some(false), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + image: Some("https://ark.pass/image.png".to_string()), + royalty_info: Some(RoyaltyInfoResponse { + payment_address: source_cw721_owner.to_string(), + share: Decimal::bps(1000), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), + creator: None, minter: Some(source_cw721_owner.to_string()), withdraw_address: None, }, @@ -397,6 +416,7 @@ impl Test { Self { app, + admin_and_pauser: admin, source_cw721_owner, source_cw721_id, source_cw721, @@ -523,7 +543,7 @@ impl Test { .wrap() .query_wasm_smart( self.source_cw721.clone(), - &cw721_base::msg::QueryMsg::::AllNftInfo { + &cw721_metadata_onchain::msg::QueryMsg::AllNftInfo { token_id, include_expired: None, }, @@ -538,7 +558,7 @@ impl Test { .execute_contract( self.source_cw721_owner.clone(), self.source_cw721.clone(), - &cw721_base::msg::ExecuteMsg::::Mint { + &cw721_metadata_onchain::msg::ExecuteMsg::Mint { token_id: self.nfts_minted.to_string(), owner: owner.to_string(), token_uri: None, @@ -552,9 +572,9 @@ impl Test { fn cw721_base_contract() -> Box> { let contract = ContractWrapper::new( - cw721_base::entry::execute, - cw721_base::entry::instantiate, - cw721_base::entry::query, + cw721_metadata_onchain::entry::execute, + cw721_metadata_onchain::entry::instantiate, + cw721_metadata_onchain::entry::query, ); Box::new(contract) } @@ -636,6 +656,7 @@ fn test_do_instantiate_and_mint_weird_data() { "wasm.{}/{}/{}", test.ics721, CHANNEL_TARGET_CHAIN, collection_contract_source_chain ); + let collection_owner_addr = test.app.api().addr_make(COLLECTION_OWNER_SOURCE_CHAIN); test.app .execute_contract( test.ics721.clone(), @@ -650,14 +671,24 @@ fn test_do_instantiate_and_mint_weird_data() { to_json_binary(&CollectionData { owner: Some( // incoming collection data from source chain - test.app - .api() - .addr_make(COLLECTION_OWNER_SOURCE_CHAIN) - .to_string(), + collection_owner_addr.to_string(), ), contract_info: Default::default(), name: "name".to_string(), symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some( + "https://interchain.arkprotocol.io".to_string(), + ), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: collection_owner_addr, + share: Decimal::bps(1000), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), num_tokens: Some(1), }) .unwrap(), @@ -733,29 +764,31 @@ fn test_do_instantiate_and_mint() { .unwrap(); // check name and symbol contains class id for instantiated nft contract - let contract_info: cw721::ContractInfoResponse = test - .app - .wrap() - .query_wasm_smart( - nft_contract.clone(), - &Cw721QueryMsg::::ContractInfo {}, - ) - .unwrap(); + let contract_info: CollectionInfoAndExtensionResponse = + test.app + .wrap() + .query_wasm_smart( + nft_contract.clone(), + &Cw721QueryMsg::GetCollectionInfoAndExtension {}, + ) + .unwrap(); assert_eq!( contract_info, - cw721::ContractInfoResponse { + CollectionInfoAndExtensionResponse:: { name: class_id.to_string(), // name is set to class_id - symbol: class_id.to_string() // symbol is set to class_id + symbol: class_id.to_string(), // symbol is set to class_id + extension: None, + updated_at: contract_info.updated_at, } ); // Check that token_uri was set properly. - let token_info: cw721::NftInfoResponse = test + let token_info: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -764,12 +797,12 @@ fn test_do_instantiate_and_mint() { token_info.token_uri, Some("https://moonphase.is/image.svg".to_string()) ); - let token_info: cw721::NftInfoResponse = test + let token_info: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -781,7 +814,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), token_id: "1".to_string(), }, @@ -790,7 +823,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner: cw721::OwnerOfResponse = test + let owner: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -804,12 +837,12 @@ fn test_do_instantiate_and_mint() { assert_eq!(owner.owner, nft_contract.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner: cw721::OwnerOfResponse = test + let base_owner: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -819,13 +852,32 @@ fn test_do_instantiate_and_mint() { } // test case: instantiate cw721 with ClassData containing owner, name, and symbol { - let mut test = Test::new(false, false, None, None, cw721_base_contract(), true); + let mut test = Test::new( + false, + false, + None, + Some(COLLECTION_OWNER_SOURCE_CHAIN.to_string()), // admin is used for royalty payment address! + cw721_base_contract(), + true, + ); let collection_contract_source_chain = ClassId::new(test.app.api().addr_make(COLLECTION_CONTRACT_SOURCE_CHAIN)); let class_id = format!( "wasm.{}/{}/{}", test.ics721, CHANNEL_TARGET_CHAIN, collection_contract_source_chain ); + let collection_owner_addr = Addr::unchecked(test.admin_and_pauser.clone().unwrap()); + let collection_extension = Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: collection_owner_addr.clone(), + share: Decimal::bps(1000), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }); test.app .execute_contract( test.ics721.clone(), @@ -840,14 +892,12 @@ fn test_do_instantiate_and_mint() { to_json_binary(&CollectionData { owner: Some( // incoming collection data from source chain - test.app - .api() - .addr_make(COLLECTION_OWNER_SOURCE_CHAIN) - .to_string(), + collection_owner_addr.to_string(), ), contract_info: Default::default(), name: "ark".to_string(), symbol: "protocol".to_string(), + extension: collection_extension.clone(), num_tokens: Some(1), }) .unwrap(), @@ -887,29 +937,31 @@ fn test_do_instantiate_and_mint() { .unwrap(); // check name and symbol is using class data for instantiated nft contract - let contract_info: cw721::ContractInfoResponse = test - .app - .wrap() - .query_wasm_smart( - nft_contract.clone(), - &Cw721QueryMsg::::ContractInfo {}, - ) - .unwrap(); + let contract_info: CollectionInfoAndExtensionResponse = + test.app + .wrap() + .query_wasm_smart( + nft_contract.clone(), + &Cw721QueryMsg::GetCollectionInfoAndExtension {}, + ) + .unwrap(); assert_eq!( contract_info, - cw721::ContractInfoResponse { + CollectionInfoAndExtensionResponse { name: "ark".to_string(), - symbol: "protocol".to_string() + symbol: "protocol".to_string(), + extension: collection_extension, + updated_at: contract_info.updated_at, } ); // Check that token_uri was set properly. - let token_info: cw721::NftInfoResponse = test + let token_info: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -918,12 +970,12 @@ fn test_do_instantiate_and_mint() { token_info.token_uri, Some("https://moonphase.is/image.svg".to_string()) ); - let token_info: cw721::NftInfoResponse = test + let token_info: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -935,7 +987,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), // new recipient - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), token_id: "1".to_string(), }, @@ -944,7 +996,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner: cw721::OwnerOfResponse = test + let owner: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -958,12 +1010,12 @@ fn test_do_instantiate_and_mint() { assert_eq!(owner.owner, nft_contract.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner: cw721::OwnerOfResponse = test + let base_owner: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1040,29 +1092,31 @@ fn test_do_instantiate_and_mint() { .unwrap(); // check name and symbol contains class id for instantiated nft contract - let contract_info: cw721::ContractInfoResponse = test - .app - .wrap() - .query_wasm_smart( - nft_contract.clone(), - &Cw721QueryMsg::::ContractInfo {}, - ) - .unwrap(); + let contract_info: CollectionInfoAndExtensionResponse = + test.app + .wrap() + .query_wasm_smart( + nft_contract.clone(), + &Cw721QueryMsg::GetCollectionInfoAndExtension {}, + ) + .unwrap(); assert_eq!( contract_info, - cw721::ContractInfoResponse { + CollectionInfoAndExtensionResponse { name: class_id.to_string(), - symbol: class_id.to_string() + symbol: class_id.to_string(), + extension: None, + updated_at: contract_info.updated_at, } ); // Check that token_uri was set properly. - let token_info: cw721::NftInfoResponse = test + let token_info: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1071,12 +1125,12 @@ fn test_do_instantiate_and_mint() { token_info.token_uri, Some("https://moonphase.is/image.svg".to_string()) ); - let token_info: cw721::NftInfoResponse = test + let token_info: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1088,7 +1142,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), // new owner token_id: "1".to_string(), }, @@ -1097,7 +1151,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner: cw721::OwnerOfResponse = test + let owner: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -1112,12 +1166,12 @@ fn test_do_instantiate_and_mint() { assert_eq!(owner.owner, nft_contract.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner: cw721::OwnerOfResponse = test + let base_owner: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1197,29 +1251,31 @@ fn test_do_instantiate_and_mint() { .unwrap(); // check name and symbol contains class id for instantiated nft contract - let contract_info: cw721::ContractInfoResponse = test - .app - .wrap() - .query_wasm_smart( - nft_contract.clone(), - &Cw721QueryMsg::::ContractInfo {}, - ) - .unwrap(); + let contract_info: CollectionInfoAndExtensionResponse = + test.app + .wrap() + .query_wasm_smart( + nft_contract.clone(), + &Cw721QueryMsg::GetCollectionInfoAndExtension {}, + ) + .unwrap(); assert_eq!( contract_info, - cw721::ContractInfoResponse { + CollectionInfoAndExtensionResponse { name: "collection-name".to_string(), - symbol: "collection-symbol".to_string() + symbol: "collection-symbol".to_string(), + extension: None, + updated_at: contract_info.updated_at, } ); // Check that token_uri was set properly. - let token_info: cw721::NftInfoResponse = test + let token_info: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1228,12 +1284,12 @@ fn test_do_instantiate_and_mint() { token_info.token_uri, Some("https://moonphase.is/image.svg".to_string()) ); - let token_info: cw721::NftInfoResponse = test + let token_info: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1245,7 +1301,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), // new owner token_id: "1".to_string(), }, @@ -1254,7 +1310,7 @@ fn test_do_instantiate_and_mint() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner: cw721::OwnerOfResponse = test + let owner: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -1269,12 +1325,12 @@ fn test_do_instantiate_and_mint() { assert_eq!(owner.owner, nft_contract.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner: cw721::OwnerOfResponse = test + let base_owner: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1389,54 +1445,62 @@ fn test_do_instantiate_and_mint_2_different_collections() { .unwrap(); // check name and symbol contains class id for instantiated nft contract - let contract_info_1: cw721::ContractInfoResponse = test + let contract_info_1: CollectionInfoAndExtensionResponse< + DefaultOptionalCollectionExtension, + > = test .app .wrap() .query_wasm_smart( nft_contract_1.clone(), - &Cw721QueryMsg::::ContractInfo {}, + &Cw721QueryMsg::GetCollectionInfoAndExtension {}, ) .unwrap(); - let contract_info_2: cw721::ContractInfoResponse = test + let contract_info_2: CollectionInfoAndExtensionResponse< + DefaultOptionalCollectionExtension, + > = test .app .wrap() .query_wasm_smart( nft_contract_2.clone(), - &Cw721QueryMsg::::ContractInfo {}, + &Cw721QueryMsg::GetCollectionInfoAndExtension {}, ) .unwrap(); assert_eq!( contract_info_1, - cw721::ContractInfoResponse { + CollectionInfoAndExtensionResponse { name: class_id_1.to_string(), // name is set to class_id - symbol: class_id_1.to_string() // symbol is set to class_id + symbol: class_id_1.to_string(), // symbol is set to class_id + extension: None, + updated_at: contract_info_1.updated_at, } ); assert_eq!( contract_info_2, - cw721::ContractInfoResponse { + CollectionInfoAndExtensionResponse { name: class_id_2.to_string(), // name is set to class_id - symbol: class_id_2.to_string() // symbol is set to class_id + symbol: class_id_2.to_string(), // symbol is set to class_id + extension: None, + updated_at: contract_info_2.updated_at, } ); // Check that token_uri was set properly. - let token_info_1_1: cw721::NftInfoResponse = test + let token_info_1_1: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract_1.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) .unwrap(); - let token_info_2_1: cw721::NftInfoResponse = test + let token_info_2_1: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract_2.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1446,22 +1510,22 @@ fn test_do_instantiate_and_mint_2_different_collections() { Some("https://moonphase.is/image.svg".to_string()) ); assert_eq!(token_info_2_1.token_uri, Some("https://mr.t".to_string())); - let token_info_1_2: cw721::NftInfoResponse = test + let token_info_1_2: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract_1.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) .unwrap(); - let token_info_2_2: cw721::NftInfoResponse = test + let token_info_2_2: cw721::msg::NftInfoResponse = test .app .wrap() .query_wasm_smart( nft_contract_2.clone(), - &cw721::Cw721QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1480,7 +1544,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract_1.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract_1.to_string(), token_id: "1".to_string(), }, @@ -1491,7 +1555,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract_2.clone(), - &cw721_base::msg::ExecuteMsg::::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract_2.to_string(), token_id: "1".to_string(), }, @@ -1500,7 +1564,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .unwrap(); // ics721 owner query and check nft contract owns it - let owner_1: cw721::OwnerOfResponse = test + let owner_1: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -1511,7 +1575,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { }, ) .unwrap(); - let owner_2: cw721::OwnerOfResponse = test + let owner_2: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( @@ -1526,23 +1590,23 @@ fn test_do_instantiate_and_mint_2_different_collections() { assert_eq!(owner_2.owner, nft_contract_2.to_string()); // check cw721 owner query matches ics721 owner query - let base_owner_1: cw721::OwnerOfResponse = test + let base_owner_1: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract_1, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, ) .unwrap(); - let base_owner_2: cw721::OwnerOfResponse = test + let base_owner_2: cw721::msg::OwnerOfResponse = test .app .wrap() .query_wasm_smart( nft_contract_2, - &cw721::Cw721QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1565,6 +1629,7 @@ fn test_do_instantiate_and_mint_no_instantiate() { // Check calling CreateVouchers twice with same class id // on 2nd call it will not instantiate a new contract, // instead it will just mint the token on existing contract + let collection_owner_addr = test.app.api().addr_make(COLLECTION_OWNER_SOURCE_CHAIN); test.app .execute_contract( test.ics721.clone(), @@ -1579,14 +1644,24 @@ fn test_do_instantiate_and_mint_no_instantiate() { to_json_binary(&CollectionData { owner: Some( // incoming collection data from source chain - test.app - .api() - .addr_make(COLLECTION_OWNER_SOURCE_CHAIN) - .to_string(), + collection_owner_addr.to_string(), ), contract_info: Default::default(), name: "name".to_string(), symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some( + "https://interchain.arkprotocol.io".to_string(), + ), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: collection_owner_addr, + share: Decimal::bps(1000), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), num_tokens: Some(1), }) .unwrap(), @@ -1647,12 +1722,12 @@ fn test_do_instantiate_and_mint_no_instantiate() { .unwrap(); // Make sure we have our tokens. - let tokens: cw721::TokensResponse = test + let tokens: cw721::msg::TokensResponse = test .app .wrap() .query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::AllTokens { + &cw721_metadata_onchain::msg::QueryMsg::AllTokens { start_after: None, limit: None, }, @@ -1694,6 +1769,21 @@ fn test_do_instantiate_and_mint_permissions() { contract_info: Default::default(), name: "name".to_string(), symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some( + "https://interchain.arkprotocol.io".to_string(), + ), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: Addr::unchecked( + "payment_address".to_string(), + ), + share: Decimal::one(), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), num_tokens: Some(1), }) .unwrap(), @@ -1736,7 +1826,7 @@ fn test_no_proxy_unknown_msg() { .execute_contract( test.app.api().addr_make("proxy"), test.ics721, - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN).to_string(), token_id: "1".to_string(), msg: to_json_binary(&msg).unwrap(), @@ -1774,7 +1864,7 @@ fn test_no_proxy_unauthorized() { .execute_contract( test.app.api().addr_make("foo"), test.ics721, - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN).to_string(), token_id: "1".to_string(), msg: to_json_binary(&msg).unwrap(), @@ -1805,9 +1895,11 @@ fn test_proxy_authorized() { .instantiate_contract( source_cw721_id, test.app.api().addr_make("ekez"), - &cw721_base::InstantiateMsg { + &cw721_metadata_onchain::msg::InstantiateMsg { name: "token".to_string(), symbol: "nonfungible".to_string(), + collection_info_extension: None, + creator: None, minter: Some( test.app .api() @@ -1827,11 +1919,11 @@ fn test_proxy_authorized() { .execute_contract( test.app.api().addr_make(COLLECTION_OWNER_SOURCE_CHAIN), source_cw721.clone(), - &cw721_base::ExecuteMsg::::Mint { + &cw721_metadata_onchain::msg::ExecuteMsg::Mint { token_id: "1".to_string(), owner: test.ics721.to_string(), token_uri: None, - extension: Empty::default(), + extension: None, }, &[], ) @@ -1844,7 +1936,7 @@ fn test_proxy_authorized() { .execute_contract( proxy_address, test.ics721, - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test .app .api() @@ -1886,7 +1978,7 @@ fn test_receive_nft() { .execute_contract( test.source_cw721.clone(), test.ics721, - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test.source_cw721_owner.to_string(), token_id, msg: to_json_binary(&IbcOutgoingMsg { @@ -1905,11 +1997,11 @@ fn test_receive_nft() { .unwrap(); // get class data (containing collection data) from response - let event = res.events.into_iter().find(|e| e.ty == "wasm").unwrap(); - let class_data_attribute = event - .attributes + let event = res + .events + .clone() .into_iter() - .find(|a| a.key == "class_data") + .find(|e| e.ty == "wasm") .unwrap(); // check collection data matches with data from source nft contract let expected_contract_info: cosmwasm_std::ContractInfoResponse = @@ -1933,9 +2025,25 @@ fn test_receive_nft() { contract_info: Some(expected_contract_info), name: "name".to_string(), symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: test.source_cw721_owner.clone(), + share: Decimal::bps(1000), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), num_tokens: Some(1), }) .unwrap(); + let class_data_attribute = event + .attributes + .into_iter() + .find(|a| a.key == "class_data") + .unwrap(); assert_eq!( class_data_attribute.value, format!("{expected_collection_data:?}") @@ -1954,7 +2062,7 @@ fn test_receive_nft() { .execute_contract( test.source_cw721.clone(), test.ics721, - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test.source_cw721_owner.to_string(), token_id, msg: to_json_binary(&IbcOutgoingMsg { @@ -2004,6 +2112,7 @@ fn test_receive_nft() { contract_info: Some(expected_contract_info), name: "name".to_string(), symbol: "symbol".to_string(), + extension: None, num_tokens: Some(1), }) .unwrap(); @@ -2035,7 +2144,7 @@ fn test_admin_clean_and_unescrow_nft() { .execute_contract( test.source_cw721.clone(), test.ics721.clone(), - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test.source_cw721_owner.to_string(), token_id: token_id_escrowed_by_ics721.clone(), msg: to_json_binary(&IbcOutgoingMsg { @@ -2169,7 +2278,7 @@ fn test_no_receive_with_proxy() { .execute_contract( test.app.api().addr_make("cw721"), test.ics721, - &ExecuteMsg::ReceiveNft(cw721::Cw721ReceiveMsg { + &ExecuteMsg::ReceiveNft(cw721::receiver::Cw721ReceiveMsg { sender: test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN).to_string(), token_id: "1".to_string(), msg: to_json_binary(&IbcOutgoingMsg { diff --git a/packages/ics721/src/testing/mod.rs b/packages/ics721/src/testing/mod.rs index 7d31de9a..b89c6082 100644 --- a/packages/ics721/src/testing/mod.rs +++ b/packages/ics721/src/testing/mod.rs @@ -1,3 +1,3 @@ -mod contract; mod ibc_tests; pub mod integration_tests; +mod unit_tests; diff --git a/packages/ics721/src/testing/unit_tests.rs b/packages/ics721/src/testing/unit_tests.rs new file mode 100644 index 00000000..25986327 --- /dev/null +++ b/packages/ics721/src/testing/unit_tests.rs @@ -0,0 +1,1315 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + from_json, + testing::{mock_dependencies, mock_env, mock_info, MockQuerier, MOCK_CONTRACT_ADDR}, + to_json_binary, Addr, ContractResult, CosmosMsg, Decimal, DepsMut, Empty, IbcMsg, IbcTimeout, + Order, QuerierResult, Response, StdResult, SubMsg, Timestamp, WasmMsg, WasmQuery, +}; +use cw721::{ + msg::{ + AllNftInfoResponse, CollectionInfoAndExtensionResponse, NftExtensionMsg, NftInfoResponse, + NumTokensResponse, + }, + CollectionExtension, DefaultOptionalCollectionExtension, DefaultOptionalNftExtension, + NftExtension, RoyaltyInfo, +}; +use cw721_metadata_onchain::msg::QueryMsg; +use cw_cii::ContractInstantiateInfo; +use cw_ownable::Ownership; +use cw_storage_plus::Map; +use serde::{Deserialize, Serialize}; + +use crate::{ + execute::Ics721Execute, + ibc::{Ics721Ibc, INSTANTIATE_INCOMING_PROXY_REPLY_ID, INSTANTIATE_OUTGOING_PROXY_REPLY_ID}, + msg::{InstantiateMsg, MigrateMsg}, + query::{ + query_class_id_for_nft_contract, query_nft_contract_for_class_id, query_nft_contracts, + Ics721Query, + }, + state::{ + ClassIdInfo, CollectionData, CLASS_ID_AND_NFT_CONTRACT_INFO, CLASS_ID_TO_CLASS, + CONTRACT_ADDR_LENGTH, CW721_ADMIN, CW721_CODE_ID, IBC_RECEIVE_TOKEN_METADATA, + INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, + }, + utils::get_collection_data, +}; +use ics721_types::{ + ibc_types::{IbcOutgoingMsg, NonFungibleTokenPacketData}, + token_types::{ClassId, Token, TokenId}, +}; + +const NFT_CONTRACT_1: &str = "nft1"; +const NFT_CONTRACT_2: &str = "nft2"; +const CLASS_ID_1: &str = "some/class/id1"; +const CLASS_ID_2: &str = "some/class/id2"; +const OWNER_ADDR: &str = "owner"; +const ADMIN_ADDR: &str = "admin"; +const PAUSER_ADDR: &str = "pauser"; + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct UnknownMetadata { + pub unknown: String, +} + +#[derive(Default)] +pub struct Ics721Contract {} +impl Ics721Execute for Ics721Contract { + type ClassData = CollectionData; + + fn get_class_data(&self, deps: &DepsMut, sender: &Addr) -> StdResult> { + get_collection_data(deps, sender).map(Option::Some) + } +} +impl Ics721Ibc for Ics721Contract {} +impl Ics721Query for Ics721Contract {} + +#[derive(Default)] +pub struct Ics721ContractNoClassData {} +impl Ics721Execute for Ics721ContractNoClassData { + type ClassData = CollectionData; + + fn get_class_data( + &self, + _deps: &DepsMut, + _sender: &Addr, + ) -> StdResult> { + Ok(None) + } +} +impl Ics721Ibc for Ics721ContractNoClassData {} +impl Ics721Query for Ics721ContractNoClassData {} + +// copy of cosmwasm_std::ContractInfoResponse (marked as non-exhaustive) +#[cw_serde] +pub struct ContractInfoResponse { + pub code_id: u64, + /// address that instantiated this contract + pub creator: String, + /// admin who can run migrations (if any) + pub admin: Option, + /// if set, the contract is pinned to the cache, and thus uses less gas when called + pub pinned: bool, + /// set if this contract has bound an IBC port + pub ibc_port: Option, +} + +fn mock_querier(query: &WasmQuery) -> QuerierResult { + match query { + cosmwasm_std::WasmQuery::Smart { + contract_addr: _, + msg, + } => match from_json::(&msg).unwrap() { + QueryMsg::GetMinterOwnership {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&Ownership:: { + owner: Some(Addr::unchecked(OWNER_ADDR)), + pending_owner: None, + pending_expiry: None, + }) + .unwrap(), + )), + QueryMsg::GetCreatorOwnership {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&Ownership:: { + owner: Some(Addr::unchecked(OWNER_ADDR)), + pending_owner: None, + pending_expiry: None, + }) + .unwrap(), + )), + QueryMsg::AllNftInfo { .. } => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&AllNftInfoResponse:: { + access: cw721::msg::OwnerOfResponse { + owner: MOCK_CONTRACT_ADDR.to_string(), + approvals: vec![], + }, + info: NftInfoResponse { + token_uri: Some("https://moonphase.is/image.svg".to_string()), + extension: Some(NftExtension { + image: Some("https://ark.pass/image.png".to_string()), + external_url: Some("https://interchain.arkprotocol.io".to_string()), + description: Some("description".to_string()), + ..Default::default() + }), + }, + }) + .unwrap(), + )), + #[allow(deprecated)] + QueryMsg::ContractInfo {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&CollectionInfoAndExtensionResponse::< + DefaultOptionalCollectionExtension, + > { + name: "name".to_string(), + symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: Addr::unchecked("payment_address".to_string()), + share: Decimal::one(), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), + updated_at: Timestamp::from_seconds(0), + }) + .unwrap(), + )), + QueryMsg::GetCollectionInfoAndExtension {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&CollectionInfoAndExtensionResponse::< + DefaultOptionalCollectionExtension, + > { + name: "name".to_string(), + symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: Addr::unchecked("payment_address".to_string()), + share: Decimal::one(), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), + updated_at: Timestamp::default(), + }) + .unwrap(), + )), + QueryMsg::NumTokens {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&NumTokensResponse { count: 1 }).unwrap(), + )), + _ => unimplemented!(), + }, + cosmwasm_std::WasmQuery::ContractInfo { .. } => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&ContractInfoResponse { + code_id: 0, + creator: "creator".to_string(), + admin: None, + pinned: false, + ibc_port: None, + }) + .unwrap(), + )), + _ => unimplemented!(), + } +} + +fn mock_querier_v018(query: &WasmQuery) -> QuerierResult { + use cw721_018 as cw721; + use cw721_base_018 as cw721_base; + use cw721_base_018::QueryMsg; + match query { + cosmwasm_std::WasmQuery::Smart { + contract_addr: _, + msg, + } => match from_json::>(&msg) { + Ok(msg) => match msg { + QueryMsg::Ownership {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&Ownership:: { + owner: Some(Addr::unchecked(OWNER_ADDR)), + pending_owner: None, + pending_expiry: None, + }) + .unwrap(), + )), + QueryMsg::AllNftInfo { .. } => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&cw721_018::AllNftInfoResponse::> { + access: cw721::OwnerOfResponse { + owner: MOCK_CONTRACT_ADDR.to_string(), + approvals: vec![], + }, + info: cw721_018::NftInfoResponse { + token_uri: Some("https://moonphase.is/image.svg".to_string()), + extension: None, + }, + }) + .unwrap(), + )), + QueryMsg::ContractInfo {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&cw721::ContractInfoResponse { + name: "name".to_string(), + symbol: "symbol".to_string(), + }) + .unwrap(), + )), + QueryMsg::NumTokens {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&NumTokensResponse { count: 1 }).unwrap(), + )), + _ => QuerierResult::Err(cosmwasm_std::SystemError::Unknown {}), // other queries not needed in tests + }, + _ => QuerierResult::Err(cosmwasm_std::SystemError::Unknown {}), // not a v018 query + }, + cosmwasm_std::WasmQuery::ContractInfo { .. } => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&ContractInfoResponse { + code_id: 0, + creator: "creator".to_string(), + admin: None, + pinned: false, + ibc_port: None, + }) + .unwrap(), + )), + _ => unimplemented!(), + } +} + +fn mock_querier_v017(query: &WasmQuery) -> QuerierResult { + use cw721_017 as cw721; + use cw721_base_017 as cw721_base; + use cw721_base_017::QueryMsg; + match query { + cosmwasm_std::WasmQuery::Smart { + contract_addr: _, + msg, + } => match from_json::>(&msg) { + Ok(msg) => match msg { + QueryMsg::Ownership {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&Ownership:: { + owner: Some(Addr::unchecked(OWNER_ADDR)), + pending_owner: None, + pending_expiry: None, + }) + .unwrap(), + )), + QueryMsg::AllNftInfo { .. } => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&cw721_017::AllNftInfoResponse::> { + access: cw721::OwnerOfResponse { + owner: MOCK_CONTRACT_ADDR.to_string(), + approvals: vec![], + }, + info: cw721_017::NftInfoResponse { + token_uri: Some("https://moonphase.is/image.svg".to_string()), + extension: None, + }, + }) + .unwrap(), + )), + QueryMsg::ContractInfo {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&cw721::ContractInfoResponse { + name: "name".to_string(), + symbol: "symbol".to_string(), + }) + .unwrap(), + )), + QueryMsg::NumTokens {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&NumTokensResponse { count: 1 }).unwrap(), + )), + _ => QuerierResult::Err(cosmwasm_std::SystemError::Unknown {}), // other queries not needed in tests + }, + _ => QuerierResult::Err(cosmwasm_std::SystemError::Unknown {}), // not a v018 query + }, + cosmwasm_std::WasmQuery::ContractInfo { .. } => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&ContractInfoResponse { + code_id: 0, + creator: "creator".to_string(), + admin: None, + pinned: false, + ibc_port: None, + }) + .unwrap(), + )), + _ => unimplemented!(), + } +} + +fn mock_querier_v016(query: &WasmQuery) -> QuerierResult { + match query { + cosmwasm_std::WasmQuery::Smart { + contract_addr: _, + msg, + } => match from_json::>(&msg) { + Ok(msg) => match msg { + // unwrap using latest (not old) cw721-base, since it is backwards compatible + cw721_base_016::QueryMsg::Minter {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary( + // return v016 response + &cw721_base_016::msg::MinterResponse { + minter: OWNER_ADDR.to_string(), + }, + ) + .unwrap(), + )), + cw721_base_016::QueryMsg::AllNftInfo { .. } => { + QuerierResult::Ok(ContractResult::Ok( + to_json_binary( + // return v016 response + &cw721_016::AllNftInfoResponse::> { + access: cw721_016::OwnerOfResponse { + owner: MOCK_CONTRACT_ADDR.to_string(), + approvals: vec![], + }, + info: cw721_016::NftInfoResponse { + token_uri: Some("https://moonphase.is/image.svg".to_string()), + extension: None, + }, + }, + ) + .unwrap(), + )) + } + cw721_base_016::QueryMsg::ContractInfo {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&cw721_016::ContractInfoResponse { + name: "name".to_string(), + symbol: "symbol".to_string(), + }) + .unwrap(), + )), + cw721_base_016::QueryMsg::NumTokens {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&cw721_016::NumTokensResponse { count: 1 }).unwrap(), + )), + _ => QuerierResult::Err(cosmwasm_std::SystemError::Unknown {}), // throws error for Ownership query + }, + _ => QuerierResult::Err(cosmwasm_std::SystemError::Unknown {}), // not a v016 query + }, + cosmwasm_std::WasmQuery::ContractInfo { .. } => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&ContractInfoResponse { + code_id: 0, + creator: "creator".to_string(), + admin: None, + pinned: false, + ibc_port: None, + }) + .unwrap(), + )), + _ => unimplemented!(), + } +} + +#[test] +fn test_receive_nft() { + let expected_contract_info: cosmwasm_std::ContractInfoResponse = from_json( + to_json_binary(&ContractInfoResponse { + code_id: 0, + creator: "creator".to_string(), + admin: None, + pinned: false, + ibc_port: None, + }) + .unwrap(), + ) + .unwrap(); + // test case: receive nft from cw721-base + { + let mut querier = MockQuerier::default(); + querier.update_wasm(mock_querier); + + let mut deps = mock_dependencies(); + deps.querier = querier; + let env = mock_env(); + + let info = mock_info(NFT_CONTRACT_1, &[]); + let token_id = "1"; + let sender = "ekez".to_string(); + let msg = to_json_binary(&IbcOutgoingMsg { + receiver: "callum".to_string(), + channel_id: "channel-1".to_string(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), + memo: None, + }) + .unwrap(); + + let res: cosmwasm_std::Response<_> = Ics721Contract::default() + .receive_nft( + deps.as_mut(), + env, + &info.sender, + TokenId::new(token_id), + sender.clone(), + msg, + ) + .unwrap(); + assert_eq!(res.messages.len(), 1); + + let channel_id = "channel-1".to_string(); + let sub_msg = res.messages[0].clone(); + match sub_msg.msg { + CosmosMsg::Ibc(IbcMsg::SendPacket { data, .. }) => { + let packet_data: NonFungibleTokenPacketData = from_json(data).unwrap(); + let class_data: CollectionData = + from_json(packet_data.class_data.clone().unwrap()).unwrap(); + let expected_class_data = CollectionData { + owner: Some(OWNER_ADDR.to_string()), + contract_info: Some(expected_contract_info.clone()), + name: "name".to_string(), + symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: Addr::unchecked("payment_address".to_string()), + share: Decimal::one(), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), + num_tokens: Some(1), + }; + assert_eq!(class_data, expected_class_data); + assert_eq!( + packet_data, + NonFungibleTokenPacketData { + class_id: ClassId::new(NFT_CONTRACT_1), + class_uri: None, + class_data: Some(to_json_binary(&expected_class_data).unwrap()), + token_ids: vec![TokenId::new(token_id)], + token_data: Some( + [to_json_binary(&NftExtension { + image: Some("https://ark.pass/image.png".to_string()), + external_url: Some("https://interchain.arkprotocol.io".to_string()), + description: Some("description".to_string()), + ..Default::default() + }) + .unwrap()] + .to_vec() + ), + token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), + sender, + receiver: "callum".to_string(), + memo: None, + } + ); + } + _ => panic!("unexpected message type"), + } + + // check outgoing classID and tokenID + let keys = OUTGOING_CLASS_TOKEN_TO_CHANNEL + .keys(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() + .unwrap(); + assert_eq!(keys, [(NFT_CONTRACT_1.to_string(), token_id.to_string())]); + + // check channel + let key = ( + ClassId::new(keys[0].clone().0), + TokenId::new(keys[0].clone().1), + ); + assert_eq!( + OUTGOING_CLASS_TOKEN_TO_CHANNEL + .load(deps.as_mut().storage, key) + .unwrap(), + channel_id + ) + } + // test case: receive nft with metadata from IBC_RECEIVE_TOKEN_METADATA storage + { + let mut querier = MockQuerier::default(); + querier.update_wasm(mock_querier); + + let mut deps = mock_dependencies(); + let token_id = "1"; + IBC_RECEIVE_TOKEN_METADATA + .save( + deps.as_mut().storage, + (ClassId::new(NFT_CONTRACT_1), TokenId::new(token_id)), + &Some(to_json_binary(&UnknownMetadata { + unknown: "unknown".to_string(), + })) + .transpose() + .unwrap(), + ) + .unwrap(); + deps.querier = querier; + let env = mock_env(); + + let info = mock_info(NFT_CONTRACT_1, &[]); + let sender = "ekez".to_string(); + let msg = to_json_binary(&IbcOutgoingMsg { + receiver: "callum".to_string(), + channel_id: "channel-1".to_string(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), + memo: None, + }) + .unwrap(); + + let res: cosmwasm_std::Response<_> = Ics721Contract::default() + .receive_nft( + deps.as_mut(), + env, + &info.sender, + TokenId::new(token_id), + sender.clone(), + msg, + ) + .unwrap(); + assert_eq!(res.messages.len(), 1); + + let channel_id = "channel-1".to_string(); + let sub_msg = res.messages[0].clone(); + match sub_msg.msg { + CosmosMsg::Ibc(IbcMsg::SendPacket { data, .. }) => { + let packet_data: NonFungibleTokenPacketData = from_json(data).unwrap(); + let class_data: CollectionData = + from_json(packet_data.class_data.clone().unwrap()).unwrap(); + let expected_class_data = CollectionData { + owner: Some(OWNER_ADDR.to_string()), + contract_info: Some(expected_contract_info.clone()), + name: "name".to_string(), + symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: Addr::unchecked("payment_address".to_string()), + share: Decimal::one(), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), + num_tokens: Some(1), + }; + assert_eq!(class_data, expected_class_data); + assert_eq!( + packet_data, + NonFungibleTokenPacketData { + class_id: ClassId::new(NFT_CONTRACT_1), + class_uri: None, + class_data: Some(to_json_binary(&expected_class_data).unwrap()), + token_ids: vec![TokenId::new(token_id)], + token_data: Some( + [to_json_binary(&UnknownMetadata { + unknown: "unknown".to_string(), + }) + .unwrap()] + .to_vec() + ), + token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), + sender, + receiver: "callum".to_string(), + memo: None, + } + ); + } + _ => panic!("unexpected message type"), + } + + // check outgoing classID and tokenID + let keys = OUTGOING_CLASS_TOKEN_TO_CHANNEL + .keys(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() + .unwrap(); + assert_eq!(keys, [(NFT_CONTRACT_1.to_string(), token_id.to_string())]); + + // check channel + let key = ( + ClassId::new(keys[0].clone().0), + TokenId::new(keys[0].clone().1), + ); + assert_eq!( + OUTGOING_CLASS_TOKEN_TO_CHANNEL + .load(deps.as_mut().storage, key) + .unwrap(), + channel_id + ) + } + // test case: receive nft from old/v016 cw721-base + { + let mut querier = MockQuerier::default(); + querier.update_wasm(mock_querier_v016); + + let mut deps = mock_dependencies(); + deps.querier = querier; + let env = mock_env(); + + let info = mock_info(NFT_CONTRACT_1, &[]); + let token_id = "1"; + let sender = "ekez".to_string(); + let msg = to_json_binary(&IbcOutgoingMsg { + receiver: "callum".to_string(), + channel_id: "channel-1".to_string(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), + memo: None, + }) + .unwrap(); + + let res: cosmwasm_std::Response<_> = Ics721Contract::default() + .receive_nft( + deps.as_mut(), + env, + &info.sender, + TokenId::new(token_id), + sender.clone(), + msg, + ) + .unwrap(); + assert_eq!(res.messages.len(), 1); + + let channel_id = "channel-1".to_string(); + let sub_msg = res.messages[0].clone(); + match sub_msg.msg { + CosmosMsg::Ibc(IbcMsg::SendPacket { data, .. }) => { + let packet_data: NonFungibleTokenPacketData = from_json(data).unwrap(); + let class_data: CollectionData = + from_json(packet_data.class_data.clone().unwrap()).unwrap(); + let expected_class_data = CollectionData { + owner: Some(OWNER_ADDR.to_string()), + contract_info: Some(expected_contract_info.clone()), + name: "name".to_string(), + symbol: "symbol".to_string(), + extension: None, + num_tokens: Some(1), + }; + assert_eq!(class_data, expected_class_data); + assert_eq!( + packet_data, + NonFungibleTokenPacketData { + class_id: ClassId::new(NFT_CONTRACT_1), + class_uri: None, + class_data: Some(to_json_binary(&expected_class_data).unwrap()), + token_ids: vec![TokenId::new(token_id)], + token_data: None, + token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), + sender, + receiver: "callum".to_string(), + memo: None, + } + ); + } + _ => panic!("unexpected message type"), + } + + // check outgoing classID and tokenID + let keys = OUTGOING_CLASS_TOKEN_TO_CHANNEL + .keys(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() + .unwrap(); + assert_eq!(keys, [(NFT_CONTRACT_1.to_string(), token_id.to_string())]); + + // check channel + let key = ( + ClassId::new(keys[0].clone().0), + TokenId::new(keys[0].clone().1), + ); + assert_eq!( + OUTGOING_CLASS_TOKEN_TO_CHANNEL + .load(deps.as_mut().storage, key) + .unwrap(), + channel_id + ) + } + // test case: receive nft from old/v017 cw721-base + { + let mut querier = MockQuerier::default(); + querier.update_wasm(mock_querier_v017); + + let mut deps = mock_dependencies(); + deps.querier = querier; + let env = mock_env(); + + let info = mock_info(NFT_CONTRACT_1, &[]); + let token_id = "1"; + let sender = "ekez".to_string(); + let msg = to_json_binary(&IbcOutgoingMsg { + receiver: "callum".to_string(), + channel_id: "channel-1".to_string(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), + memo: None, + }) + .unwrap(); + + let res: cosmwasm_std::Response<_> = Ics721Contract::default() + .receive_nft( + deps.as_mut(), + env, + &info.sender, + TokenId::new(token_id), + sender.clone(), + msg, + ) + .unwrap(); + assert_eq!(res.messages.len(), 1); + + let channel_id = "channel-1".to_string(); + let sub_msg = res.messages[0].clone(); + match sub_msg.msg { + CosmosMsg::Ibc(IbcMsg::SendPacket { data, .. }) => { + let packet_data: NonFungibleTokenPacketData = from_json(data).unwrap(); + let class_data: CollectionData = + from_json(packet_data.class_data.clone().unwrap()).unwrap(); + let expected_class_data = CollectionData { + owner: Some(OWNER_ADDR.to_string()), + contract_info: Some(expected_contract_info.clone()), + name: "name".to_string(), + symbol: "symbol".to_string(), + extension: None, + num_tokens: Some(1), + }; + assert_eq!(class_data, expected_class_data); + assert_eq!( + packet_data, + NonFungibleTokenPacketData { + class_id: ClassId::new(NFT_CONTRACT_1), + class_uri: None, + class_data: Some(to_json_binary(&expected_class_data).unwrap()), + token_ids: vec![TokenId::new(token_id)], + token_data: None, + token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), + sender, + receiver: "callum".to_string(), + memo: None, + } + ); + } + _ => panic!("unexpected message type"), + } + + // check outgoing classID and tokenID + let keys = OUTGOING_CLASS_TOKEN_TO_CHANNEL + .keys(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() + .unwrap(); + assert_eq!(keys, [(NFT_CONTRACT_1.to_string(), token_id.to_string())]); + + // check channel + let key = ( + ClassId::new(keys[0].clone().0), + TokenId::new(keys[0].clone().1), + ); + assert_eq!( + OUTGOING_CLASS_TOKEN_TO_CHANNEL + .load(deps.as_mut().storage, key) + .unwrap(), + channel_id + ) + } + // test case: receive nft from old/v018 cw721-base + { + let mut querier = MockQuerier::default(); + querier.update_wasm(mock_querier_v018); + + let mut deps = mock_dependencies(); + deps.querier = querier; + let env = mock_env(); + + let info = mock_info(NFT_CONTRACT_1, &[]); + let token_id = "1"; + let sender = "ekez".to_string(); + let msg = to_json_binary(&IbcOutgoingMsg { + receiver: "callum".to_string(), + channel_id: "channel-1".to_string(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), + memo: None, + }) + .unwrap(); + + let res: cosmwasm_std::Response<_> = Ics721Contract::default() + .receive_nft( + deps.as_mut(), + env, + &info.sender, + TokenId::new(token_id), + sender.clone(), + msg, + ) + .unwrap(); + assert_eq!(res.messages.len(), 1); + + let channel_id = "channel-1".to_string(); + let sub_msg = res.messages[0].clone(); + match sub_msg.msg { + CosmosMsg::Ibc(IbcMsg::SendPacket { data, .. }) => { + let packet_data: NonFungibleTokenPacketData = from_json(data).unwrap(); + let class_data: CollectionData = + from_json(packet_data.class_data.clone().unwrap()).unwrap(); + let expected_class_data = CollectionData { + owner: Some(OWNER_ADDR.to_string()), + contract_info: Some(expected_contract_info.clone()), + name: "name".to_string(), + symbol: "symbol".to_string(), + extension: None, + num_tokens: Some(1), + }; + assert_eq!(class_data, expected_class_data); + assert_eq!( + packet_data, + NonFungibleTokenPacketData { + class_id: ClassId::new(NFT_CONTRACT_1), + class_uri: None, + class_data: Some(to_json_binary(&expected_class_data).unwrap()), + token_ids: vec![TokenId::new(token_id)], + token_data: None, + token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), + sender, + receiver: "callum".to_string(), + memo: None, + } + ); + } + _ => panic!("unexpected message type"), + } + + // check outgoing classID and tokenID + let keys = OUTGOING_CLASS_TOKEN_TO_CHANNEL + .keys(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() + .unwrap(); + assert_eq!(keys, [(NFT_CONTRACT_1.to_string(), token_id.to_string())]); + + // check channel + let key = ( + ClassId::new(keys[0].clone().0), + TokenId::new(keys[0].clone().1), + ); + assert_eq!( + OUTGOING_CLASS_TOKEN_TO_CHANNEL + .load(deps.as_mut().storage, key) + .unwrap(), + channel_id + ) + } + // test case: receive nft with no class data + { + let mut querier = MockQuerier::default(); + querier.update_wasm(mock_querier_v016); + + let mut deps = mock_dependencies(); + deps.querier = querier; + let env = mock_env(); + + let info = mock_info(NFT_CONTRACT_1, &[]); + let token_id = "1"; + let sender = "ekez".to_string(); + let msg = to_json_binary(&IbcOutgoingMsg { + receiver: "callum".to_string(), + channel_id: "channel-1".to_string(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), + memo: None, + }) + .unwrap(); + + let res: cosmwasm_std::Response<_> = Ics721ContractNoClassData::default() + .receive_nft( + deps.as_mut(), + env, + &info.sender, + TokenId::new(token_id), + sender.clone(), + msg, + ) + .unwrap(); + assert_eq!(res.messages.len(), 1); + + let channel_id = "channel-1".to_string(); + assert_eq!( + res.messages[0], + SubMsg::new(CosmosMsg::::Ibc(IbcMsg::SendPacket { + channel_id: channel_id.clone(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_seconds(42)), + data: to_json_binary(&NonFungibleTokenPacketData { + class_id: ClassId::new(NFT_CONTRACT_1), + class_uri: None, + class_data: None, + token_data: None, + token_ids: vec![TokenId::new(token_id)], + token_uris: Some(vec!["https://moonphase.is/image.svg".to_string()]), + sender, + receiver: "callum".to_string(), + memo: None, + }) + .unwrap() + })) + ); + + // check outgoing classID and tokenID + let keys = OUTGOING_CLASS_TOKEN_TO_CHANNEL + .keys(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() + .unwrap(); + assert_eq!(keys, [(NFT_CONTRACT_1.to_string(), token_id.to_string())]); + + // check channel + let key = ( + ClassId::new(keys[0].clone().0), + TokenId::new(keys[0].clone().1), + ); + assert_eq!( + OUTGOING_CLASS_TOKEN_TO_CHANNEL + .load(deps.as_mut().storage, key) + .unwrap(), + channel_id + ) + } +} + +#[test] +fn test_callback_mint() { + // test case: token data is NftExtension + { + let mut querier = MockQuerier::default(); + querier.update_wasm(mock_querier); + + let mut deps = mock_dependencies(); + deps.querier = querier; + let class_id_info = ClassIdInfo { + class_id: ClassId::new(NFT_CONTRACT_1), + address: Addr::unchecked(NFT_CONTRACT_1), + }; + CLASS_ID_AND_NFT_CONTRACT_INFO + .save( + deps.as_mut().storage, + &ClassId::new(NFT_CONTRACT_1), + &class_id_info, + ) + .unwrap(); + + let token_id = "1"; + let token = Token { + id: TokenId::new(token_id), + uri: None, + data: Some(to_json_binary(&NftExtension { + image: Some("https://ark.pass/image.png".to_string()), + external_url: Some("https://interchain.arkprotocol.io".to_string()), + description: Some("description".to_string()), + ..Default::default() + })) + .transpose() + .unwrap(), + }; + let res: cosmwasm_std::Response<_> = Ics721Contract::default() + .callback_mint( + deps.as_mut(), + ClassId::new(NFT_CONTRACT_1), + vec![token], + "receiver".to_string(), + ) + .unwrap(); + assert_eq!(res.messages.len(), 1); + // get mint message + let sub_msg = res.messages[0].clone(); + match sub_msg.msg { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr, msg, .. + }) => { + assert_eq!(contract_addr, NFT_CONTRACT_1); + let msg: cw721_metadata_onchain::msg::ExecuteMsg = from_json(msg).unwrap(); + match msg { + cw721_metadata_onchain::msg::ExecuteMsg::Mint { + token_id, + token_uri, + owner, + extension, + } => { + assert_eq!(token_id, "1"); + assert_eq!(token_uri, None); + assert_eq!(owner, "receiver"); + assert_eq!( + extension, + Some(NftExtensionMsg { + image: Some("https://ark.pass/image.png".to_string()), + external_url: Some("https://interchain.arkprotocol.io".to_string()), + description: Some("description".to_string()), + ..Default::default() + }) + ); + } + _ => panic!("unexpected message type"), + } + } + _ => panic!("unexpected message type"), + } + } + // test case: token data is unknown + { + let mut querier = MockQuerier::default(); + querier.update_wasm(mock_querier); + + let mut deps = mock_dependencies(); + deps.querier = querier; + let class_id_info = ClassIdInfo { + class_id: ClassId::new(NFT_CONTRACT_1), + address: Addr::unchecked(NFT_CONTRACT_1), + }; + CLASS_ID_AND_NFT_CONTRACT_INFO + .save( + deps.as_mut().storage, + &ClassId::new(NFT_CONTRACT_1), + &class_id_info, + ) + .unwrap(); + + let token_id = "1"; + let token = Token { + id: TokenId::new(token_id), + uri: None, + data: Some(to_json_binary(&UnknownMetadata { + unknown: "unknown".to_string(), + })) + .transpose() + .unwrap(), + }; + let res: cosmwasm_std::Response<_> = Ics721Contract::default() + .callback_mint( + deps.as_mut(), + ClassId::new(NFT_CONTRACT_1), + vec![token], + "receiver".to_string(), + ) + .unwrap(); + assert_eq!(res.messages.len(), 1); + // get mint message + let sub_msg = res.messages[0].clone(); + match sub_msg.msg { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr, msg, .. + }) => { + assert_eq!(contract_addr, NFT_CONTRACT_1); + let msg: cw721_metadata_onchain::msg::ExecuteMsg = from_json(msg).unwrap(); + match msg { + cw721_metadata_onchain::msg::ExecuteMsg::Mint { + token_id, + token_uri, + owner, + extension, + } => { + assert_eq!(token_id, "1"); + assert_eq!(token_uri, None); + assert_eq!(owner, "receiver"); + assert_eq!(extension, None); + } + _ => panic!("unexpected message type"), + } + } + _ => panic!("unexpected message type"), + } + } +} + +#[test] +fn test_receive_sets_uri() { + let mut querier = MockQuerier::default(); + querier.update_wasm(mock_querier); + + let mut deps = mock_dependencies(); + deps.querier = querier; + let env = mock_env(); + + let info = mock_info(NFT_CONTRACT_1, &[]); + let token_id = TokenId::new("1"); + let sender = "ekez".to_string(); + let msg = to_json_binary(&IbcOutgoingMsg { + receiver: "ekez".to_string(), + channel_id: "channel-1".to_string(), + timeout: IbcTimeout::with_timestamp(Timestamp::from_nanos(42)), + memo: None, + }) + .unwrap(); + + Ics721Contract {} + .receive_nft(deps.as_mut(), env, &info.sender, token_id, sender, msg) + .unwrap(); + + let class = CLASS_ID_TO_CLASS + .load(deps.as_ref().storage, ClassId::new(NFT_CONTRACT_1)) + .unwrap(); + assert_eq!(class.uri, None); + let expected_contract_info: cosmwasm_std::ContractInfoResponse = from_json( + to_json_binary(&ContractInfoResponse { + code_id: 0, + creator: "creator".to_string(), + admin: None, + pinned: false, + ibc_port: None, + }) + .unwrap(), + ) + .unwrap(); + assert_eq!( + class.data, + Some( + to_json_binary(&CollectionData { + owner: Some(OWNER_ADDR.to_string()), + contract_info: Some(expected_contract_info), + name: "name".to_string(), + symbol: "symbol".to_string(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some("https://interchain.arkprotocol.io".to_string()), + image: "https://ark.pass/image.png".to_string(), + royalty_info: Some(RoyaltyInfo { + payment_address: Addr::unchecked("payment_address".to_string()), + share: Decimal::one(), + }), + start_trading_time: Some(Timestamp::from_seconds(42)), + }), + num_tokens: Some(1), + }) + .unwrap() + ), + ); +} + +fn instantiate_msg( + incoming_proxy: Option, + outgoing_proxy: Option, +) -> InstantiateMsg { + InstantiateMsg { + cw721_base_code_id: 0, + incoming_proxy, + outgoing_proxy, + pauser: Some(PAUSER_ADDR.to_string()), + cw721_admin: Some(ADMIN_ADDR.to_string()), + contract_addr_length: None, + } +} + +#[test] +fn test_instantiate() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info(OWNER_ADDR, &[]); + let incoming_proxy_init_msg = ContractInstantiateInfo { + code_id: 0, + msg: to_json_binary("incoming").unwrap(), + admin: Some(cw_cii::Admin::Address { + addr: ADMIN_ADDR.to_string(), + }), + label: "incoming".to_string(), + }; + let outgoing_proxy_init_msg = ContractInstantiateInfo { + code_id: 0, + msg: to_json_binary("outgoing").unwrap(), + admin: Some(cw_cii::Admin::Address { + addr: ADMIN_ADDR.to_string(), + }), + label: "outgoing".to_string(), + }; + let mut msg = instantiate_msg( + Some(incoming_proxy_init_msg.clone()), + Some(outgoing_proxy_init_msg.clone()), + ); + msg.contract_addr_length = Some(20); + let response = Ics721Contract {} + .instantiate(deps.as_mut(), env.clone(), info, msg.clone()) + .unwrap(); + + let expected_incoming_proxy_msg = + incoming_proxy_init_msg.into_wasm_msg(env.clone().contract.address); + let expected_outgoing_proxy_msg = outgoing_proxy_init_msg.into_wasm_msg(env.contract.address); + let expected_response = Response::::default() + .add_submessage(SubMsg::reply_on_success( + expected_incoming_proxy_msg, + INSTANTIATE_INCOMING_PROXY_REPLY_ID, + )) + .add_submessage(SubMsg::reply_on_success( + expected_outgoing_proxy_msg, + INSTANTIATE_OUTGOING_PROXY_REPLY_ID, + )) + .add_attribute("method", "instantiate") + .add_attribute("cw721_code_id", msg.cw721_base_code_id.to_string()) + .add_attribute("cw721_admin", ADMIN_ADDR) + .add_attribute("contract_addr_length", "20"); + assert_eq!(response, expected_response); + assert_eq!(CW721_CODE_ID.load(&deps.storage).unwrap(), 0); + // incoming and outgoing proxy initially set to None and set later in sub msg + assert_eq!(OUTGOING_PROXY.load(&deps.storage).unwrap(), None); + assert_eq!(INCOMING_PROXY.load(&deps.storage).unwrap(), None); + assert_eq!( + PO.pauser.load(&deps.storage).unwrap(), + Some(Addr::unchecked(PAUSER_ADDR)) + ); + assert!(!PO.paused.load(&deps.storage).unwrap()); + assert_eq!( + CW721_ADMIN.load(&deps.storage).unwrap(), + Some(Addr::unchecked(ADMIN_ADDR.to_string())) + ); + assert_eq!(CONTRACT_ADDR_LENGTH.load(&deps.storage).unwrap(), 20); +} + +#[test] +fn test_migrate() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info(OWNER_ADDR, &[]); + let msg = instantiate_msg(None, None); + Ics721Contract {} + .instantiate(deps.as_mut(), env.clone(), info, msg.clone()) + .unwrap(); + let msg = MigrateMsg::WithUpdate { + pauser: Some("some_other_pauser".to_string()), + outgoing_proxy: Some("outgoing".to_string()), + incoming_proxy: Some("incoming".to_string()), + cw721_base_code_id: Some(1), + cw721_admin: Some("some_other_admin".to_string()), + contract_addr_length: Some(20), + }; + + // before migrate, populate legacy + let class_id_to_nft_contract: Map = Map::new("e"); + class_id_to_nft_contract + .save( + deps.as_mut().storage, + ClassId::new(CLASS_ID_1), + &Addr::unchecked(NFT_CONTRACT_1), + ) + .unwrap(); + class_id_to_nft_contract + .save( + deps.as_mut().storage, + ClassId::new(CLASS_ID_2), + &Addr::unchecked(NFT_CONTRACT_2), + ) + .unwrap(); + let nft_contract_to_class_id: Map = Map::new("f"); + nft_contract_to_class_id + .save( + deps.as_mut().storage, + Addr::unchecked(NFT_CONTRACT_1), + &ClassId::new(CLASS_ID_1), + ) + .unwrap(); + nft_contract_to_class_id + .save( + deps.as_mut().storage, + Addr::unchecked(NFT_CONTRACT_2), + &ClassId::new(CLASS_ID_2), + ) + .unwrap(); + + // migrate + Ics721Contract {} + .migrate(deps.as_mut(), env.clone(), msg) + .unwrap(); + + assert_eq!( + PO.pauser.load(&deps.storage).unwrap(), + Some(Addr::unchecked("some_other_pauser")) + ); + assert_eq!( + OUTGOING_PROXY.load(&deps.storage).unwrap(), + Some(Addr::unchecked("outgoing")) + ); + assert_eq!( + INCOMING_PROXY.load(&deps.storage).unwrap(), + Some(Addr::unchecked("incoming")) + ); + assert_eq!(CW721_CODE_ID.load(&deps.storage).unwrap(), 1); + assert_eq!( + CW721_ADMIN.load(&deps.storage).unwrap(), + Some(Addr::unchecked("some_other_admin")) + ); + assert_eq!(CONTRACT_ADDR_LENGTH.load(&deps.storage).unwrap(), 20); + let nft_contract_and_class_id_list = query_nft_contracts(deps.as_ref(), None, None).unwrap(); + assert_eq!(nft_contract_and_class_id_list.len(), 2); + assert_eq!(nft_contract_and_class_id_list[0].0, CLASS_ID_1); + assert_eq!(nft_contract_and_class_id_list[0].1, NFT_CONTRACT_1); + assert_eq!(nft_contract_and_class_id_list[1].0, CLASS_ID_2); + assert_eq!(nft_contract_and_class_id_list[1].1, NFT_CONTRACT_2); + // test query and indexers for class id and addr are working + let nft_contract_1 = + query_nft_contract_for_class_id(&deps.storage, CLASS_ID_1.to_string().into()).unwrap(); + assert_eq!(nft_contract_1, Some(Addr::unchecked(NFT_CONTRACT_1))); + let nft_contract_2 = + query_nft_contract_for_class_id(&deps.storage, CLASS_ID_2.to_string().into()).unwrap(); + assert_eq!(nft_contract_2, Some(Addr::unchecked(NFT_CONTRACT_2))); + let class_id_1 = + query_class_id_for_nft_contract(deps.as_ref(), NFT_CONTRACT_1.to_string()).unwrap(); + assert_eq!(class_id_1, Some(ClassId::new(CLASS_ID_1))); + let class_id_2 = + query_class_id_for_nft_contract(deps.as_ref(), NFT_CONTRACT_2.to_string()).unwrap(); + assert_eq!(class_id_2, Some(ClassId::new(CLASS_ID_2))); +} diff --git a/packages/ics721/src/utils.rs b/packages/ics721/src/utils.rs index 3a29e4e5..5ea0c2d5 100644 --- a/packages/ics721/src/utils.rs +++ b/packages/ics721/src/utils.rs @@ -1,52 +1,72 @@ use cosmwasm_std::{Addr, DepsMut, Empty, Env, StdResult}; -use cw721::NumTokensResponse; +use cw721::msg::NumTokensResponse; use cw_ownable::Ownership; use crate::state::{CollectionData, UniversalCollectionInfoResponse}; pub fn get_collection_data(deps: &DepsMut, collection: &Addr) -> StdResult { - // cw721 v0.17 and higher holds ownership in the contract - let ownership: StdResult> = deps - .querier - .query_wasm_smart(collection, &cw721_base::msg::QueryMsg::Ownership:: {}); - let owner = match ownership { + // cw721 v0.19 and higher holds creator ownership (cw-ownable storage) in the contract + let ownership_result: StdResult> = deps.querier.query_wasm_smart( + collection, + &cw721_metadata_onchain::msg::QueryMsg::GetCreatorOwnership {}, + ); + let owner = match ownership_result { Ok(ownership) => ownership.owner.map(|a| a.to_string()), Err(_) => { - // cw721 v0.16 and lower holds minter - let minter_response: cw721_base_016::msg::MinterResponse = deps - .querier - .query_wasm_smart(collection, &cw721_base_016::QueryMsg::Minter:: {})?; - deps.api.addr_validate(&minter_response.minter)?; - Some(minter_response.minter) + // cw721 v0.17 and v0.18 holds minter ownership (cw-ownable storage) in the contract + let ownership: StdResult> = deps.querier.query_wasm_smart( + collection, + &cw721_base_018::msg::QueryMsg::Ownership:: {}, // nb: could also use `GetMinterOwnership`, but some custom contracts may only know about `Ownership` + ); + match ownership { + Ok(ownership) => ownership.owner.map(|a| a.to_string()), + Err(_) => { + // cw721 v0.16 and lower holds minter (simple string storage) + let minter_response: cw721_base_016::msg::MinterResponse = + deps.querier.query_wasm_smart( + collection, + &cw721_base_016::QueryMsg::Minter:: {}, + )?; + deps.api.addr_validate(&minter_response.minter)?; + Some(minter_response.minter) + } + } } }; let contract_info = deps.querier.query_wasm_contract_info(collection)?; - let UniversalCollectionInfoResponse { name, symbol } = deps.querier.query_wasm_smart( + let UniversalCollectionInfoResponse { + name, + symbol, + extension, + updated_at: _, + } = deps.querier.query_wasm_smart( collection, - &cw721_base::msg::QueryMsg::::ContractInfo {}, + #[allow(deprecated)] + // For now we use `ContractInfo` which is known across all version, whilst `GetCollectionInfoAndExtension` is only available in v0.19 and higher + &cw721_metadata_onchain::msg::QueryMsg::ContractInfo {}, )?; let NumTokensResponse { count } = deps.querier.query_wasm_smart( collection, - &cw721_base::msg::QueryMsg::::NumTokens {}, + &cw721_metadata_onchain::msg::QueryMsg::NumTokens {}, )?; Ok(CollectionData { owner, contract_info: Some(contract_info), + num_tokens: Some(count), name, symbol, - num_tokens: Some(count), + extension, }) } /// Convert owner chain address (e.g. `juno1XXX`) to target owner chain address (e.g. `stars1XXX`). pub fn convert_owner_chain_address(env: &Env, source_owner: &str) -> StdResult { // convert the source owner (e.g. `juno1XXX`) to target owner (e.g. `stars1XXX`) - let (_source_hrp, source_data, source_variant) = bech32::decode(source_owner).unwrap(); + let (_source_hrp, source_data) = bech32::decode(source_owner).unwrap(); // detect target hrp (e.g. `stars`) using contract address - let (target_hrp, _target_data, _target_variant) = - bech32::decode(env.contract.address.as_str()).unwrap(); + let (target_hrp, _target_data) = bech32::decode(env.contract.address.as_str()).unwrap(); // convert source owner to target owner - let target_owner = bech32::encode(target_hrp.as_str(), source_data, source_variant).unwrap(); + let target_owner = bech32::encode::(target_hrp, &source_data).unwrap(); Ok(target_owner) } diff --git a/ts-relayer-tests/README.md b/ts-relayer-tests/README.md index de3630d9..52d70e97 100644 --- a/ts-relayer-tests/README.md +++ b/ts-relayer-tests/README.md @@ -75,7 +75,7 @@ npm run full-test **NOTE** If you modify your contract, you will need to recompile the contracts again, you can use `full-test` for that. ics721.spec.test uses a cw721-base binary build, stored at -`tests/internal/cw721_base_v0.18.0.wasm` ([cw-nfs](https://github.com/CosmWasm/cw-nfts/releases/tag/v0.18.0)). +`tests/internal/cw721_metadata_onchain_v0.19.0.wasm` ([cw-nfs](https://github.com/CosmWasm/cw-nfts/releases/tag/v0.18.0)). ### Run tests diff --git a/ts-relayer-tests/build.sh b/ts-relayer-tests/build.sh index ca76ecb4..391706e5 100755 --- a/ts-relayer-tests/build.sh +++ b/ts-relayer-tests/build.sh @@ -11,7 +11,7 @@ cd "$(git rev-parse --show-toplevel)" docker run --rm -v "$(pwd)":/code --platform linux/amd64 \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.15.1 + cosmwasm/workspace-optimizer:0.16.0 mkdir -p ./ts-relayer-tests/internal cp ./artifacts/*.wasm ./ts-relayer-tests/internal diff --git a/ts-relayer-tests/src/cw721-utils.ts b/ts-relayer-tests/src/cw721-utils.ts index ce447f00..04e1d5a5 100644 --- a/ts-relayer-tests/src/cw721-utils.ts +++ b/ts-relayer-tests/src/cw721-utils.ts @@ -10,7 +10,15 @@ export function mint( token_uri: string | undefined ) { const msg = { - mint: { token_id, owner, token_uri }, + mint: { + token_id, + owner, + token_uri, + extension: { + description: "This is a test NFT", + image: "https://ark.pass/image.png", + }, + }, }; return client.sign.execute( client.senderAddress, @@ -82,11 +90,29 @@ export function allTokens( return client.sign.queryContractSmart(cw721Contract, msg); } +/// valid for v16 - v19 export function nftInfo( client: CosmWasmSigner, cw721Contract: string, token_id: string -) { +): Promise<{ + token_uri: string | null; + extension: { + image: string | null; + image_data: string | null; + external_url: string | null; + description: string | null; + name: string | null; + attributes: null | Array<{ + trait_type: string; + value: string; + display_type: string | null; + }>; + background_color: string | null; + animation_url: string | null; + youtube_url: string | null; + }; +}> { const msg = { nft_info: { token_id, @@ -128,6 +154,84 @@ export function ownerOf( return client.sign.queryContractSmart(cw721Contract, msg); } +export function getCw721CollectionInfoAndExtension( + client: CosmWasmSigner, + cw721Contract: string +): Promise<{ + name: string; + symbol: string; + extension: { + description: string; + image: string; + external_link: string | null; + explicit_content: string | null; + start_trading_time: string | null; + royalty_info: { + payment_address: string; + share: string; + } | null; + } | null; + updated_at: "1723541397075433676"; +}> { + const msg = { + get_collection_info_and_extension: {}, + }; + return client.sign.queryContractSmart(cw721Contract, msg); +} + +export function getCw721ContractInfo_v16( + client: CosmWasmSigner, + cw721Contract: string +): Promise<{ + name: string; + symbol: string; +}> { + const msg = { + contract_info: {}, + }; + return client.sign.queryContractSmart(cw721Contract, msg); +} + +export function getCw721MinterOwnership( + client: CosmWasmSigner, + cw721Contract: string +): Promise<{ + owner: string; + pending_owner: string | null; + pending_expiry: string | null; +}> { + const msg = { + get_minter_ownership: {}, + }; + return client.sign.queryContractSmart(cw721Contract, msg); +} + +export function getCw721Minter_v16( + client: CosmWasmSigner, + cw721Contract: string +): Promise<{ + minter: string; +}> { + const msg = { + minter: {}, + }; + return client.sign.queryContractSmart(cw721Contract, msg); +} + +export function getCw721CreatorOwnership( + client: CosmWasmSigner, + cw721Contract: string +): Promise<{ + owner: string; + pending_owner: string | null; + pending_expiry: string | null; +}> { + const msg = { + get_creator_ownership: {}, + }; + return client.sign.queryContractSmart(cw721Contract, msg); +} + export function numTokens( client: CosmWasmSigner, cw721Contract: string diff --git a/ts-relayer-tests/src/ics721.spec.ts b/ts-relayer-tests/src/ics721.spec.ts index 746ff153..49448c3d 100644 --- a/ts-relayer-tests/src/ics721.spec.ts +++ b/ts-relayer-tests/src/ics721.spec.ts @@ -4,7 +4,19 @@ import anyTest, { ExecutionContext, TestFn } from "ava"; import { Order } from "cosmjs-types/ibc/core/channel/v1/channel"; import { instantiateContract } from "./controller"; -import { allTokens, approve, mint, ownerOf, sendNft } from "./cw721-utils"; +import { + allTokens, + approve, + getCw721CollectionInfoAndExtension, + getCw721ContractInfo_v16, + getCw721CreatorOwnership, + getCw721Minter_v16, + getCw721MinterOwnership, + mint, + nftInfo, + ownerOf, + sendNft, +} from "./cw721-utils"; import { adminCleanAndBurnNft, adminCleanAndUnescrowNft, @@ -36,12 +48,14 @@ interface TestContext { osmoAddr: string; wasmCw721: string; + wasmCw721_v16: string; wasmIcs721: string; wasmCw721IncomingProxyId: number; wasmCw721IncomingProxy: string; wasmCw721OutgoingProxy: string; osmoCw721: string; + osmoCw721_v16: string; osmoIcs721: string; osmoCw721IncomingProxy: string; osmoCw721OutgoingProxy: string; @@ -53,7 +67,8 @@ interface TestContext { const test = anyTest as TestFn; -const WASM_FILE_CW721 = "./internal/cw721_base_v0.18.0.wasm"; +const WASM_FILE_CW721 = "./internal/cw721_metadata_onchain_v0.19.0.wasm"; +const WASM_FILE_CW721_v16 = "./internal/cw721_metadata_onchain_v0.16.0.wasm"; const WASM_FILE_CW721_INCOMING_PROXY = "./internal/cw721_incoming_proxy.wasm"; const WASM_FILE_CW721_OUTGOING_PROXY = "./internal/cw721_outgoing_proxy_rate_limit.wasm"; @@ -73,8 +88,28 @@ const standardSetup = async (t: ExecutionContext) => { cw721: { path: WASM_FILE_CW721, instantiateMsg: { - name: "ark", - symbol: "ark", + name: "ark wasm", + symbol: "ark wasm", + collection_info_extension: { + description: "description", + image: "https://ark.pass/image.png", + external_link: "https://interchain.arkprotocol.io", + explicit_content: false, + royalty_info: { + payment_address: wasmClient.senderAddress, + share: "0.1", + }, + }, + creator: wasmClient.senderAddress, + minter: wasmClient.senderAddress, + withdraw_address: wasmClient.senderAddress, + }, + }, + cw721_v16: { + path: WASM_FILE_CW721_v16, + instantiateMsg: { + name: "ark wasm", + symbol: "ark wasm", minter: wasmClient.senderAddress, }, }, @@ -91,12 +126,33 @@ const standardSetup = async (t: ExecutionContext) => { instantiateMsg: undefined, }, }; + const osmoContracts: Record = { cw721: { path: WASM_FILE_CW721, instantiateMsg: { - name: "ark", - symbol: "ark", + name: "ark osmo", + symbol: "ark osmo", + collection_info_extension: { + description: "description", + image: "https://ark.pass/image.png", + external_link: "https://interchain.arkprotocol.io", + explicit_content: false, + royalty_info: { + payment_address: osmoClient.senderAddress, + share: "0.1", + }, + }, + creator: osmoClient.senderAddress, + minter: osmoClient.senderAddress, + withdraw_address: osmoClient.senderAddress, + }, + }, + cw721_v16: { + path: WASM_FILE_CW721_v16, + instantiateMsg: { + name: "ark osmo", + symbol: "ark osmo", minter: osmoClient.senderAddress, }, }, @@ -139,13 +195,18 @@ const standardSetup = async (t: ExecutionContext) => { info.osmoContractInfos.cw721OutgoingProxy.codeId; t.context.wasmCw721 = info.wasmContractInfos.cw721.address as string; + t.context.wasmCw721_v16 = info.wasmContractInfos.cw721_v16.address as string; t.context.osmoCw721 = info.osmoContractInfos.cw721.address as string; + t.context.osmoCw721_v16 = info.osmoContractInfos.cw721_v16.address as string; t.log(`instantiating wasm ICS721 contract (${wasmIcs721Id})`); const { contractAddress: wasmIcs721 } = await instantiateContract( wasmClient, wasmIcs721Id, - { cw721_base_code_id: wasmCw721Id }, + { + cw721_base_code_id: wasmCw721Id, + cw721_admin: wasmClient.senderAddress, // set cw721 admin, this will be also used as creator and payment address for escrowed cw721 by ics721 + }, "label ics721" ); t.log(`- wasm ICS721 contract address: ${wasmIcs721}`); @@ -155,7 +216,10 @@ const standardSetup = async (t: ExecutionContext) => { const { contractAddress: osmoIcs721 } = await instantiateContract( osmoClient, osmoIcs721Id, - { cw721_base_code_id: osmoCw721Id }, + { + cw721_base_code_id: osmoCw721Id, + cw721_admin: osmoClient.senderAddress, // set cw721 admin, this will be also used as creator and payment address for escrowed cw721 by ics721 + }, "label ics721" ); t.log(`- osmo ICS721 contract address: ${osmoIcs721}`); @@ -368,7 +432,7 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { tokenOwner = await ownerOf(wasmClient, wasmCw721, tokenId); t.is(wasmIcs721, tokenOwner.owner); // assert NFT minted on chain B - let osmoClassId = `${channel.channel.dest.portId}/${channel.channel.dest.channelId}/${t.context.wasmCw721}`; + let osmoClassId = `${channel.channel.dest.portId}/${channel.channel.dest.channelId}/${wasmCw721}`; let osmoCw721 = await osmoClient.sign.queryContractSmart(osmoIcs721, { nft_contract: { class_id: osmoClassId }, }); @@ -377,6 +441,43 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { // assert NFT on chain B is owned by osmoAddr tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); t.is(osmoAddr, tokenOwner.owner); + // assert escrowed collection data on chain B is properly set + const wasmCollectionData = await getCw721CollectionInfoAndExtension( + wasmClient, + wasmCw721 + ); + const osmoCollectionData = await getCw721CollectionInfoAndExtension( + osmoClient, + osmoCw721 + ); + osmoCollectionData.updated_at = wasmCollectionData.updated_at; // ignore updated_at + if (wasmCollectionData.extension?.royalty_info?.payment_address) { + wasmCollectionData.extension.royalty_info.payment_address = osmoAddr; // osmo cw721 admin address is used as payment address + } + t.deepEqual( + wasmCollectionData, + osmoCollectionData, + `collection data must match: ${JSON.stringify( + wasmCollectionData + )} vs ${JSON.stringify(osmoCollectionData)}` + ); + // assert creator is set to osmoAddr + const creatorOwnerShip = await getCw721CreatorOwnership( + osmoClient, + osmoCw721 + ); + t.is(osmoAddr, creatorOwnerShip.owner); + // assert minter is set to ics721 + const minterOwnerShip = await getCw721MinterOwnership(osmoClient, osmoCw721); + t.is(osmoIcs721, minterOwnerShip.owner); + + const wasmNftInfo = await nftInfo(wasmClient, wasmCw721, tokenId); + // assert extension is not null + t.truthy(wasmNftInfo.extension); + const osmoNftInfo = await nftInfo(osmoClient, osmoCw721, tokenId); + t.truthy(osmoNftInfo.extension); + // assert nft with extension is same + t.deepEqual(wasmNftInfo, osmoNftInfo); // test back transfer NFT to wasm chain t.log(`transfering back to wasm chain via ${channel.channel.dest.channelId}`); @@ -594,7 +695,7 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { ); // assert NFT minted on chain B - osmoClassId = `${onlyOsmoIncomingChannel.channel.dest.portId}/${onlyOsmoIncomingChannel.channel.dest.channelId}/${t.context.wasmCw721}`; + osmoClassId = `${onlyOsmoIncomingChannel.channel.dest.portId}/${onlyOsmoIncomingChannel.channel.dest.channelId}/${wasmCw721}`; osmoCw721 = await osmoClient.sign.queryContractSmart(osmoIcs721, { nft_contract: { class_id: osmoClassId }, }); @@ -607,7 +708,7 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { tokenOwner = await ownerOf(wasmClient, wasmCw721, tokenId); t.is(wasmIcs721, tokenOwner.owner); // assert NFT on chain B is owned by osmoAddr - osmoClassId = `${onlyOsmoIncomingChannel.channel.dest.portId}/${onlyOsmoIncomingChannel.channel.dest.channelId}/${t.context.wasmCw721}`; + osmoClassId = `${onlyOsmoIncomingChannel.channel.dest.portId}/${onlyOsmoIncomingChannel.channel.dest.channelId}/${wasmCw721}`; osmoCw721 = await osmoClient.sign.queryContractSmart(osmoIcs721, { nft_contract: { class_id: osmoClassId }, }); @@ -746,6 +847,471 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { t.is(wasmAddr, tokenOwner.owner); }); +test.serial("transfer NFT v16: wasm -> osmo", async (t) => { + await standardSetup(t); + + const { + wasmClient, + wasmAddr, + wasmCw721_v16, + wasmIcs721, + wasmCw721IncomingProxyId, + wasmCw721IncomingProxy, + wasmCw721OutgoingProxy, + osmoClient, + osmoAddr, + osmoIcs721, + channel, + otherChannel, + onlyOsmoIncomingChannel, + } = t.context; + + let tokenId = "1"; + await mint(wasmClient, wasmCw721_v16, tokenId, wasmAddr, undefined); + // assert token is minted + let tokenOwner = await ownerOf(wasmClient, wasmCw721_v16, tokenId); + t.is(wasmAddr, tokenOwner.owner); + + // ==== happy path: transfer NFT to osmo chain and back to wasm chain ==== + // test transfer NFT to osmo chain + t.log(`transfering to osmo chain via ${channel.channel.src.channelId}`); + let ibcMsg = { + receiver: osmoAddr, + channel_id: channel.channel.src.channelId, + timeout: { + block: { + revision: 1, + height: 90000, + }, + }, + }; + let transferResponse = await sendNft( + wasmClient, + wasmCw721_v16, + wasmCw721OutgoingProxy, + ibcMsg, + tokenId + ); + t.truthy(transferResponse); + + // Relay and verify we got a success + t.log("relaying packets"); + let info = await channel.link.relayAll(); + assertAckSuccess(info.acksFromA); + + // assert NFT on chain A is locked/owned by ICS contract + tokenOwner = await ownerOf(wasmClient, wasmCw721_v16, tokenId); + t.is(wasmIcs721, tokenOwner.owner); + // assert NFT minted on chain B + let osmoClassId = `${channel.channel.dest.portId}/${channel.channel.dest.channelId}/${wasmCw721_v16}`; + let osmoCw721 = await osmoClient.sign.queryContractSmart(osmoIcs721, { + nft_contract: { class_id: osmoClassId }, + }); + let allNFTs = await allTokens(osmoClient, osmoCw721); + t.true(allNFTs.tokens.length === 1); + // assert NFT on chain B is owned by osmoAddr + tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); + t.is(osmoAddr, tokenOwner.owner); + // assert escrowed collection data on chain B is properly set + // wasm collection data holds only name and symbol + const wasmCollectionData = await getCw721ContractInfo_v16( + wasmClient, + wasmCw721_v16 + ); + const osmoCollectionData = await getCw721CollectionInfoAndExtension( + osmoClient, + osmoCw721 + ); + t.deepEqual( + wasmCollectionData.name, + osmoCollectionData.name, + `name must match: ${wasmCollectionData.name} vs ${osmoCollectionData.name}` + ); + t.deepEqual( + wasmCollectionData.symbol, + osmoCollectionData.symbol, + `symbol must match: ${wasmCollectionData.symbol} vs ${osmoCollectionData.symbol}` + ); + // assert minter is set to ics721 + const minterOwnerShip = await getCw721Minter_v16(osmoClient, osmoCw721); + t.is(osmoIcs721, minterOwnerShip.minter); + + const wasmNftInfo = await nftInfo(wasmClient, wasmCw721_v16, tokenId); + // assert extension is not null + t.truthy(wasmNftInfo.extension); + const osmoNftInfo = await nftInfo(osmoClient, osmoCw721, tokenId); + t.truthy(osmoNftInfo.extension); + // assert nft with extension is same + t.deepEqual(wasmNftInfo, osmoNftInfo); + + // test back transfer NFT to wasm chain + t.log(`transfering back to wasm chain via ${channel.channel.dest.channelId}`); + transferResponse = await sendNft( + osmoClient, + osmoCw721, + t.context.osmoCw721OutgoingProxy, + { + receiver: wasmAddr, + channel_id: channel.channel.dest.channelId, + timeout: { + block: { + revision: 1, + height: 90000, + }, + }, + }, + tokenId + ); + t.truthy(transferResponse); + t.log("relaying packets"); + + // Verify we got a success + info = await channel.link.relayAll(); + assertAckSuccess(info.acksFromA); + + // assert NFT burned on chain B + allNFTs = await allTokens(osmoClient, osmoCw721); + t.true(allNFTs.tokens.length === 0); + // assert NFT on chain A is returned to owner + tokenOwner = await ownerOf(wasmClient, wasmCw721_v16, tokenId); + t.is(wasmAddr, tokenOwner.owner); + + // ==== test transfer NFT to osmo chain via unknown, not WLed channel by incoming proxy ==== + // test rejected NFT transfer due to unknown channel by incoming proxy + tokenId = "2"; + await mint(wasmClient, wasmCw721_v16, tokenId, wasmAddr, undefined); + // assert token is minted + tokenOwner = await ownerOf(wasmClient, wasmCw721_v16, tokenId); + t.is(wasmAddr, tokenOwner.owner); + + t.log( + `transfering to osmo chain via unknown ${otherChannel.channel.src.channelId}` + ); + const beforeWasmOutgoingClassTokenToChannelList = await outgoingChannels( + wasmClient, + wasmIcs721 + ); + const beforeWasmIncomingClassTokenToChannelList = await incomingChannels( + wasmClient, + wasmIcs721 + ); + const beforeWasmNftContractsToClassIdList = await nftContracts( + wasmClient, + wasmIcs721 + ); + const beforeOsmoOutgoingClassTokenToChannelList = await outgoingChannels( + osmoClient, + osmoIcs721 + ); + const beforeOsmoIncomingClassTokenToChannelList = await incomingChannels( + osmoClient, + osmoIcs721 + ); + const beforeOsmoNftContractsToClassIdList = await nftContracts( + osmoClient, + osmoIcs721 + ); + + ibcMsg = { + receiver: osmoAddr, + channel_id: otherChannel.channel.src.channelId, + timeout: { + block: { + revision: 1, + height: 90000, + }, + }, + }; + transferResponse = await sendNft( + wasmClient, + wasmCw721_v16, + wasmCw721OutgoingProxy, + ibcMsg, + tokenId + ); + t.truthy(transferResponse); + + // Relay and verify we got an error + t.log("relaying packets"); + info = await otherChannel.link.relayAll(); + assertAckErrors(info.acksFromA); + // assert no change before and after relay + const afterWasmOutgoingClassTokenToChannelList = await outgoingChannels( + wasmClient, + wasmIcs721 + ); + const afterWasmIncomingClassTokenToChannelList = await incomingChannels( + wasmClient, + wasmIcs721 + ); + const afterWasmNftContractsToClassIdList = await nftContracts( + wasmClient, + wasmIcs721 + ); + t.deepEqual( + beforeWasmOutgoingClassTokenToChannelList, + afterWasmOutgoingClassTokenToChannelList, + `outgoing channels must be unchanged: +- wasm before: ${JSON.stringify(beforeWasmOutgoingClassTokenToChannelList)} +- wasm after: ${JSON.stringify(afterWasmOutgoingClassTokenToChannelList)}` + ); + t.deepEqual( + beforeWasmIncomingClassTokenToChannelList, + afterWasmIncomingClassTokenToChannelList, + `incoming channels must be unchanged: +- wasm before: ${JSON.stringify(beforeWasmIncomingClassTokenToChannelList)} +- wasm after: ${JSON.stringify(afterWasmIncomingClassTokenToChannelList)}` + ); + t.deepEqual( + beforeWasmNftContractsToClassIdList, + afterWasmNftContractsToClassIdList, + `nft contracts must be unchanged: +- wasm before: ${JSON.stringify(beforeWasmNftContractsToClassIdList)} +- wasm after: ${JSON.stringify(afterWasmNftContractsToClassIdList)}` + ); + const afterOsmoOutgoingClassTokenToChannelList = await outgoingChannels( + osmoClient, + osmoIcs721 + ); + const afterOsmoIncomingClassTokenToChannelList = await incomingChannels( + osmoClient, + osmoIcs721 + ); + const afterOsmoNftContractsToClassIdList = await nftContracts( + osmoClient, + osmoIcs721 + ); + t.deepEqual( + beforeOsmoOutgoingClassTokenToChannelList, + afterOsmoOutgoingClassTokenToChannelList, + `outgoing channels must be unchanged: +- osmo before: ${JSON.stringify(beforeOsmoOutgoingClassTokenToChannelList)} +- osmo after: ${JSON.stringify(afterOsmoOutgoingClassTokenToChannelList)}` + ); + t.deepEqual( + beforeOsmoIncomingClassTokenToChannelList, + afterOsmoIncomingClassTokenToChannelList, + `incoming channels must be unchanged: +- osmo before: ${JSON.stringify(beforeOsmoIncomingClassTokenToChannelList)} +- osmo after: ${JSON.stringify(afterOsmoIncomingClassTokenToChannelList)}` + ); + t.deepEqual( + beforeOsmoNftContractsToClassIdList, + afterOsmoNftContractsToClassIdList, + `nft contracts must be unchanged: +- osmo before: ${JSON.stringify(beforeOsmoNftContractsToClassIdList)} +- osmo after: ${JSON.stringify(afterOsmoNftContractsToClassIdList)}` + ); + + // assert NFT on chain A is returned to owner + tokenOwner = await ownerOf(wasmClient, wasmCw721_v16, tokenId); + t.is(wasmAddr, tokenOwner.owner); + + // ==== test transfer NFT to osmo chain via channel WLed ONLY on osmo incoming proxy and back to wasm chain ==== + tokenId = "3"; + await mint(wasmClient, wasmCw721_v16, tokenId, wasmAddr, undefined); + // assert token is minted + tokenOwner = await ownerOf(wasmClient, wasmCw721_v16, tokenId); + t.is(wasmAddr, tokenOwner.owner); + + // test transfer NFT to osmo chain + t.log( + `transfering to osmo chain via ${onlyOsmoIncomingChannel.channel.src.channelId}` + ); + ibcMsg = { + receiver: osmoAddr, + channel_id: onlyOsmoIncomingChannel.channel.src.channelId, + timeout: { + block: { + revision: 1, + height: 90000, + }, + }, + }; + transferResponse = await sendNft( + wasmClient, + wasmCw721_v16, + wasmCw721OutgoingProxy, + ibcMsg, + tokenId + ); + t.truthy(transferResponse); + + // Relay and verify we got a success + t.log("relaying packets"); + info = await onlyOsmoIncomingChannel.link.relayAll(); + assertAckSuccess(info.acksFromA); + + // assert 1 entry for outgoing channels + let wasmOutgoingClassTokenToChannelList = await outgoingChannels( + wasmClient, + wasmIcs721 + ); + t.log( + `- outgoing channels: ${JSON.stringify( + wasmOutgoingClassTokenToChannelList + )}` + ); + t.true( + wasmOutgoingClassTokenToChannelList.length === 1, + `outgoing channels must have one entry: ${JSON.stringify( + wasmOutgoingClassTokenToChannelList + )}` + ); + + // assert NFT minted on chain B + osmoClassId = `${onlyOsmoIncomingChannel.channel.dest.portId}/${onlyOsmoIncomingChannel.channel.dest.channelId}/${wasmCw721_v16}`; + osmoCw721 = await osmoClient.sign.queryContractSmart(osmoIcs721, { + nft_contract: { class_id: osmoClassId }, + }); + allNFTs = await allTokens(osmoClient, osmoCw721); + t.true(allNFTs.tokens.length === 1); + // assert NFT on chain B is owned by osmoAddr + tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); + t.is(osmoAddr, tokenOwner.owner); + // assert NFT on chain A is locked/owned by ICS contract + tokenOwner = await ownerOf(wasmClient, wasmCw721_v16, tokenId); + t.is(wasmIcs721, tokenOwner.owner); + // assert NFT on chain B is owned by osmoAddr + osmoClassId = `${onlyOsmoIncomingChannel.channel.dest.portId}/${onlyOsmoIncomingChannel.channel.dest.channelId}/${wasmCw721_v16}`; + osmoCw721 = await osmoClient.sign.queryContractSmart(osmoIcs721, { + nft_contract: { class_id: osmoClassId }, + }); + tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); + t.is(osmoAddr, tokenOwner.owner); + + // test back transfer NFT to wasm chain, where onlyOsmoIncomingChannel is not WLed on wasm chain + t.log( + `transfering back to wasm chain via unknown ${onlyOsmoIncomingChannel.channel.dest.channelId}` + ); + transferResponse = await sendNft( + osmoClient, + osmoCw721, + t.context.osmoCw721OutgoingProxy, + { + receiver: wasmAddr, + channel_id: onlyOsmoIncomingChannel.channel.dest.channelId, + timeout: { + block: { + revision: 1, + height: 90000, + }, + }, + }, + tokenId + ); + t.truthy(transferResponse); + // before relay NFT escrowed by ICS721 + tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); + t.is(osmoIcs721, tokenOwner.owner); + + // Relay and verify we got an error + t.log("relaying packets"); + info = await onlyOsmoIncomingChannel.link.relayAll(); + for (const ack of info.acksFromB) { + const parsed = JSON.parse(fromUtf8(ack.acknowledgement)); + t.log(`- ack: ${JSON.stringify(parsed)}`); + } + assertAckErrors(info.acksFromB); + + // assert after failed relay, NFT on chain B is returned to owner + allNFTs = await allTokens(osmoClient, osmoCw721); + t.true(allNFTs.tokens.length === 1); + // assert NFT is returned to sender on osmo chain + tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); + t.is(osmoAddr, tokenOwner.owner); + + // ==== WL channel on wasm chain and test back transfer again ==== + t.log( + `migrate ${wasmCw721IncomingProxy} contract and add channel ${onlyOsmoIncomingChannel.channel.src.channelId}` + ); + await migrateIncomingProxy( + wasmClient, + wasmCw721IncomingProxy, + wasmCw721IncomingProxyId, + [ + channel.channel.src.channelId, + onlyOsmoIncomingChannel.channel.src.channelId, + ] + ); + + // test back transfer NFT to wasm chain, where onlyOsmoIncomingChannel is not WLed on wasm chain + t.log( + `transfering back to wasm chain via WLed ${onlyOsmoIncomingChannel.channel.dest.channelId}` + ); + transferResponse = await sendNft( + osmoClient, + osmoCw721, + t.context.osmoCw721OutgoingProxy, + { + receiver: wasmAddr, + channel_id: onlyOsmoIncomingChannel.channel.dest.channelId, + timeout: { + block: { + revision: 1, + height: 90000, + }, + }, + }, + tokenId + ); + t.truthy(transferResponse); + // before relay NFT escrowed by ICS721 + tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); + t.is(osmoIcs721, tokenOwner.owner); + + allNFTs = await allTokens(osmoClient, osmoCw721); + t.log(`- all tokens: ${JSON.stringify(allNFTs)}`); + + // query nft contracts + let nftContractsToClassIdList = await nftContracts(wasmClient, wasmIcs721); + t.log(`- nft contracts: ${JSON.stringify(nftContractsToClassIdList)}`); + t.true( + nftContractsToClassIdList.length === 1, + `nft contracts must have exactly one entry: ${JSON.stringify( + nftContractsToClassIdList + )}` + ); + + // Relay and verify success + t.log("relaying packets"); + info = await onlyOsmoIncomingChannel.link.relayAll(); + for (const ack of info.acksFromB) { + const parsed = JSON.parse(fromUtf8(ack.acknowledgement)); + t.log(`- ack: ${JSON.stringify(parsed)}`); + } + assertAckSuccess(info.acksFromB); + + // assert outgoing channels is empty + wasmOutgoingClassTokenToChannelList = await outgoingChannels( + wasmClient, + wasmIcs721 + ); + t.true( + wasmOutgoingClassTokenToChannelList.length === 0, + `outgoing channels not empty: ${JSON.stringify( + wasmOutgoingClassTokenToChannelList + )}` + ); + + // assert after success relay, NFT on chain B is burned + allNFTs = await allTokens(osmoClient, osmoCw721); + t.log(`- all tokens: ${JSON.stringify(allNFTs)}`); + t.true(allNFTs.tokens.length === 0); + // assert list is unchanged + nftContractsToClassIdList = await nftContracts(wasmClient, wasmIcs721); + t.log(`- nft contracts: ${JSON.stringify(nftContractsToClassIdList)}`); + t.true( + nftContractsToClassIdList.length === 1, + `nft contracts must have exactly one entry: ${JSON.stringify( + nftContractsToClassIdList + )}` + ); + // assert NFT is returned to sender on wasm chain + tokenOwner = await ownerOf(wasmClient, wasmCw721_v16, tokenId); + t.is(wasmAddr, tokenOwner.owner); +}); + test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { await standardSetup(t); @@ -798,7 +1364,7 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { tokenOwner = await ownerOf(wasmClient, wasmCw721, tokenId); t.is(wasmIcs721, tokenOwner.owner); // assert NFT minted on chain B - const osmoClassId = `${channel.channel.dest.portId}/${channel.channel.dest.channelId}/${t.context.wasmCw721}`; + const osmoClassId = `${channel.channel.dest.portId}/${channel.channel.dest.channelId}/${wasmCw721}`; const osmoCw721 = await osmoClient.sign.queryContractSmart(osmoIcs721, { nft_contract: { class_id: osmoClassId }, }); diff --git a/ts-relayer-tests/src/utils.ts b/ts-relayer-tests/src/utils.ts index 06ba5696..198a5433 100644 --- a/ts-relayer-tests/src/utils.ts +++ b/ts-relayer-tests/src/utils.ts @@ -91,7 +91,7 @@ export async function uploadAndInstantiate( for (const name in contracts) { const contractMsg = contracts[name]; console.debug(`storing ${name} contract from ${contractMsg.path}`); - const wasm = await readFileSync(contractMsg.path); + const wasm = readFileSync(contractMsg.path); const receipt = await client.sign.upload( client.senderAddress, wasm,