From 7adef9fb080dc8ac2dd13fc7e5d52b38ac7e9d1d Mon Sep 17 00:00:00 2001 From: mr-t Date: Mon, 12 Aug 2024 00:17:37 +0200 Subject: [PATCH 01/21] use latest cw721 v19 --- Cargo.lock | 346 ++++++++++++------ Cargo.toml | 7 +- contracts/cw721-tester/Cargo.toml | 3 +- contracts/cw721-tester/src/lib.rs | 55 ++- contracts/ics721-base-tester/src/contract.rs | 32 +- contracts/ics721-base-tester/src/msg.rs | 2 +- contracts/ics721-base-tester/src/state.rs | 4 +- contracts/sg-ics721/Cargo.toml | 3 +- .../src/testing/integration_tests.rs | 161 ++++---- packages/ics721/Cargo.toml | 1 + packages/ics721/src/execute.rs | 30 +- packages/ics721/src/ibc.rs | 4 +- packages/ics721/src/msg.rs | 5 +- packages/ics721/src/query.rs | 8 +- packages/ics721/src/state.rs | 6 +- packages/ics721/src/testing/contract.rs | 120 +++--- .../ics721/src/testing/integration_tests.rs | 252 +++++++------ packages/ics721/src/utils.rs | 51 ++- 18 files changed, 661 insertions(+), 429 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f471560..245e55b7 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" @@ -75,9 +75,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 +93,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 +106,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 +128,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,9 +139,9 @@ 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", @@ -223,6 +222,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,9 +333,9 @@ 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", @@ -336,7 +343,7 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "derivative", - "itertools 0.12.1", + "itertools", "prost", "schemars", "serde", @@ -352,8 +359,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 +391,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 +528,18 @@ 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.18.0" @@ -513,7 +556,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.1#e63a7bbb620e6ea39224bb5580967477066cb7ae" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -522,6 +565,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=migrate_msgs#16997350c36bf6aa03c9428ce300fa48f17a0e65" +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 +614,7 @@ 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", @@ -547,7 +628,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 +642,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.1#e63a7bbb620e6ea39224bb5580967477066cb7ae" 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.1)", "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=migrate_msgs#16997350c36bf6aa03c9428ce300fa48f17a0e65" +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=migrate_msgs)", + "serde", +] + +[[package]] +name = "cw721-metadata-onchain" +version = "0.19.0" +source = "git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs#16997350c36bf6aa03c9428ce300fa48f17a0e65" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 1.1.2", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs)", + "schemars", + "serde", +] + [[package]] name = "cw721-tester" version = "0.1.0" @@ -584,15 +691,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=migrate_msgs)", + "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 +740,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 +775,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 +836,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", @@ -784,16 +892,17 @@ dependencies = [ "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.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs)", "cw721-base 0.16.0", - "cw721-base 0.18.0 (git+https://github.com/CosmWasm/cw-nfts?branch=main)", + "cw721-base 0.19.0", + "cw721-metadata-onchain", "ics721-types 0.1.0", "serde", "sha2 0.10.8", @@ -819,7 +928,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=migrate_msgs)", "ics721", "ics721-types 0.1.0", "thiserror", @@ -832,7 +941,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=migrate_msgs)", "serde", "thiserror", ] @@ -845,7 +954,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 +969,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 +980,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 +1000,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 +1018,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 +1040,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 +1059,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 +1106,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 +1124,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 +1150,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 +1174,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", ] @@ -1114,8 +1221,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.1)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs)", + "cw721-base 0.18.0 (git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.1)", "ics721", "ics721-types 0.1.0", "sg-std", @@ -1141,13 +1249,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 +1264,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 +1335,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 +1352,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 +1363,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 +1416,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 +1436,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 +1448,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..3e55e1ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,12 @@ 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 = "migrate_msgs"} # 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.1", package = "cw721"} # 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-metadata-onchain = { git = "https://github.com/public-awesome/cw-nfts", branch = "migrate_msgs", 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 = "migrate_msgs"} # 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.1", package = "cw721-base"} # TODO switch to version 0.18.1/0.19.0, once released cw721-base-016 = { version = "0.16.0", package = "cw721-base" } 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" } 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..4d45dfd3 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::ExecuteMsg; +pub type QueryMsg = cw721_metadata_onchain::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..2e0aa07f 100644 --- a/contracts/sg-ics721/Cargo.toml +++ b/contracts/sg-ics721/Cargo.toml @@ -31,7 +31,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/testing/integration_tests.rs b/contracts/sg-ics721/src/testing/integration_tests.rs index 49cf3b00..ef9ae498 100644 --- a/contracts/sg-ics721/src/testing/integration_tests.rs +++ b/contracts/sg-ics721/src/testing/integration_tests.rs @@ -7,7 +7,7 @@ use cosmwasm_std::{ RecoverPubkeyError, Reply, Response, StdError, StdResult, Storage, VerificationError, WasmMsg, }; use cw2::set_contract_version; -use cw721_base::msg::QueryMsg as Cw721QueryMsg; +use cw721_base_018::msg::QueryMsg as Cw721QueryMsg; use cw_cii::{Admin, ContractInstantiateInfo}; use cw_multi_test::{ AddressGenerator, App, AppBuilder, BankKeeper, Contract, ContractWrapper, DistributionKeeper, @@ -288,6 +288,7 @@ struct Test { } impl Test { + /// Test setup with optional pauser and proxy contracts. fn new( outgoing_proxy: bool, incoming_proxy: bool, @@ -518,7 +519,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, @@ -710,7 +711,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 +721,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 +749,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 +763,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 +780,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 +789,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 +803,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, }, @@ -886,7 +887,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 +897,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() } @@ -924,12 +925,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(), }, ) @@ -938,12 +939,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 +956,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 +965,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 +979,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 +1061,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 +1071,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 +1099,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 +1113,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 +1130,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 +1139,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 +1154,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 +1239,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 +1249,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 +1277,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 +1291,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 +1308,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 +1317,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 +1332,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 +1417,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 +1427,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 +1455,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 +1469,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 +1486,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 +1495,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 +1510,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 +1630,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 +1638,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 +1648,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 +1700,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 +1725,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 +1759,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 +1770,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 +1779,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 +1790,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 +1805,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, }, @@ -1946,12 +1947,12 @@ fn test_do_instantiate_and_mint_no_instantiate() { ); // 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, }, @@ -2035,7 +2036,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 +2074,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 +2138,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 +2155,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 +2195,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 { @@ -2269,7 +2270,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/Cargo.toml b/packages/ics721/Cargo.toml index e850a9fb..23b50db4 100644 --- a/packages/ics721/Cargo.toml +++ b/packages/ics721/Cargo.toml @@ -14,6 +14,7 @@ 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"] } ics721-types = { workspace = true } thiserror = { workspace = true } diff --git a/packages/ics721/src/execute.rs b/packages/ics721/src/execute.rs index 67d4297d..152f99ea 100644 --- a/packages/ics721/src/execute.rs +++ b/packages/ics721/src/execute.rs @@ -107,7 +107,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, @@ -185,7 +185,7 @@ where .querier .query_wasm_smart( child_collection.clone(), - &cw721::Cw721QueryMsg::AllNftInfo { + &cw721_metadata_onchain::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -206,7 +206,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::ExecuteMsg::Burn { token_id: token_id.clone().into(), })?, funds: vec![], @@ -268,7 +268,7 @@ where .querier .query_wasm_smart( home_collection.clone(), - &cw721::Cw721QueryMsg::AllNftInfo { + &cw721_metadata_onchain::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -284,7 +284,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::ExecuteMsg::TransferNft { recipient: recipient.to_string(), token_id: token_id.clone().into(), })?, @@ -401,7 +401,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::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -608,9 +608,11 @@ where .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 mut instantiate_msg = cw721_metadata_onchain::InstantiateMsg { name: class.id.clone().into(), symbol: class.id.clone().into(), + collection_info_extension: None, // TODO consider class data in NonFungibleTokenPacketData + creator: Some(creator.clone()), // TODO maybe better using cw721 admin? minter: Some(env.contract.address.to_string()), withdraw_address: Some(creator), }; @@ -655,10 +657,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::ExecuteMsg::TransferNft { + recipient: receiver.to_string(), + token_id: token_id.into(), + }, + )?, funds: vec![], }) }) @@ -686,11 +690,11 @@ where // 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)?; - let msg = cw721_base::msg::ExecuteMsg::::Mint { + let msg = cw721_metadata_onchain::ExecuteMsg::Mint { token_id: id.into(), token_uri: uri, owner: receiver.to_string(), - extension: Empty::default(), + extension: None, // TODO consider token data in NonFungibleTokenPacketData }; Ok(WasmMsg::Execute { contract_addr: nft_contract.to_string(), diff --git a/packages/ics721/src/ibc.rs b/packages/ics721/src/ibc.rs index fa96eef6..5992759e 100644 --- a/packages/ics721/src/ibc.rs +++ b/packages/ics721/src/ibc.rs @@ -142,7 +142,7 @@ where messages.push(WasmMsg::Execute { contract_addr: nft_contract.to_string(), - msg: to_json_binary(&cw721::Cw721ExecuteMsg::Burn { + msg: to_json_binary(&cw721_metadata_onchain::ExecuteMsg::Burn { token_id: token.into(), })?, funds: vec![], @@ -212,7 +212,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::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..dea4905b 100644 --- a/packages/ics721/src/query.rs +++ b/packages/ics721/src/query.rs @@ -150,7 +150,7 @@ pub fn query_token_metadata( }; let UniversalAllNftInfoResponse { info, .. } = deps.querier.query_wasm_smart( nft_contract, - &cw721::Cw721QueryMsg::AllNftInfo { + &cw721_metadata_onchain::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -166,11 +166,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::QueryMsg::OwnerOf { token_id, include_expired: None, }, diff --git a/packages/ics721/src/state.rs b/packages/ics721/src/state.rs index 0c43b0bc..744d4e09 100644 --- a/packages/ics721/src/state.rs +++ b/packages/ics721/src/state.rs @@ -131,12 +131,12 @@ mod tests { #[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"), }, diff --git a/packages/ics721/src/testing/contract.rs b/packages/ics721/src/testing/contract.rs index 6980a4f3..51d66743 100644 --- a/packages/ics721/src/testing/contract.rs +++ b/packages/ics721/src/testing/contract.rs @@ -5,8 +5,13 @@ use cosmwasm_std::{ 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 cw721::{ + msg::{ + AllNftInfoResponse, CollectionInfoAndExtensionResponse, NftInfoResponse, NumTokensResponse, + }, + DefaultOptionalCollectionExtension, +}; +use cw721_metadata_onchain::QueryMsg; use cw_cii::ContractInstantiateInfo; use cw_ownable::Ownership; use cw_storage_plus::Map; @@ -85,8 +90,16 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { cosmwasm_std::WasmQuery::Smart { contract_addr: _, msg, - } => match from_json::>(&msg).unwrap() { - QueryMsg::Ownership {} => QuerierResult::Ok(ContractResult::Ok( + } => 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, @@ -96,7 +109,7 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { )), QueryMsg::AllNftInfo { .. } => QuerierResult::Ok(ContractResult::Ok( to_json_binary(&AllNftInfoResponse::> { - access: cw721::OwnerOfResponse { + access: cw721::msg::OwnerOfResponse { owner: MOCK_CONTRACT_ADDR.to_string(), approvals: vec![], }, @@ -107,10 +120,26 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { }) .unwrap(), )), + #[allow(deprecated)] QueryMsg::ContractInfo {} => QuerierResult::Ok(ContractResult::Ok( - to_json_binary(&cw721::ContractInfoResponse { + to_json_binary(&CollectionInfoAndExtensionResponse::< + DefaultOptionalCollectionExtension, + > { name: "name".to_string(), symbol: "symbol".to_string(), + extension: None, + updated_at: Timestamp::default(), + }) + .unwrap(), + )), + QueryMsg::GetCollectionInfoAndExtension {} => QuerierResult::Ok(ContractResult::Ok( + to_json_binary(&CollectionInfoAndExtensionResponse::< + DefaultOptionalCollectionExtension, + > { + name: "name".to_string(), + symbol: "symbol".to_string(), + extension: None, + updated_at: Timestamp::default(), }) .unwrap(), )), @@ -138,44 +167,49 @@ fn mock_querier_v016(query: &WasmQuery) -> QuerierResult { 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![], + } => 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(), }, - 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 + ) + .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 { diff --git a/packages/ics721/src/testing/integration_tests.rs b/packages/ics721/src/testing/integration_tests.rs index bda6f06b..26c61192 100644 --- a/packages/ics721/src/testing/integration_tests.rs +++ b/packages/ics721/src/testing/integration_tests.rs @@ -7,7 +7,11 @@ use cosmwasm_std::{ RecoverPubkeyError, Reply, Response, StdError, StdResult, Storage, VerificationError, WasmMsg, }; use cw2::set_contract_version; -use cw721_base::msg::{InstantiateMsg as Cw721InstantiateMsg, QueryMsg as Cw721QueryMsg}; +use cw721::{ + msg::CollectionInfoAndExtensionResponse, DefaultOptionalCollectionExtension, + DefaultOptionalNftExtension, +}; +use cw721_metadata_onchain::{InstantiateMsg as Cw721InstantiateMsg, QueryMsg as Cw721QueryMsg}; use cw_cii::{Admin, ContractInstantiateInfo}; use cw_multi_test::{ AddressGenerator, App, AppBuilder, BankKeeper, Contract, ContractWrapper, DistributionKeeper, @@ -371,6 +375,8 @@ impl Test { &Cw721InstantiateMsg { name: "name".to_string(), symbol: "symbol".to_string(), + collection_info_extension: None, + creator: None, minter: Some(source_cw721_owner.to_string()), withdraw_address: None, }, @@ -523,7 +529,7 @@ impl Test { .wrap() .query_wasm_smart( self.source_cw721.clone(), - &cw721_base::msg::QueryMsg::::AllNftInfo { + &cw721_metadata_onchain::QueryMsg::AllNftInfo { token_id, include_expired: None, }, @@ -538,7 +544,7 @@ impl Test { .execute_contract( self.source_cw721_owner.clone(), self.source_cw721.clone(), - &cw721_base::msg::ExecuteMsg::::Mint { + &cw721_metadata_onchain::ExecuteMsg::Mint { token_id: self.nfts_minted.to_string(), owner: owner.to_string(), token_uri: None, @@ -552,9 +558,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) } @@ -733,29 +739,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::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -764,12 +772,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::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -781,7 +789,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::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), token_id: "1".to_string(), }, @@ -790,7 +798,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 +812,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::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -887,29 +895,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: 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::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -918,12 +928,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::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -935,7 +945,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::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), token_id: "1".to_string(), }, @@ -944,7 +954,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 +968,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::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1040,29 +1050,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::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1071,12 +1083,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::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1088,7 +1100,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::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), // new owner token_id: "1".to_string(), }, @@ -1097,7 +1109,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 +1124,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::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1197,29 +1209,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::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1228,12 +1242,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::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1245,7 +1259,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::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), // new owner token_id: "1".to_string(), }, @@ -1254,7 +1268,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 +1283,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::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1389,54 +1403,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::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::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1446,22 +1468,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::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::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1480,7 +1502,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::ExecuteMsg::TransferNft { recipient: nft_contract_1.to_string(), token_id: "1".to_string(), }, @@ -1491,7 +1513,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::ExecuteMsg::TransferNft { recipient: nft_contract_2.to_string(), token_id: "1".to_string(), }, @@ -1500,7 +1522,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 +1533,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 +1548,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::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::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1647,12 +1669,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::QueryMsg::AllTokens { start_after: None, limit: None, }, @@ -1736,7 +1758,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 +1796,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 +1827,11 @@ fn test_proxy_authorized() { .instantiate_contract( source_cw721_id, test.app.api().addr_make("ekez"), - &cw721_base::InstantiateMsg { + &cw721_metadata_onchain::InstantiateMsg { name: "token".to_string(), symbol: "nonfungible".to_string(), + collection_info_extension: None, + creator: None, minter: Some( test.app .api() @@ -1827,11 +1851,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::ExecuteMsg::Mint { token_id: "1".to_string(), owner: test.ics721.to_string(), token_uri: None, - extension: Empty::default(), + extension: None, }, &[], ) @@ -1844,7 +1868,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 +1910,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 { @@ -1954,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 { @@ -2035,7 +2059,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 +2193,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/utils.rs b/packages/ics721/src/utils.rs index 3a29e4e5..38a1c8bf 100644 --- a/packages/ics721/src/utils.rs +++ b/packages/ics721/src/utils.rs @@ -1,34 +1,51 @@ 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 in the contract + let ownership_result: StdResult> = deps.querier.query_wasm_smart( + collection, + &cw721_metadata_onchain::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 in the contract + let ownership: StdResult> = deps.querier.query_wasm_smart( + collection, + &cw721_metadata_onchain::QueryMsg::GetMinterOwnership {}, + ); + match ownership { + Ok(ownership) => ownership.owner.map(|a| a.to_string()), + Err(_) => { + // cw721 v0.16 and lower holds minter + println!(">>> 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) + } + } } }; + println!(">>> owner: {:?}", owner); let contract_info = deps.querier.query_wasm_contract_info(collection)?; + println!(">>> contract_info: {:?}", contract_info); let UniversalCollectionInfoResponse { name, symbol } = deps.querier.query_wasm_smart( collection, - &cw721_base::msg::QueryMsg::::ContractInfo {}, - )?; - let NumTokensResponse { count } = deps.querier.query_wasm_smart( - collection, - &cw721_base::msg::QueryMsg::::NumTokens {}, + #[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::QueryMsg::ContractInfo {}, )?; + let NumTokensResponse { count } = deps + .querier + .query_wasm_smart(collection, &cw721_metadata_onchain::QueryMsg::NumTokens {})?; Ok(CollectionData { owner, From a18f86641f0e1e08eab0619e3954ad65db6a2865 Mon Sep 17 00:00:00 2001 From: mr-t Date: Mon, 12 Aug 2024 00:21:11 +0200 Subject: [PATCH 02/21] update workspace-optimizer v0.16.0 --- justfile | 2 +- ts-relayer-tests/build.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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 From 088d43b02003cd0de9e0cd7bc86083d946a767c3 Mon Sep 17 00:00:00 2001 From: mr-t Date: Mon, 12 Aug 2024 10:34:34 +0200 Subject: [PATCH 03/21] add cw721_metadata_onchain_v0.19.0.wasm for tests --- .../cw721_metadata_onchain_v0.19.0.wasm | Bin 0 -> 712722 bytes ts-relayer-tests/src/ics721.spec.ts | 3 ++- ts-relayer-tests/src/utils.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 external-wasms/cw721_metadata_onchain_v0.19.0.wasm 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 0000000000000000000000000000000000000000..9a3dd6f28a1a0d781cc94552d14fd3aaa842ee3b GIT binary patch literal 712722 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�zFB@iqr$^;^--}WoDon;6yb-pvHWS*sjjWpv7B* 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�f1+W1%} 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 z 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 MlTvxvJfATJpQk*72455) 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 -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_INCOMING_PROXY = "./internal/cw721_incoming_proxy.wasm"; const WASM_FILE_CW721_OUTGOING_PROXY = "./internal/cw721_outgoing_proxy_rate_limit.wasm"; @@ -97,6 +97,7 @@ const standardSetup = async (t: ExecutionContext) => { instantiateMsg: { name: "ark", symbol: "ark", + creator: osmoClient.senderAddress, minter: osmoClient.senderAddress, }, }, 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, From 4cfad8b10a656cced7b809a876a242630d80fc61 Mon Sep 17 00:00:00 2001 From: mr-t Date: Mon, 12 Aug 2024 11:12:42 +0200 Subject: [PATCH 04/21] fix go tests --- e2e/adversarial_test.go | 9 +++++---- e2e/basic_test.go | 2 +- e2e/callback_test.go | 2 +- e2e/migrate_update_test.go | 5 +++-- e2e/test_suite/actions.go | 4 ++-- e2e/transfer_test.go | 18 +++++++++--------- packages/ics721/src/utils.rs | 3 --- ts-relayer-tests/README.md | 2 +- 8 files changed, 22 insertions(+), 23 deletions(-) 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/packages/ics721/src/utils.rs b/packages/ics721/src/utils.rs index 38a1c8bf..48e9320c 100644 --- a/packages/ics721/src/utils.rs +++ b/packages/ics721/src/utils.rs @@ -22,7 +22,6 @@ pub fn get_collection_data(deps: &DepsMut, collection: &Addr) -> StdResult ownership.owner.map(|a| a.to_string()), Err(_) => { // cw721 v0.16 and lower holds minter - println!(">>> cw721 v0.16 and lower holds minter"); let minter_response: cw721_base_016::msg::MinterResponse = deps.querier.query_wasm_smart( collection, @@ -34,9 +33,7 @@ pub fn get_collection_data(deps: &DepsMut, collection: &Addr) -> StdResult>> owner: {:?}", owner); let contract_info = deps.querier.query_wasm_contract_info(collection)?; - println!(">>> contract_info: {:?}", contract_info); let UniversalCollectionInfoResponse { name, symbol } = deps.querier.query_wasm_smart( collection, #[allow(deprecated)] 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 From e82d03c080c3e4488c97a54f2bf52c67511b80b1 Mon Sep 17 00:00:00 2001 From: mr-t Date: Mon, 12 Aug 2024 14:22:22 +0200 Subject: [PATCH 05/21] retrieve collection data --- contracts/sg-ics721/Cargo.toml | 1 + contracts/sg-ics721/src/execute.rs | 22 ++- contracts/sg-ics721/src/state.rs | 18 -- .../src/testing/integration_tests.rs | 98 ++++++++-- packages/ics721/src/state.rs | 38 ++-- packages/ics721/src/testing/contract.rs | 182 +++++++++++------- packages/ics721/src/testing/ibc_tests.rs | 1 - .../ics721/src/testing/integration_tests.rs | 101 +++++++++- packages/ics721/src/utils.rs | 10 +- 9 files changed, 337 insertions(+), 134 deletions(-) diff --git a/contracts/sg-ics721/Cargo.toml b/contracts/sg-ics721/Cargo.toml index 2e0aa07f..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} diff --git a/contracts/sg-ics721/src/execute.rs b/contracts/sg-ics721/src/execute.rs index 2bcfba51..073051ea 100644 --- a/contracts/sg-ics721/src/execute.rs +++ b/contracts/sg-ics721/src/execute.rs @@ -1,13 +1,14 @@ use cosmwasm_std::{from_json, to_json_binary, Addr, Binary, 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_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,19 +17,32 @@ 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, })) } 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 ef9ae498..49fbdf94 100644 --- a/contracts/sg-ics721/src/testing/integration_tests.rs +++ b/contracts/sg-ics721/src/testing/integration_tests.rs @@ -2,11 +2,13 @@ use anyhow::Result; use bech32::{decode, encode, FromBase32, ToBase32, Variant}; 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::{CollectionExtension, RoyaltyInfo}; use cw721_base_018::msg::QueryMsg as Cw721QueryMsg; use cw_cii::{Admin, ContractInstantiateInfo}; use cw_multi_test::{ @@ -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"; @@ -375,12 +374,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://ark.pass".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), + }), }, }, &[], @@ -636,6 +638,19 @@ 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://ark.pass".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(), @@ -848,6 +863,19 @@ 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://ark.pass".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(), @@ -1868,6 +1896,19 @@ 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://ark.pass".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(), @@ -1994,6 +2035,19 @@ 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://ark.pass".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(), @@ -2234,7 +2288,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(), @@ -2243,14 +2297,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://ark.pass".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(); diff --git a/packages/ics721/src/state.rs b/packages/ics721/src/state.rs index 744d4e09..cdb6dc33 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}; @@ -54,26 +55,28 @@ pub const ADMIN_USED_FOR_CW721: Item> = Item::new("l"); /// 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 +91,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 +129,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::msg::AllNftInfoResponse:: { + let start = cw721::msg::AllNftInfoResponse:: { access: cw721::msg::OwnerOfResponse { owner: "foo".to_string(), approvals: vec![], }, 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 +153,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 index 51d66743..6339fa1c 100644 --- a/packages/ics721/src/testing/contract.rs +++ b/packages/ics721/src/testing/contract.rs @@ -2,14 +2,15 @@ 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, + to_json_binary, Addr, ContractResult, CosmosMsg, Decimal, DepsMut, Empty, IbcMsg, IbcTimeout, + Order, QuerierResult, Response, StdResult, SubMsg, Timestamp, WasmQuery, }; use cw721::{ msg::{ AllNftInfoResponse, CollectionInfoAndExtensionResponse, NftInfoResponse, NumTokensResponse, }, - DefaultOptionalCollectionExtension, + CollectionExtension, DefaultOptionalCollectionExtension, DefaultOptionalNftExtension, + NftExtension, RoyaltyInfo, }; use cw721_metadata_onchain::QueryMsg; use cw_cii::ContractInstantiateInfo; @@ -108,14 +109,16 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { .unwrap(), )), QueryMsg::AllNftInfo { .. } => QuerierResult::Ok(ContractResult::Ok( - to_json_binary(&AllNftInfoResponse::> { + 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: None, + extension: Some(NftExtension { + ..Default::default() + }), }, }) .unwrap(), @@ -127,8 +130,18 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { > { name: "name".to_string(), symbol: "symbol".to_string(), - extension: None, - updated_at: Timestamp::default(), + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some("https://ark.pass".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(), )), @@ -138,7 +151,17 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { > { name: "name".to_string(), symbol: "symbol".to_string(), - extension: None, + extension: Some(CollectionExtension { + description: "description".to_string(), + explicit_content: Some(false), + external_link: Some("https://ark.pass".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(), @@ -227,7 +250,6 @@ fn mock_querier_v016(query: &WasmQuery) -> QuerierResult { #[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, @@ -239,6 +261,7 @@ fn test_receive_nft() { .unwrap(), ) .unwrap(); + // test case: receive nft from cw721-base { let mut querier = MockQuerier::default(); querier.update_wasm(mock_querier); @@ -271,34 +294,48 @@ fn test_receive_nft() { 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() - })) - ); + 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://ark.pass".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: 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 @@ -352,34 +389,38 @@ fn test_receive_nft() { 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() - })) - ); + 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 @@ -521,6 +562,17 @@ fn test_receive_sets_uri() { 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://ark.pass".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() 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 26c61192..47d9f23d 100644 --- a/packages/ics721/src/testing/integration_tests.rs +++ b/packages/ics721/src/testing/integration_tests.rs @@ -2,14 +2,16 @@ use anyhow::Result; use bech32::{decode, encode, FromBase32, ToBase32, Variant}; 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::{ - msg::CollectionInfoAndExtensionResponse, DefaultOptionalCollectionExtension, - DefaultOptionalNftExtension, + msg::{CollectionExtensionMsg, CollectionInfoAndExtensionResponse, RoyaltyInfoResponse}, + CollectionExtension, DefaultOptionalCollectionExtension, DefaultOptionalNftExtension, + RoyaltyInfo, }; use cw721_metadata_onchain::{InstantiateMsg as Cw721InstantiateMsg, QueryMsg as Cw721QueryMsg}; use cw_cii::{Admin, ContractInstantiateInfo}; @@ -375,7 +377,17 @@ impl Test { &Cw721InstantiateMsg { name: "name".to_string(), symbol: "symbol".to_string(), - collection_info_extension: None, + collection_info_extension: Some(CollectionExtensionMsg { + description: Some("description".to_string()), + explicit_content: Some(false), + external_link: Some("https://ark.pass".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, @@ -664,6 +676,19 @@ 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://ark.pass".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(), @@ -856,6 +881,19 @@ 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://ark.pass".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(), @@ -1609,6 +1647,19 @@ 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://ark.pass".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(), @@ -1716,6 +1767,19 @@ 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://ark.pass".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(), @@ -1929,11 +1993,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 = @@ -1957,9 +2021,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://ark.pass".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:?}") @@ -2028,6 +2108,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(); diff --git a/packages/ics721/src/utils.rs b/packages/ics721/src/utils.rs index 48e9320c..4ff9fe2d 100644 --- a/packages/ics721/src/utils.rs +++ b/packages/ics721/src/utils.rs @@ -34,7 +34,12 @@ pub fn get_collection_data(deps: &DepsMut, collection: &Addr) -> StdResult StdResult Date: Mon, 12 Aug 2024 16:51:50 +0200 Subject: [PATCH 06/21] clippy --- packages/ics721/src/testing/contract.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ics721/src/testing/contract.rs b/packages/ics721/src/testing/contract.rs index 6339fa1c..186e65ba 100644 --- a/packages/ics721/src/testing/contract.rs +++ b/packages/ics721/src/testing/contract.rs @@ -297,9 +297,9 @@ fn test_receive_nft() { 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 packet_data: NonFungibleTokenPacketData = from_json(data).unwrap(); let class_data: CollectionData = - from_json(&packet_data.class_data.clone().unwrap()).unwrap(); + 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()), @@ -392,9 +392,9 @@ fn test_receive_nft() { 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 packet_data: NonFungibleTokenPacketData = from_json(data).unwrap(); let class_data: CollectionData = - from_json(&packet_data.class_data.clone().unwrap()).unwrap(); + 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()), From 6b124ed781f2d522e8bdc76de2c0d567d0ad9bd3 Mon Sep 17 00:00:00 2001 From: mr-t Date: Mon, 12 Aug 2024 17:48:15 +0200 Subject: [PATCH 07/21] instantiate nft contract with collection data --- contracts/sg-ics721/src/execute.rs | 26 ++++++- .../src/testing/integration_tests.rs | 51 ++++++++++---- packages/ics721/src/execute.rs | 21 +++++- .../ics721/src/testing/integration_tests.rs | 68 +++++++++---------- 4 files changed, 111 insertions(+), 55 deletions(-) diff --git a/contracts/sg-ics721/src/execute.rs b/contracts/sg-ics721/src/execute.rs index 073051ea..e0be7817 100644 --- a/contracts/sg-ics721/src/execute.rs +++ b/contracts/sg-ics721/src/execute.rs @@ -1,8 +1,11 @@ -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::{SgIcs721Contract, STARGAZE_ICON_PLACEHOLDER}; @@ -48,7 +51,7 @@ impl Ics721Execute for SgIcs721Contract { fn init_msg(&self, deps: Deps, env: &Env, class: &Class) -> 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 @@ -60,7 +63,7 @@ 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: creator.clone(), description: "".to_string(), // use Stargaze icon as placeholder image: STARGAZE_ICON_PLACEHOLDER.to_string(), @@ -79,6 +82,23 @@ impl Ics721Execute for SgIcs721Contract { if let Some(collection_data) = collection_data { instantiate_msg.name = collection_data.name; instantiate_msg.symbol = collection_data.symbol; + let admin_or_creator = admin.unwrap_or(creator.clone()); + if let Some(collection_info_extension_msg) = + collection_data.extension.map(|ext| sg721::CollectionInfo { + creator: 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: admin_or_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/testing/integration_tests.rs b/contracts/sg-ics721/src/testing/integration_tests.rs index 49fbdf94..fedc0609 100644 --- a/contracts/sg-ics721/src/testing/integration_tests.rs +++ b/contracts/sg-ics721/src/testing/integration_tests.rs @@ -276,6 +276,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, @@ -353,7 +354,7 @@ impl Test { incoming_proxy, outgoing_proxy, pauser: admin.clone(), - cw721_admin: admin, + cw721_admin: admin.clone(), contract_addr_length: None, }, &[], @@ -393,6 +394,7 @@ impl Test { Self { app, + admin_and_pauser: admin, source_cw721_owner, source_cw721_id, source_cw721, @@ -833,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!( @@ -943,12 +951,15 @@ fn test_do_instantiate_and_mint() { 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, + description: "description".to_string(), + image: "https://ark.pass/image.png".to_string(), + external_link: Some("https://ark.pass".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(), + }), } ); @@ -1862,7 +1873,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!( @@ -1977,13 +1994,17 @@ fn test_do_instantiate_and_mint_no_instantiate() { 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, + description: "description".to_string(), + image: "https://ark.pass/image.png".to_string(), + external_link: Some("https://ark.pass".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(), + }), } ); diff --git a/packages/ics721/src/execute.rs b/packages/ics721/src/execute.rs index 152f99ea..d4642e48 100644 --- a/packages/ics721/src/execute.rs +++ b/packages/ics721/src/execute.rs @@ -4,6 +4,7 @@ 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::RoyaltyInfoResponse; use cw_storage_plus::Map; use ics721_types::{ ibc_types::{IbcOutgoingMsg, IbcOutgoingProxyMsg, NonFungibleTokenPacketData}, @@ -603,7 +604,7 @@ where /// Default implementation using `cw721_base::msg::InstantiateMsg` fn init_msg(&self, deps: Deps, env: &Env, class: &Class) -> 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())?; @@ -614,7 +615,7 @@ where collection_info_extension: None, // TODO consider class data in NonFungibleTokenPacketData creator: Some(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 @@ -625,6 +626,22 @@ where if let Some(collection_data) = collection_data { instantiate_msg.name = collection_data.name; instantiate_msg.symbol = collection_data.symbol; + let admin_or_creator = admin.unwrap_or(creator.clone()); + 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: admin_or_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) diff --git a/packages/ics721/src/testing/integration_tests.rs b/packages/ics721/src/testing/integration_tests.rs index 47d9f23d..0f5953ee 100644 --- a/packages/ics721/src/testing/integration_tests.rs +++ b/packages/ics721/src/testing/integration_tests.rs @@ -281,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, @@ -415,6 +416,7 @@ impl Test { Self { app, + admin_and_pauser: admin, source_cw721_owner, source_cw721_id, source_cw721, @@ -654,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(), @@ -668,10 +671,7 @@ 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(), @@ -682,10 +682,8 @@ fn test_do_instantiate_and_mint_weird_data() { external_link: Some("https://ark.pass".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(), + payment_address: collection_owner_addr, + share: Decimal::bps(1000), }), start_trading_time: Some(Timestamp::from_seconds(42)), }), @@ -852,13 +850,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://ark.pass".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(), @@ -873,27 +890,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: Some(CollectionExtension { - description: "description".to_string(), - explicit_content: Some(false), - external_link: Some("https://ark.pass".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)), - }), + extension: collection_extension.clone(), num_tokens: Some(1), }) .unwrap(), @@ -946,7 +948,7 @@ fn test_do_instantiate_and_mint() { CollectionInfoAndExtensionResponse { name: "ark".to_string(), symbol: "protocol".to_string(), - extension: None, + extension: collection_extension, updated_at: contract_info.updated_at, } ); @@ -1625,6 +1627,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(), @@ -1639,10 +1642,7 @@ 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(), @@ -1653,10 +1653,8 @@ fn test_do_instantiate_and_mint_no_instantiate() { external_link: Some("https://ark.pass".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(), + payment_address: collection_owner_addr, + share: Decimal::bps(1000), }), start_trading_time: Some(Timestamp::from_seconds(42)), }), From 5b5f60c7f2c95f2bb38ddd7e495f170963c80ed6 Mon Sep 17 00:00:00 2001 From: mr-t Date: Mon, 12 Aug 2024 18:28:42 +0200 Subject: [PATCH 08/21] use cw721 admin as creator --- contracts/sg-ics721/src/execute.rs | 21 +++++++++++---- .../src/testing/integration_tests.rs | 22 +++++++--------- packages/ics721/src/execute.rs | 26 +++++++++++++------ packages/ics721/src/testing/contract.rs | 8 +++--- .../ics721/src/testing/integration_tests.rs | 18 ++++++++----- 5 files changed, 60 insertions(+), 35 deletions(-) diff --git a/contracts/sg-ics721/src/execute.rs b/contracts/sg-ics721/src/execute.rs index e0be7817..7eaebe27 100644 --- a/contracts/sg-ics721/src/execute.rs +++ b/contracts/sg-ics721/src/execute.rs @@ -49,12 +49,23 @@ impl Ics721Execute for SgIcs721Contract { })) } - 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 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(), @@ -63,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: creator.clone(), + 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, @@ -85,7 +96,7 @@ impl Ics721Execute for SgIcs721Contract { let admin_or_creator = admin.unwrap_or(creator.clone()); if let Some(collection_info_extension_msg) = collection_data.extension.map(|ext| sg721::CollectionInfo { - creator: creator.clone(), + creator: cw721_admin_or_ics721_admin_or_ics721_creator.clone(), description: ext.description, image: ext.image, external_link: ext.external_link, diff --git a/contracts/sg-ics721/src/testing/integration_tests.rs b/contracts/sg-ics721/src/testing/integration_tests.rs index fedc0609..8723e3f6 100644 --- a/contracts/sg-ics721/src/testing/integration_tests.rs +++ b/contracts/sg-ics721/src/testing/integration_tests.rs @@ -378,7 +378,7 @@ impl Test { description: "description".to_string(), image: STARGAZE_ICON_PLACEHOLDER.to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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(), @@ -643,7 +643,7 @@ fn test_do_instantiate_and_mint_weird_data() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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( @@ -874,7 +874,7 @@ fn test_do_instantiate_and_mint() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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( @@ -949,11 +949,10 @@ 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(), + creator: test.admin_and_pauser.clone().unwrap(), description: "description".to_string(), image: "https://ark.pass/image.png".to_string(), - external_link: Some("https://ark.pass".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 { @@ -1916,7 +1915,7 @@ fn test_do_instantiate_and_mint_no_instantiate() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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( @@ -1994,11 +1993,10 @@ fn test_do_instantiate_and_mint_no_instantiate() { assert_eq!( collection_info, CollectionInfoResponse { - // creator based on owner from collection in soure chain - creator: test.app.api().addr_make(ICS721_CREATOR).to_string(), + creator: test.admin_and_pauser.clone().unwrap(), description: "description".to_string(), image: "https://ark.pass/image.png".to_string(), - external_link: Some("https://ark.pass".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 { @@ -2059,7 +2057,7 @@ fn test_do_instantiate_and_mint_permissions() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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( @@ -2321,7 +2319,7 @@ fn test_receive_nft() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + external_link: Some("https://interchain.arkprotocol.io".to_string()), image: STARGAZE_ICON_PLACEHOLDER.to_string(), royalty_info: Some(RoyaltyInfo { payment_address: test.source_cw721_owner, diff --git a/packages/ics721/src/execute.rs b/packages/ics721/src/execute.rs index d4642e48..50ec5f8e 100644 --- a/packages/ics721/src/execute.rs +++ b/packages/ics721/src/execute.rs @@ -580,14 +580,14 @@ where }; CLASS_ID_AND_NFT_CONTRACT_INFO.save(deps.storage, &class.id, &class_id_info)?; - let admin = ADMIN_USED_FOR_CW721 + let cw721_admin = ADMIN_USED_FOR_CW721 .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 @@ -602,18 +602,29 @@ 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, 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 = cw721_metadata_onchain::InstantiateMsg { name: class.id.clone().into(), symbol: class.id.clone().into(), - collection_info_extension: None, // TODO consider class data in NonFungibleTokenPacketData - creator: Some(creator.clone()), // TODO maybe better using cw721 admin? + 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.clone()), }; @@ -626,7 +637,6 @@ where if let Some(collection_data) = collection_data { instantiate_msg.name = collection_data.name; instantiate_msg.symbol = collection_data.symbol; - let admin_or_creator = admin.unwrap_or(creator.clone()); let collection_info_extension_msg = collection_data .extension @@ -637,7 +647,7 @@ where explicit_content: ext.explicit_content, start_trading_time: ext.start_trading_time, royalty_info: ext.royalty_info.map(|r| RoyaltyInfoResponse { - payment_address: admin_or_creator, // r.payment_address cant be used, since it is from another chain + 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, }), }); diff --git a/packages/ics721/src/testing/contract.rs b/packages/ics721/src/testing/contract.rs index 186e65ba..91fe4de1 100644 --- a/packages/ics721/src/testing/contract.rs +++ b/packages/ics721/src/testing/contract.rs @@ -133,7 +133,7 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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()), @@ -154,7 +154,7 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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()), @@ -308,7 +308,7 @@ fn test_receive_nft() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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()), @@ -565,7 +565,7 @@ fn test_receive_sets_uri() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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()), diff --git a/packages/ics721/src/testing/integration_tests.rs b/packages/ics721/src/testing/integration_tests.rs index 0f5953ee..bba9f6d5 100644 --- a/packages/ics721/src/testing/integration_tests.rs +++ b/packages/ics721/src/testing/integration_tests.rs @@ -381,7 +381,7 @@ impl Test { collection_info_extension: Some(CollectionExtensionMsg { description: Some("description".to_string()), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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(), @@ -679,7 +679,9 @@ fn test_do_instantiate_and_mint_weird_data() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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, @@ -868,7 +870,7 @@ fn test_do_instantiate_and_mint() { let collection_extension = Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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(), @@ -1650,7 +1652,9 @@ fn test_do_instantiate_and_mint_no_instantiate() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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, @@ -1768,7 +1772,9 @@ fn test_do_instantiate_and_mint_permissions() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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( @@ -2022,7 +2028,7 @@ fn test_receive_nft() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://ark.pass".to_string()), + 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(), From 4087a78fea550c7ded512d11ee9de04396eab949 Mon Sep 17 00:00:00 2001 From: mr-t Date: Mon, 12 Aug 2024 18:29:04 +0200 Subject: [PATCH 09/21] ts-relayer: instantiate with collection info --- ts-relayer-tests/src/ics721.spec.ts | 543 ++++++++-------------------- 1 file changed, 141 insertions(+), 402 deletions(-) diff --git a/ts-relayer-tests/src/ics721.spec.ts b/ts-relayer-tests/src/ics721.spec.ts index cfbf65ae..ac49a86f 100644 --- a/ts-relayer-tests/src/ics721.spec.ts +++ b/ts-relayer-tests/src/ics721.spec.ts @@ -1,19 +1,11 @@ -import { CosmWasmSigner } from "@confio/relayer"; -import { fromUtf8 } from "@cosmjs/encoding"; -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 { - adminCleanAndBurnNft, - adminCleanAndUnescrowNft, - incomingChannels, - migrate, - migrateIncomingProxy, - nftContracts, - outgoingChannels, -} from "./ics721-utils"; +import { CosmWasmSigner } from '@confio/relayer'; +import { fromUtf8 } from '@cosmjs/encoding'; +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 { adminCleanAndBurnNft, adminCleanAndUnescrowNft, incomingChannels, migrate, migrateIncomingProxy, nftContracts, outgoingChannels } from './ics721-utils'; import { assertAckErrors, assertAckSuccess, @@ -26,7 +18,7 @@ import { setupWasmClient, uploadAndInstantiate, uploadAndInstantiateAll, -} from "./utils"; +} from './utils'; interface TestContext { wasmClient: CosmWasmSigner; @@ -53,12 +45,11 @@ interface TestContext { const test = anyTest as TestFn; -const WASM_FILE_CW721 = "./internal/cw721_metadata_onchain_v0.19.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"; -const WASM_FILE_CW_ICS721_ICS721 = "./internal/ics721_base.wasm"; -const MALICIOUS_CW721 = "./internal/cw721_tester.wasm"; +const WASM_FILE_CW721 = './internal/cw721_metadata_onchain_v0.19.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'; +const WASM_FILE_CW_ICS721_ICS721 = './internal/ics721_base.wasm'; +const MALICIOUS_CW721 = './internal/cw721_tester.wasm'; const standardSetup = async (t: ExecutionContext) => { t.context.wasmClient = await setupWasmClient(MNEMONIC); @@ -73,9 +64,21 @@ const standardSetup = async (t: ExecutionContext) => { cw721: { path: WASM_FILE_CW721, instantiateMsg: { - name: "ark", - symbol: "ark", + name: 'ark', + symbol: 'ark', + collection_info_extension: { + description: 'description', + image: 'https://ark.pass/image.png', + external_link: 'https://interchain.arkprotocol.io', + explicit_content: false, + royalty_info: { + payment_address: t.context.wasmClient.senderAddress, + share: 0.1, + }, + }, + creator: wasmClient.senderAddress, minter: wasmClient.senderAddress, + withdraw_address: wasmClient.senderAddress, }, }, cw721IncomingProxy: { @@ -95,10 +98,21 @@ const standardSetup = async (t: ExecutionContext) => { cw721: { path: WASM_FILE_CW721, instantiateMsg: { - name: "ark", - symbol: "ark", + name: 'ark', + symbol: 'ark', + collection_info_extension: { + description: 'description', + image: 'https://ark.pass/image.png', + external_link: 'https://interchain.arkprotocol.io', + explicit_content: false, + royalty_info: { + payment_address: t.context.osmoClient.senderAddress, + share: 0.1, + }, + }, creator: osmoClient.senderAddress, minter: osmoClient.senderAddress, + withdraw_address: osmoClient.senderAddress, }, }, cw721IncomingProxy: { @@ -115,76 +129,40 @@ const standardSetup = async (t: ExecutionContext) => { }, }; - const info = await uploadAndInstantiateAll( - wasmClient, - osmoClient, - wasmContracts, - osmoContracts - ); + const info = await uploadAndInstantiateAll(wasmClient, osmoClient, wasmContracts, osmoContracts); const wasmCw721Id = info.wasmContractInfos.cw721.codeId; const osmoCw721Id = info.osmoContractInfos.cw721.codeId; - const wasmCw721IncomingProxyId = - info.wasmContractInfos.cw721IncomingProxy.codeId; + const wasmCw721IncomingProxyId = info.wasmContractInfos.cw721IncomingProxy.codeId; t.context.wasmCw721IncomingProxyId = wasmCw721IncomingProxyId; - const osmoCw721IncomingProxyId = - info.osmoContractInfos.cw721IncomingProxy.codeId; + const osmoCw721IncomingProxyId = info.osmoContractInfos.cw721IncomingProxy.codeId; const wasmIcs721Id = info.wasmContractInfos.ics721.codeId; const osmoIcs721Id = info.osmoContractInfos.ics721.codeId; - const wasmCw721OutgoingProxyId = - info.wasmContractInfos.cw721OutgoingProxy.codeId; - const osmoCw721OutgoingProxyId = - info.osmoContractInfos.cw721OutgoingProxy.codeId; + const wasmCw721OutgoingProxyId = info.wasmContractInfos.cw721OutgoingProxy.codeId; + const osmoCw721OutgoingProxyId = info.osmoContractInfos.cw721OutgoingProxy.codeId; t.context.wasmCw721 = info.wasmContractInfos.cw721.address as string; t.context.osmoCw721 = info.osmoContractInfos.cw721.address as string; t.log(`instantiating wasm ICS721 contract (${wasmIcs721Id})`); - const { contractAddress: wasmIcs721 } = await instantiateContract( - wasmClient, - wasmIcs721Id, - { cw721_base_code_id: wasmCw721Id }, - "label ics721" - ); + const { contractAddress: wasmIcs721 } = await instantiateContract(wasmClient, wasmIcs721Id, { cw721_base_code_id: wasmCw721Id }, 'label ics721'); t.log(`- wasm ICS721 contract address: ${wasmIcs721}`); t.context.wasmIcs721 = wasmIcs721; t.log(`instantiating osmo ICS721 contract (${osmoIcs721Id})`); - const { contractAddress: osmoIcs721 } = await instantiateContract( - osmoClient, - osmoIcs721Id, - { cw721_base_code_id: osmoCw721Id }, - "label ics721" - ); + const { contractAddress: osmoIcs721 } = await instantiateContract(osmoClient, osmoIcs721Id, { cw721_base_code_id: osmoCw721Id }, 'label ics721'); t.log(`- osmo ICS721 contract address: ${osmoIcs721}`); t.context.osmoIcs721 = osmoIcs721; - t.log( - `creating IBC connection and channel between ${wasmIcs721} <-> ${osmoIcs721}` - ); - const channelInfo = await createIbcConnectionAndChannel( - wasmClient, - osmoClient, - wasmIcs721, - osmoIcs721, - Order.ORDER_UNORDERED, - "ics721-1" - ); - t.log( - `- channel for incoming proxy on both chains: ${JSON.stringify( - channelInfo.channel, - bigIntReplacer, - 2 - )}` - ); + t.log(`creating IBC connection and channel between ${wasmIcs721} <-> ${osmoIcs721}`); + const channelInfo = await createIbcConnectionAndChannel(wasmClient, osmoClient, wasmIcs721, osmoIcs721, Order.ORDER_UNORDERED, 'ics721-1'); + t.log(`- channel for incoming proxy on both chains: ${JSON.stringify(channelInfo.channel, bigIntReplacer, 2)}`); t.context.channel = channelInfo; - t.log( - `instantiating wasm cw721-incoming-proxy (${wasmCw721IncomingProxyId}) with channel ${channelInfo.channel.src.channelId}` - ); + t.log(`instantiating wasm cw721-incoming-proxy (${wasmCw721IncomingProxyId}) with channel ${channelInfo.channel.src.channelId}`); const { contractAddress: wasmCw721IncomingProxy } = await instantiateContract( wasmClient, wasmCw721IncomingProxyId, @@ -192,55 +170,33 @@ const standardSetup = async (t: ExecutionContext) => { origin: wasmIcs721, channels: [channelInfo.channel.src.channelId], }, - "label incoming proxy" + 'label incoming proxy' ); t.log(`- wasm cw721-incoming-proxy address: ${wasmCw721IncomingProxy}`); t.context.wasmCw721IncomingProxy = wasmCw721IncomingProxy; - t.log( - `migrate ${wasmIcs721} contract with incoming proxy ${wasmCw721IncomingProxy}` - ); + t.log(`migrate ${wasmIcs721} contract with incoming proxy ${wasmCw721IncomingProxy}`); await migrate(wasmClient, wasmIcs721, wasmIcs721Id, wasmCw721IncomingProxy); - const onlyOsmoIncomingChannelInfo = await createIbcConnectionAndChannel( - wasmClient, - osmoClient, - wasmIcs721, - osmoIcs721, - Order.ORDER_UNORDERED, - "ics721-1" - ); - t.log( - `- channel for incoming proxy only on wasm chain: ${JSON.stringify( - onlyOsmoIncomingChannelInfo.channel, - bigIntReplacer, - 2 - )}` - ); + const onlyOsmoIncomingChannelInfo = await createIbcConnectionAndChannel(wasmClient, osmoClient, wasmIcs721, osmoIcs721, Order.ORDER_UNORDERED, 'ics721-1'); + t.log(`- channel for incoming proxy only on wasm chain: ${JSON.stringify(onlyOsmoIncomingChannelInfo.channel, bigIntReplacer, 2)}`); t.context.onlyOsmoIncomingChannel = onlyOsmoIncomingChannelInfo; - t.log( - `instantiating osmo cw721-incoming-proxy (${osmoCw721IncomingProxyId}) with channel ${channelInfo.channel.dest.channelId}and ${onlyOsmoIncomingChannelInfo.channel.dest.channelId}` - ); + t.log(`instantiating osmo cw721-incoming-proxy (${osmoCw721IncomingProxyId}) with channel ${channelInfo.channel.dest.channelId}and ${onlyOsmoIncomingChannelInfo.channel.dest.channelId}`); const { contractAddress: osmoCw721IncomingProxy } = await instantiateContract( osmoClient, osmoCw721IncomingProxyId, { origin: osmoIcs721, - channels: [ - channelInfo.channel.dest.channelId, - onlyOsmoIncomingChannelInfo.channel.dest.channelId, - ], + channels: [channelInfo.channel.dest.channelId, onlyOsmoIncomingChannelInfo.channel.dest.channelId], }, - "label incoming proxy" + 'label incoming proxy' ); t.log(`- osmo cw721-incoming-proxy address: ${osmoCw721IncomingProxy}`); t.context.osmoCw721IncomingProxy = osmoCw721IncomingProxy; const per_block = 10; // use high rate limit to avoid test failures - t.log( - `instantiating wasm cw721-outgoing-proxy (${wasmCw721OutgoingProxyId}) with ${per_block} per blocks rate limit` - ); + t.log(`instantiating wasm cw721-outgoing-proxy (${wasmCw721OutgoingProxyId}) with ${per_block} per blocks rate limit`); const { contractAddress: wasmCw721OutgoingProxy } = await instantiateContract( wasmClient, wasmCw721OutgoingProxyId, @@ -248,14 +204,12 @@ const standardSetup = async (t: ExecutionContext) => { origin: wasmIcs721, rate_limit: { per_block }, }, - "label outgoing proxy" + 'label outgoing proxy' ); t.log(`- wasm cw721-outgoing-proxy address: ${wasmCw721OutgoingProxy}`); t.context.wasmCw721OutgoingProxy = wasmCw721OutgoingProxy; - t.log( - `instantiating osmo cw721-outgoing-proxy (${osmoCw721OutgoingProxyId}) with ${per_block} per blocks rate limit` - ); + t.log(`instantiating osmo cw721-outgoing-proxy (${osmoCw721OutgoingProxyId}) with ${per_block} per blocks rate limit`); const { contractAddress: osmoCw721OutgoingProxy } = await instantiateContract( osmoClient, osmoCw721OutgoingProxyId, @@ -263,57 +217,26 @@ const standardSetup = async (t: ExecutionContext) => { origin: osmoIcs721, rate_limit: { per_block }, }, - "label outgoing proxy" + 'label outgoing proxy' ); t.log(`- osmo cw721-outgoing-proxy address: ${osmoCw721OutgoingProxy}`); t.context.osmoCw721OutgoingProxy = osmoCw721OutgoingProxy; - t.log( - `migrate ${wasmIcs721} contract with incoming (${wasmCw721IncomingProxy}) and outgoing proxy (${wasmCw721OutgoingProxy})` - ); - await migrate( - wasmClient, - wasmIcs721, - wasmIcs721Id, - wasmCw721IncomingProxy, - wasmCw721OutgoingProxy - ); + t.log(`migrate ${wasmIcs721} contract with incoming (${wasmCw721IncomingProxy}) and outgoing proxy (${wasmCw721OutgoingProxy})`); + await migrate(wasmClient, wasmIcs721, wasmIcs721Id, wasmCw721IncomingProxy, wasmCw721OutgoingProxy); - t.log( - `migrate ${osmoIcs721} contract with incoming (${osmoCw721IncomingProxy}) and outgoing proxy (${osmoCw721OutgoingProxy})` - ); - await migrate( - osmoClient, - osmoIcs721, - osmoIcs721Id, - osmoCw721IncomingProxy, - osmoCw721OutgoingProxy - ); + t.log(`migrate ${osmoIcs721} contract with incoming (${osmoCw721IncomingProxy}) and outgoing proxy (${osmoCw721OutgoingProxy})`); + await migrate(osmoClient, osmoIcs721, osmoIcs721Id, osmoCw721IncomingProxy, osmoCw721OutgoingProxy); - t.log( - `creating another IBC connection and channel between wasm and osmo (${wasmIcs721} <-> ${osmoIcs721})` - ); - const otherChannelInfo = await createIbcConnectionAndChannel( - wasmClient, - osmoClient, - wasmIcs721, - osmoIcs721, - Order.ORDER_UNORDERED, - "ics721-1" - ); - t.log( - `- other channel not WLed for incoming proxy: ${JSON.stringify( - otherChannelInfo.channel, - bigIntReplacer, - 2 - )}` - ); + t.log(`creating another IBC connection and channel between wasm and osmo (${wasmIcs721} <-> ${osmoIcs721})`); + const otherChannelInfo = await createIbcConnectionAndChannel(wasmClient, osmoClient, wasmIcs721, osmoIcs721, Order.ORDER_UNORDERED, 'ics721-1'); + t.log(`- other channel not WLed for incoming proxy: ${JSON.stringify(otherChannelInfo.channel, bigIntReplacer, 2)}`); t.context.otherChannel = otherChannelInfo; t.pass(); }; -test.serial("transfer NFT: wasmd -> osmo", async (t) => { +test.serial('transfer NFT: wasmd -> osmo', async (t) => { await standardSetup(t); const { @@ -332,7 +255,7 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { onlyOsmoIncomingChannel, } = t.context; - let tokenId = "1"; + let tokenId = '1'; await mint(wasmClient, wasmCw721, tokenId, wasmAddr, undefined); // assert token is minted let tokenOwner = await ownerOf(wasmClient, wasmCw721, tokenId); @@ -351,17 +274,11 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { }, }, }; - let transferResponse = await sendNft( - wasmClient, - wasmCw721, - wasmCw721OutgoingProxy, - ibcMsg, - tokenId - ); + let transferResponse = await sendNft(wasmClient, wasmCw721, wasmCw721OutgoingProxy, ibcMsg, tokenId); t.truthy(transferResponse); // Relay and verify we got a success - t.log("relaying packets"); + t.log('relaying packets'); let info = await channel.link.relayAll(); assertAckSuccess(info.acksFromA); @@ -398,7 +315,7 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { tokenId ); t.truthy(transferResponse); - t.log("relaying packets"); + t.log('relaying packets'); // Verify we got a success info = await channel.link.relayAll(); @@ -413,39 +330,19 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { // ==== 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"; + tokenId = '2'; await mint(wasmClient, wasmCw721, tokenId, wasmAddr, undefined); // assert token is minted tokenOwner = await ownerOf(wasmClient, wasmCw721, 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 - ); + 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, @@ -457,32 +354,17 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { }, }, }; - transferResponse = await sendNft( - wasmClient, - wasmCw721, - wasmCw721OutgoingProxy, - ibcMsg, - tokenId - ); + transferResponse = await sendNft(wasmClient, wasmCw721, wasmCw721OutgoingProxy, ibcMsg, tokenId); t.truthy(transferResponse); // Relay and verify we got an error - t.log("relaying packets"); + 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 - ); + const afterWasmOutgoingClassTokenToChannelList = await outgoingChannels(wasmClient, wasmIcs721); + const afterWasmIncomingClassTokenToChannelList = await incomingChannels(wasmClient, wasmIcs721); + const afterWasmNftContractsToClassIdList = await nftContracts(wasmClient, wasmIcs721); t.deepEqual( beforeWasmOutgoingClassTokenToChannelList, afterWasmOutgoingClassTokenToChannelList, @@ -504,18 +386,9 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { - 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 - ); + const afterOsmoOutgoingClassTokenToChannelList = await outgoingChannels(osmoClient, osmoIcs721); + const afterOsmoIncomingClassTokenToChannelList = await incomingChannels(osmoClient, osmoIcs721); + const afterOsmoNftContractsToClassIdList = await nftContracts(osmoClient, osmoIcs721); t.deepEqual( beforeOsmoOutgoingClassTokenToChannelList, afterOsmoOutgoingClassTokenToChannelList, @@ -543,16 +416,14 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { 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"; + tokenId = '3'; await mint(wasmClient, wasmCw721, tokenId, wasmAddr, undefined); // assert token is minted tokenOwner = await ownerOf(wasmClient, wasmCw721, tokenId); t.is(wasmAddr, tokenOwner.owner); // test transfer NFT to osmo chain - t.log( - `transfering to osmo chain via ${onlyOsmoIncomingChannel.channel.src.channelId}` - ); + t.log(`transfering to osmo chain via ${onlyOsmoIncomingChannel.channel.src.channelId}`); ibcMsg = { receiver: osmoAddr, channel_id: onlyOsmoIncomingChannel.channel.src.channelId, @@ -563,36 +434,18 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { }, }, }; - transferResponse = await sendNft( - wasmClient, - wasmCw721, - wasmCw721OutgoingProxy, - ibcMsg, - tokenId - ); + transferResponse = await sendNft(wasmClient, wasmCw721, wasmCw721OutgoingProxy, ibcMsg, tokenId); t.truthy(transferResponse); // Relay and verify we got a success - t.log("relaying packets"); + 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 - )}` - ); + 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}/${t.context.wasmCw721}`; @@ -616,9 +469,7 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { 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}` - ); + t.log(`transfering back to wasm chain via unknown ${onlyOsmoIncomingChannel.channel.dest.channelId}`); transferResponse = await sendNft( osmoClient, osmoCw721, @@ -641,7 +492,7 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { t.is(osmoIcs721, tokenOwner.owner); // Relay and verify we got an error - t.log("relaying packets"); + t.log('relaying packets'); info = await onlyOsmoIncomingChannel.link.relayAll(); for (const ack of info.acksFromB) { const parsed = JSON.parse(fromUtf8(ack.acknowledgement)); @@ -657,23 +508,11 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { 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, - ] - ); + 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}` - ); + t.log(`transfering back to wasm chain via WLed ${onlyOsmoIncomingChannel.channel.dest.channelId}`); transferResponse = await sendNft( osmoClient, osmoCw721, @@ -701,15 +540,10 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { // 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 - )}` - ); + t.true(nftContractsToClassIdList.length === 1, `nft contracts must have exactly one entry: ${JSON.stringify(nftContractsToClassIdList)}`); // Relay and verify success - t.log("relaying packets"); + t.log('relaying packets'); info = await onlyOsmoIncomingChannel.link.relayAll(); for (const ack of info.acksFromB) { const parsed = JSON.parse(fromUtf8(ack.acknowledgement)); @@ -718,16 +552,8 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { 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 - )}` - ); + 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); @@ -736,33 +562,18 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { // 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 - )}` - ); + 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, tokenId); t.is(wasmAddr, tokenOwner.owner); }); -test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { +test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { await standardSetup(t); - const { - wasmClient, - wasmAddr, - wasmCw721, - wasmIcs721, - wasmCw721OutgoingProxy, - osmoClient, - osmoAddr, - osmoIcs721, - channel, - } = t.context; + const { wasmClient, wasmAddr, wasmCw721, wasmIcs721, wasmCw721OutgoingProxy, osmoClient, osmoAddr, osmoIcs721, channel } = t.context; - const tokenId = "1"; + const tokenId = '1'; await mint(wasmClient, wasmCw721, tokenId, wasmAddr, undefined); // assert token is minted let tokenOwner = await ownerOf(wasmClient, wasmCw721, tokenId); @@ -781,17 +592,11 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { }, }, }; - const transferResponse = await sendNft( - wasmClient, - wasmCw721, - wasmCw721OutgoingProxy, - ibcMsg, - tokenId - ); + const transferResponse = await sendNft(wasmClient, wasmCw721, wasmCw721OutgoingProxy, ibcMsg, tokenId); t.truthy(transferResponse); // Relay and verify we got a success - t.log("relaying packets"); + t.log('relaying packets'); const info = await channel.link.relayAll(); assertAckSuccess(info.acksFromA); @@ -809,10 +614,7 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); t.is(osmoAddr, tokenOwner.owner); - const beforeWasmOutgoingClassTokenToChannelList = await outgoingChannels( - wasmClient, - wasmIcs721 - ); + const beforeWasmOutgoingClassTokenToChannelList = await outgoingChannels(wasmClient, wasmIcs721); // there should be one outgoing channel entry t.deepEqual( beforeWasmOutgoingClassTokenToChannelList, @@ -821,10 +623,7 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { - before: ${JSON.stringify(beforeWasmOutgoingClassTokenToChannelList)}` ); // no incoming channel entry - const beforeWasmIncomingClassTokenToChannelList = await incomingChannels( - wasmClient, - wasmIcs721 - ); + const beforeWasmIncomingClassTokenToChannelList = await incomingChannels(wasmClient, wasmIcs721); t.deepEqual( beforeWasmIncomingClassTokenToChannelList, [], @@ -832,10 +631,7 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { - before: ${JSON.stringify(beforeWasmIncomingClassTokenToChannelList)}` ); // one nft contract entry - const beforeWasmNftContractsToClassIdList = await nftContracts( - wasmClient, - wasmIcs721 - ); + const beforeWasmNftContractsToClassIdList = await nftContracts(wasmClient, wasmIcs721); t.deepEqual( beforeWasmNftContractsToClassIdList, [[wasmCw721, wasmCw721]], @@ -844,10 +640,7 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { ); // no outgoing channel entry - const beforeOsmoOutgoingClassTokenToChannelList = await outgoingChannels( - osmoClient, - osmoIcs721 - ); + const beforeOsmoOutgoingClassTokenToChannelList = await outgoingChannels(osmoClient, osmoIcs721); t.deepEqual( beforeOsmoOutgoingClassTokenToChannelList, [], @@ -855,10 +648,7 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { - before: ${JSON.stringify(beforeOsmoOutgoingClassTokenToChannelList)}` ); // there should be one incoming channel entry - const beforeOsmoIncomingClassTokenToChannelList = await incomingChannels( - osmoClient, - osmoIcs721 - ); + const beforeOsmoIncomingClassTokenToChannelList = await incomingChannels(osmoClient, osmoIcs721); t.deepEqual( beforeOsmoIncomingClassTokenToChannelList, [[[osmoClassId, tokenId], channel.channel.dest.channelId]], @@ -866,10 +656,7 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { - before: ${JSON.stringify(beforeOsmoIncomingClassTokenToChannelList)}` ); // one nft contract entry - const beforeOsmoNftContractsToClassIdList = await nftContracts( - osmoClient, - osmoIcs721 - ); + const beforeOsmoNftContractsToClassIdList = await nftContracts(osmoClient, osmoIcs721); t.deepEqual( beforeOsmoNftContractsToClassIdList, [[osmoClassId, osmoCw721]], @@ -879,19 +666,9 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { // ==== test unescrow NFT on wasm chain ==== t.log(`unescrow NFT on wasm chain`); - await adminCleanAndUnescrowNft( - wasmClient, - wasmIcs721, - wasmAddr, - tokenId, - wasmCw721, - wasmCw721 - ); + await adminCleanAndUnescrowNft(wasmClient, wasmIcs721, wasmAddr, tokenId, wasmCw721, wasmCw721); // there should be no outgoing channel entry - const afterWasmOutgoingClassTokenToChannelList = await outgoingChannels( - wasmClient, - wasmIcs721 - ); + const afterWasmOutgoingClassTokenToChannelList = await outgoingChannels(wasmClient, wasmIcs721); t.deepEqual( afterWasmOutgoingClassTokenToChannelList, [], @@ -908,22 +685,12 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { const response = await approve(osmoClient, osmoCw721, osmoIcs721, tokenId); t.log(`- response: ${JSON.stringify(response, bigIntReplacer, 2)}`); t.log(`burn NFT on osmo chain`); - await adminCleanAndBurnNft( - osmoClient, - osmoIcs721, - osmoAddr, - tokenId, - osmoClassId, - osmoCw721 - ); + await adminCleanAndBurnNft(osmoClient, osmoIcs721, osmoAddr, tokenId, osmoClassId, osmoCw721); t.log(`- response: ${JSON.stringify(response, bigIntReplacer, 2)}`); allNFTs = await allTokens(osmoClient, osmoCw721); t.is(allNFTs.tokens.length, 0); // there should be no incoming channel entry - const afterOsmoIncomingClassTokenToChannelList = await incomingChannels( - osmoClient, - osmoIcs721 - ); + const afterOsmoIncomingClassTokenToChannelList = await incomingChannels(osmoClient, osmoIcs721); t.deepEqual( afterOsmoIncomingClassTokenToChannelList, [], @@ -932,30 +699,20 @@ test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { ); }); -test.serial("malicious NFT", async (t) => { +test.serial('malicious NFT', async (t) => { await standardSetup(t); - const { - wasmClient, - wasmAddr, - wasmIcs721, - wasmCw721OutgoingProxy, - osmoClient, - osmoAddr, - osmoIcs721, - osmoCw721OutgoingProxy, - channel, - } = t.context; - const tokenId = "1"; + const { wasmClient, wasmAddr, wasmIcs721, wasmCw721OutgoingProxy, osmoClient, osmoAddr, osmoIcs721, osmoCw721OutgoingProxy, channel } = t.context; + const tokenId = '1'; // instantiate malicious cw721 contract const res = await uploadAndInstantiate(wasmClient, { cw721_gas_tester: { path: MALICIOUS_CW721, instantiateMsg: { - name: "evil", - symbol: "evil", + name: 'evil', + symbol: 'evil', minter: wasmClient.senderAddress, - banned_recipient: "banned_recipient", // panic every time, on back transfer, when ICS721 tries to transfer/unescrow NFT to this address + banned_recipient: 'banned_recipient', // panic every time, on back transfer, when ICS721 tries to transfer/unescrow NFT to this address }, }, }); @@ -963,7 +720,7 @@ test.serial("malicious NFT", async (t) => { // ==== test malicious NFT transfer to osmo chain ==== await mint(wasmClient, cw721, tokenId, wasmAddr, undefined); - t.log("transferring to osmo chain"); + t.log('transferring to osmo chain'); let ibcMsg = { receiver: osmoAddr, channel_id: channel.channel.src.channelId, @@ -974,16 +731,10 @@ test.serial("malicious NFT", async (t) => { }, }, }; - let transferResponse = await sendNft( - wasmClient, - cw721, - wasmCw721OutgoingProxy, - ibcMsg, - tokenId - ); + let transferResponse = await sendNft(wasmClient, cw721, wasmCw721OutgoingProxy, ibcMsg, tokenId); t.truthy(transferResponse); - t.log("relaying packets"); + t.log('relaying packets'); let info = await channel.link.relayAll(); assertAckSuccess(info.acksFromB); @@ -999,9 +750,9 @@ test.serial("malicious NFT", async (t) => { t.is(osmoAddr, tokenOwner.owner); // ==== test malicious NFT back transfer to banned recipient on wasm chain ==== - t.log("transferring back to wasm chain to banned recipient"); + t.log('transferring back to wasm chain to banned recipient'); ibcMsg = { - receiver: "banned_recipient", + receiver: 'banned_recipient', channel_id: channel.channel.dest.channelId, timeout: { block: { @@ -1010,20 +761,14 @@ test.serial("malicious NFT", async (t) => { }, }, }; - transferResponse = await sendNft( - osmoClient, - osmoCw721, - osmoCw721OutgoingProxy, - ibcMsg, - tokenId - ); + transferResponse = await sendNft(osmoClient, osmoCw721, osmoCw721OutgoingProxy, ibcMsg, tokenId); t.truthy(transferResponse); // before relay NFT escrowed by ICS721 tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); t.is(osmoIcs721, tokenOwner.owner); - t.log("relaying packets"); - let pending = await channel.link.getPendingPackets("B"); + t.log('relaying packets'); + let pending = await channel.link.getPendingPackets('B'); t.is(pending.length, 1); // Despite the transfer panicking, a fail ack should be returned. info = await channel.link.relayAll(); @@ -1034,7 +779,7 @@ test.serial("malicious NFT", async (t) => { t.log(`NFT #${tokenId} returned to owner`); // ==== test malicious NFT transfer to regular recipient wasm chain ==== - t.log("transferring back to wasm chain to recipient", wasmAddr); + t.log('transferring back to wasm chain to recipient', wasmAddr); ibcMsg = { receiver: wasmAddr, channel_id: channel.channel.dest.channelId, @@ -1046,18 +791,12 @@ test.serial("malicious NFT", async (t) => { }, }; - transferResponse = await sendNft( - osmoClient, - osmoCw721, - osmoCw721OutgoingProxy, - ibcMsg, - tokenId - ); + transferResponse = await sendNft(osmoClient, osmoCw721, osmoCw721OutgoingProxy, ibcMsg, tokenId); t.truthy(transferResponse); // Relay and verify we got a success - t.log("relaying packets"); - pending = await channel.link.getPendingPackets("B"); + t.log('relaying packets'); + pending = await channel.link.getPendingPackets('B'); t.is(pending.length, 1); info = await channel.link.relayAll(); assertAckSuccess(info.acksFromB); From bc524788ec8a4be9d864cc999221c412dc13ba05 Mon Sep 17 00:00:00 2001 From: mr-t Date: Tue, 13 Aug 2024 11:32:32 +0200 Subject: [PATCH 10/21] fix --- ts-relayer-tests/src/ics721.spec.ts | 536 +++++++++++++++++++++------- 1 file changed, 410 insertions(+), 126 deletions(-) diff --git a/ts-relayer-tests/src/ics721.spec.ts b/ts-relayer-tests/src/ics721.spec.ts index ac49a86f..23ab7696 100644 --- a/ts-relayer-tests/src/ics721.spec.ts +++ b/ts-relayer-tests/src/ics721.spec.ts @@ -1,11 +1,19 @@ -import { CosmWasmSigner } from '@confio/relayer'; -import { fromUtf8 } from '@cosmjs/encoding'; -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 { adminCleanAndBurnNft, adminCleanAndUnescrowNft, incomingChannels, migrate, migrateIncomingProxy, nftContracts, outgoingChannels } from './ics721-utils'; +import { CosmWasmSigner } from "@confio/relayer"; +import { fromUtf8 } from "@cosmjs/encoding"; +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 { + adminCleanAndBurnNft, + adminCleanAndUnescrowNft, + incomingChannels, + migrate, + migrateIncomingProxy, + nftContracts, + outgoingChannels, +} from "./ics721-utils"; import { assertAckErrors, assertAckSuccess, @@ -18,7 +26,7 @@ import { setupWasmClient, uploadAndInstantiate, uploadAndInstantiateAll, -} from './utils'; +} from "./utils"; interface TestContext { wasmClient: CosmWasmSigner; @@ -45,11 +53,12 @@ interface TestContext { const test = anyTest as TestFn; -const WASM_FILE_CW721 = './internal/cw721_metadata_onchain_v0.19.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'; -const WASM_FILE_CW_ICS721_ICS721 = './internal/ics721_base.wasm'; -const MALICIOUS_CW721 = './internal/cw721_tester.wasm'; +const WASM_FILE_CW721 = "./internal/cw721_metadata_onchain_v0.19.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"; +const WASM_FILE_CW_ICS721_ICS721 = "./internal/ics721_base.wasm"; +const MALICIOUS_CW721 = "./internal/cw721_tester.wasm"; const standardSetup = async (t: ExecutionContext) => { t.context.wasmClient = await setupWasmClient(MNEMONIC); @@ -64,16 +73,16 @@ 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', + description: "description", + image: "https://ark.pass/image.png", + external_link: "https://interchain.arkprotocol.io", explicit_content: false, royalty_info: { payment_address: t.context.wasmClient.senderAddress, - share: 0.1, + share: "0.1", }, }, creator: wasmClient.senderAddress, @@ -98,16 +107,16 @@ const standardSetup = async (t: ExecutionContext) => { 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', + description: "description", + image: "https://ark.pass/image.png", + external_link: "https://interchain.arkprotocol.io", explicit_content: false, royalty_info: { payment_address: t.context.osmoClient.senderAddress, - share: 0.1, + share: "0.1", }, }, creator: osmoClient.senderAddress, @@ -129,40 +138,76 @@ const standardSetup = async (t: ExecutionContext) => { }, }; - const info = await uploadAndInstantiateAll(wasmClient, osmoClient, wasmContracts, osmoContracts); + const info = await uploadAndInstantiateAll( + wasmClient, + osmoClient, + wasmContracts, + osmoContracts + ); const wasmCw721Id = info.wasmContractInfos.cw721.codeId; const osmoCw721Id = info.osmoContractInfos.cw721.codeId; - const wasmCw721IncomingProxyId = info.wasmContractInfos.cw721IncomingProxy.codeId; + const wasmCw721IncomingProxyId = + info.wasmContractInfos.cw721IncomingProxy.codeId; t.context.wasmCw721IncomingProxyId = wasmCw721IncomingProxyId; - const osmoCw721IncomingProxyId = info.osmoContractInfos.cw721IncomingProxy.codeId; + const osmoCw721IncomingProxyId = + info.osmoContractInfos.cw721IncomingProxy.codeId; const wasmIcs721Id = info.wasmContractInfos.ics721.codeId; const osmoIcs721Id = info.osmoContractInfos.ics721.codeId; - const wasmCw721OutgoingProxyId = info.wasmContractInfos.cw721OutgoingProxy.codeId; - const osmoCw721OutgoingProxyId = info.osmoContractInfos.cw721OutgoingProxy.codeId; + const wasmCw721OutgoingProxyId = + info.wasmContractInfos.cw721OutgoingProxy.codeId; + const osmoCw721OutgoingProxyId = + info.osmoContractInfos.cw721OutgoingProxy.codeId; t.context.wasmCw721 = info.wasmContractInfos.cw721.address as string; t.context.osmoCw721 = info.osmoContractInfos.cw721.address as string; t.log(`instantiating wasm ICS721 contract (${wasmIcs721Id})`); - const { contractAddress: wasmIcs721 } = await instantiateContract(wasmClient, wasmIcs721Id, { cw721_base_code_id: wasmCw721Id }, 'label ics721'); + const { contractAddress: wasmIcs721 } = await instantiateContract( + wasmClient, + wasmIcs721Id, + { cw721_base_code_id: wasmCw721Id }, + "label ics721" + ); t.log(`- wasm ICS721 contract address: ${wasmIcs721}`); t.context.wasmIcs721 = wasmIcs721; t.log(`instantiating osmo ICS721 contract (${osmoIcs721Id})`); - const { contractAddress: osmoIcs721 } = await instantiateContract(osmoClient, osmoIcs721Id, { cw721_base_code_id: osmoCw721Id }, 'label ics721'); + const { contractAddress: osmoIcs721 } = await instantiateContract( + osmoClient, + osmoIcs721Id, + { cw721_base_code_id: osmoCw721Id }, + "label ics721" + ); t.log(`- osmo ICS721 contract address: ${osmoIcs721}`); t.context.osmoIcs721 = osmoIcs721; - t.log(`creating IBC connection and channel between ${wasmIcs721} <-> ${osmoIcs721}`); - const channelInfo = await createIbcConnectionAndChannel(wasmClient, osmoClient, wasmIcs721, osmoIcs721, Order.ORDER_UNORDERED, 'ics721-1'); - t.log(`- channel for incoming proxy on both chains: ${JSON.stringify(channelInfo.channel, bigIntReplacer, 2)}`); + t.log( + `creating IBC connection and channel between ${wasmIcs721} <-> ${osmoIcs721}` + ); + const channelInfo = await createIbcConnectionAndChannel( + wasmClient, + osmoClient, + wasmIcs721, + osmoIcs721, + Order.ORDER_UNORDERED, + "ics721-1" + ); + t.log( + `- channel for incoming proxy on both chains: ${JSON.stringify( + channelInfo.channel, + bigIntReplacer, + 2 + )}` + ); t.context.channel = channelInfo; - t.log(`instantiating wasm cw721-incoming-proxy (${wasmCw721IncomingProxyId}) with channel ${channelInfo.channel.src.channelId}`); + t.log( + `instantiating wasm cw721-incoming-proxy (${wasmCw721IncomingProxyId}) with channel ${channelInfo.channel.src.channelId}` + ); const { contractAddress: wasmCw721IncomingProxy } = await instantiateContract( wasmClient, wasmCw721IncomingProxyId, @@ -170,33 +215,55 @@ const standardSetup = async (t: ExecutionContext) => { origin: wasmIcs721, channels: [channelInfo.channel.src.channelId], }, - 'label incoming proxy' + "label incoming proxy" ); t.log(`- wasm cw721-incoming-proxy address: ${wasmCw721IncomingProxy}`); t.context.wasmCw721IncomingProxy = wasmCw721IncomingProxy; - t.log(`migrate ${wasmIcs721} contract with incoming proxy ${wasmCw721IncomingProxy}`); + t.log( + `migrate ${wasmIcs721} contract with incoming proxy ${wasmCw721IncomingProxy}` + ); await migrate(wasmClient, wasmIcs721, wasmIcs721Id, wasmCw721IncomingProxy); - const onlyOsmoIncomingChannelInfo = await createIbcConnectionAndChannel(wasmClient, osmoClient, wasmIcs721, osmoIcs721, Order.ORDER_UNORDERED, 'ics721-1'); - t.log(`- channel for incoming proxy only on wasm chain: ${JSON.stringify(onlyOsmoIncomingChannelInfo.channel, bigIntReplacer, 2)}`); + const onlyOsmoIncomingChannelInfo = await createIbcConnectionAndChannel( + wasmClient, + osmoClient, + wasmIcs721, + osmoIcs721, + Order.ORDER_UNORDERED, + "ics721-1" + ); + t.log( + `- channel for incoming proxy only on wasm chain: ${JSON.stringify( + onlyOsmoIncomingChannelInfo.channel, + bigIntReplacer, + 2 + )}` + ); t.context.onlyOsmoIncomingChannel = onlyOsmoIncomingChannelInfo; - t.log(`instantiating osmo cw721-incoming-proxy (${osmoCw721IncomingProxyId}) with channel ${channelInfo.channel.dest.channelId}and ${onlyOsmoIncomingChannelInfo.channel.dest.channelId}`); + t.log( + `instantiating osmo cw721-incoming-proxy (${osmoCw721IncomingProxyId}) with channel ${channelInfo.channel.dest.channelId}and ${onlyOsmoIncomingChannelInfo.channel.dest.channelId}` + ); const { contractAddress: osmoCw721IncomingProxy } = await instantiateContract( osmoClient, osmoCw721IncomingProxyId, { origin: osmoIcs721, - channels: [channelInfo.channel.dest.channelId, onlyOsmoIncomingChannelInfo.channel.dest.channelId], + channels: [ + channelInfo.channel.dest.channelId, + onlyOsmoIncomingChannelInfo.channel.dest.channelId, + ], }, - 'label incoming proxy' + "label incoming proxy" ); t.log(`- osmo cw721-incoming-proxy address: ${osmoCw721IncomingProxy}`); t.context.osmoCw721IncomingProxy = osmoCw721IncomingProxy; const per_block = 10; // use high rate limit to avoid test failures - t.log(`instantiating wasm cw721-outgoing-proxy (${wasmCw721OutgoingProxyId}) with ${per_block} per blocks rate limit`); + t.log( + `instantiating wasm cw721-outgoing-proxy (${wasmCw721OutgoingProxyId}) with ${per_block} per blocks rate limit` + ); const { contractAddress: wasmCw721OutgoingProxy } = await instantiateContract( wasmClient, wasmCw721OutgoingProxyId, @@ -204,12 +271,14 @@ const standardSetup = async (t: ExecutionContext) => { origin: wasmIcs721, rate_limit: { per_block }, }, - 'label outgoing proxy' + "label outgoing proxy" ); t.log(`- wasm cw721-outgoing-proxy address: ${wasmCw721OutgoingProxy}`); t.context.wasmCw721OutgoingProxy = wasmCw721OutgoingProxy; - t.log(`instantiating osmo cw721-outgoing-proxy (${osmoCw721OutgoingProxyId}) with ${per_block} per blocks rate limit`); + t.log( + `instantiating osmo cw721-outgoing-proxy (${osmoCw721OutgoingProxyId}) with ${per_block} per blocks rate limit` + ); const { contractAddress: osmoCw721OutgoingProxy } = await instantiateContract( osmoClient, osmoCw721OutgoingProxyId, @@ -217,26 +286,57 @@ const standardSetup = async (t: ExecutionContext) => { origin: osmoIcs721, rate_limit: { per_block }, }, - 'label outgoing proxy' + "label outgoing proxy" ); t.log(`- osmo cw721-outgoing-proxy address: ${osmoCw721OutgoingProxy}`); t.context.osmoCw721OutgoingProxy = osmoCw721OutgoingProxy; - t.log(`migrate ${wasmIcs721} contract with incoming (${wasmCw721IncomingProxy}) and outgoing proxy (${wasmCw721OutgoingProxy})`); - await migrate(wasmClient, wasmIcs721, wasmIcs721Id, wasmCw721IncomingProxy, wasmCw721OutgoingProxy); + t.log( + `migrate ${wasmIcs721} contract with incoming (${wasmCw721IncomingProxy}) and outgoing proxy (${wasmCw721OutgoingProxy})` + ); + await migrate( + wasmClient, + wasmIcs721, + wasmIcs721Id, + wasmCw721IncomingProxy, + wasmCw721OutgoingProxy + ); - t.log(`migrate ${osmoIcs721} contract with incoming (${osmoCw721IncomingProxy}) and outgoing proxy (${osmoCw721OutgoingProxy})`); - await migrate(osmoClient, osmoIcs721, osmoIcs721Id, osmoCw721IncomingProxy, osmoCw721OutgoingProxy); + t.log( + `migrate ${osmoIcs721} contract with incoming (${osmoCw721IncomingProxy}) and outgoing proxy (${osmoCw721OutgoingProxy})` + ); + await migrate( + osmoClient, + osmoIcs721, + osmoIcs721Id, + osmoCw721IncomingProxy, + osmoCw721OutgoingProxy + ); - t.log(`creating another IBC connection and channel between wasm and osmo (${wasmIcs721} <-> ${osmoIcs721})`); - const otherChannelInfo = await createIbcConnectionAndChannel(wasmClient, osmoClient, wasmIcs721, osmoIcs721, Order.ORDER_UNORDERED, 'ics721-1'); - t.log(`- other channel not WLed for incoming proxy: ${JSON.stringify(otherChannelInfo.channel, bigIntReplacer, 2)}`); + t.log( + `creating another IBC connection and channel between wasm and osmo (${wasmIcs721} <-> ${osmoIcs721})` + ); + const otherChannelInfo = await createIbcConnectionAndChannel( + wasmClient, + osmoClient, + wasmIcs721, + osmoIcs721, + Order.ORDER_UNORDERED, + "ics721-1" + ); + t.log( + `- other channel not WLed for incoming proxy: ${JSON.stringify( + otherChannelInfo.channel, + bigIntReplacer, + 2 + )}` + ); t.context.otherChannel = otherChannelInfo; t.pass(); }; -test.serial('transfer NFT: wasmd -> osmo', async (t) => { +test.serial("transfer NFT: wasmd -> osmo", async (t) => { await standardSetup(t); const { @@ -255,7 +355,7 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { onlyOsmoIncomingChannel, } = t.context; - let tokenId = '1'; + let tokenId = "1"; await mint(wasmClient, wasmCw721, tokenId, wasmAddr, undefined); // assert token is minted let tokenOwner = await ownerOf(wasmClient, wasmCw721, tokenId); @@ -274,11 +374,17 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { }, }, }; - let transferResponse = await sendNft(wasmClient, wasmCw721, wasmCw721OutgoingProxy, ibcMsg, tokenId); + let transferResponse = await sendNft( + wasmClient, + wasmCw721, + wasmCw721OutgoingProxy, + ibcMsg, + tokenId + ); t.truthy(transferResponse); // Relay and verify we got a success - t.log('relaying packets'); + t.log("relaying packets"); let info = await channel.link.relayAll(); assertAckSuccess(info.acksFromA); @@ -315,7 +421,7 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { tokenId ); t.truthy(transferResponse); - t.log('relaying packets'); + t.log("relaying packets"); // Verify we got a success info = await channel.link.relayAll(); @@ -330,19 +436,39 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { // ==== 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'; + tokenId = "2"; await mint(wasmClient, wasmCw721, tokenId, wasmAddr, undefined); // assert token is minted tokenOwner = await ownerOf(wasmClient, wasmCw721, 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); + 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, @@ -354,17 +480,32 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { }, }, }; - transferResponse = await sendNft(wasmClient, wasmCw721, wasmCw721OutgoingProxy, ibcMsg, tokenId); + transferResponse = await sendNft( + wasmClient, + wasmCw721, + wasmCw721OutgoingProxy, + ibcMsg, + tokenId + ); t.truthy(transferResponse); // Relay and verify we got an error - t.log('relaying packets'); + 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); + const afterWasmOutgoingClassTokenToChannelList = await outgoingChannels( + wasmClient, + wasmIcs721 + ); + const afterWasmIncomingClassTokenToChannelList = await incomingChannels( + wasmClient, + wasmIcs721 + ); + const afterWasmNftContractsToClassIdList = await nftContracts( + wasmClient, + wasmIcs721 + ); t.deepEqual( beforeWasmOutgoingClassTokenToChannelList, afterWasmOutgoingClassTokenToChannelList, @@ -386,9 +527,18 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { - 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); + const afterOsmoOutgoingClassTokenToChannelList = await outgoingChannels( + osmoClient, + osmoIcs721 + ); + const afterOsmoIncomingClassTokenToChannelList = await incomingChannels( + osmoClient, + osmoIcs721 + ); + const afterOsmoNftContractsToClassIdList = await nftContracts( + osmoClient, + osmoIcs721 + ); t.deepEqual( beforeOsmoOutgoingClassTokenToChannelList, afterOsmoOutgoingClassTokenToChannelList, @@ -416,14 +566,16 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { 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'; + tokenId = "3"; await mint(wasmClient, wasmCw721, tokenId, wasmAddr, undefined); // assert token is minted tokenOwner = await ownerOf(wasmClient, wasmCw721, tokenId); t.is(wasmAddr, tokenOwner.owner); // test transfer NFT to osmo chain - t.log(`transfering to osmo chain via ${onlyOsmoIncomingChannel.channel.src.channelId}`); + t.log( + `transfering to osmo chain via ${onlyOsmoIncomingChannel.channel.src.channelId}` + ); ibcMsg = { receiver: osmoAddr, channel_id: onlyOsmoIncomingChannel.channel.src.channelId, @@ -434,18 +586,36 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { }, }, }; - transferResponse = await sendNft(wasmClient, wasmCw721, wasmCw721OutgoingProxy, ibcMsg, tokenId); + transferResponse = await sendNft( + wasmClient, + wasmCw721, + wasmCw721OutgoingProxy, + ibcMsg, + tokenId + ); t.truthy(transferResponse); // Relay and verify we got a success - t.log('relaying packets'); + 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)}`); + 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}/${t.context.wasmCw721}`; @@ -469,7 +639,9 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { 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}`); + t.log( + `transfering back to wasm chain via unknown ${onlyOsmoIncomingChannel.channel.dest.channelId}` + ); transferResponse = await sendNft( osmoClient, osmoCw721, @@ -492,7 +664,7 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { t.is(osmoIcs721, tokenOwner.owner); // Relay and verify we got an error - t.log('relaying packets'); + t.log("relaying packets"); info = await onlyOsmoIncomingChannel.link.relayAll(); for (const ack of info.acksFromB) { const parsed = JSON.parse(fromUtf8(ack.acknowledgement)); @@ -508,11 +680,23 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { 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]); + 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}`); + t.log( + `transfering back to wasm chain via WLed ${onlyOsmoIncomingChannel.channel.dest.channelId}` + ); transferResponse = await sendNft( osmoClient, osmoCw721, @@ -540,10 +724,15 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { // 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)}`); + t.true( + nftContractsToClassIdList.length === 1, + `nft contracts must have exactly one entry: ${JSON.stringify( + nftContractsToClassIdList + )}` + ); // Relay and verify success - t.log('relaying packets'); + t.log("relaying packets"); info = await onlyOsmoIncomingChannel.link.relayAll(); for (const ack of info.acksFromB) { const parsed = JSON.parse(fromUtf8(ack.acknowledgement)); @@ -552,8 +741,16 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { 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)}`); + 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); @@ -562,18 +759,33 @@ test.serial('transfer NFT: wasmd -> osmo', async (t) => { // 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)}`); + 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, tokenId); t.is(wasmAddr, tokenOwner.owner); }); -test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { +test.skip("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { await standardSetup(t); - const { wasmClient, wasmAddr, wasmCw721, wasmIcs721, wasmCw721OutgoingProxy, osmoClient, osmoAddr, osmoIcs721, channel } = t.context; + const { + wasmClient, + wasmAddr, + wasmCw721, + wasmIcs721, + wasmCw721OutgoingProxy, + osmoClient, + osmoAddr, + osmoIcs721, + channel, + } = t.context; - const tokenId = '1'; + const tokenId = "1"; await mint(wasmClient, wasmCw721, tokenId, wasmAddr, undefined); // assert token is minted let tokenOwner = await ownerOf(wasmClient, wasmCw721, tokenId); @@ -592,11 +804,17 @@ test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { }, }, }; - const transferResponse = await sendNft(wasmClient, wasmCw721, wasmCw721OutgoingProxy, ibcMsg, tokenId); + const transferResponse = await sendNft( + wasmClient, + wasmCw721, + wasmCw721OutgoingProxy, + ibcMsg, + tokenId + ); t.truthy(transferResponse); // Relay and verify we got a success - t.log('relaying packets'); + t.log("relaying packets"); const info = await channel.link.relayAll(); assertAckSuccess(info.acksFromA); @@ -614,7 +832,10 @@ test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); t.is(osmoAddr, tokenOwner.owner); - const beforeWasmOutgoingClassTokenToChannelList = await outgoingChannels(wasmClient, wasmIcs721); + const beforeWasmOutgoingClassTokenToChannelList = await outgoingChannels( + wasmClient, + wasmIcs721 + ); // there should be one outgoing channel entry t.deepEqual( beforeWasmOutgoingClassTokenToChannelList, @@ -623,7 +844,10 @@ test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { - before: ${JSON.stringify(beforeWasmOutgoingClassTokenToChannelList)}` ); // no incoming channel entry - const beforeWasmIncomingClassTokenToChannelList = await incomingChannels(wasmClient, wasmIcs721); + const beforeWasmIncomingClassTokenToChannelList = await incomingChannels( + wasmClient, + wasmIcs721 + ); t.deepEqual( beforeWasmIncomingClassTokenToChannelList, [], @@ -631,7 +855,10 @@ test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { - before: ${JSON.stringify(beforeWasmIncomingClassTokenToChannelList)}` ); // one nft contract entry - const beforeWasmNftContractsToClassIdList = await nftContracts(wasmClient, wasmIcs721); + const beforeWasmNftContractsToClassIdList = await nftContracts( + wasmClient, + wasmIcs721 + ); t.deepEqual( beforeWasmNftContractsToClassIdList, [[wasmCw721, wasmCw721]], @@ -640,7 +867,10 @@ test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { ); // no outgoing channel entry - const beforeOsmoOutgoingClassTokenToChannelList = await outgoingChannels(osmoClient, osmoIcs721); + const beforeOsmoOutgoingClassTokenToChannelList = await outgoingChannels( + osmoClient, + osmoIcs721 + ); t.deepEqual( beforeOsmoOutgoingClassTokenToChannelList, [], @@ -648,7 +878,10 @@ test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { - before: ${JSON.stringify(beforeOsmoOutgoingClassTokenToChannelList)}` ); // there should be one incoming channel entry - const beforeOsmoIncomingClassTokenToChannelList = await incomingChannels(osmoClient, osmoIcs721); + const beforeOsmoIncomingClassTokenToChannelList = await incomingChannels( + osmoClient, + osmoIcs721 + ); t.deepEqual( beforeOsmoIncomingClassTokenToChannelList, [[[osmoClassId, tokenId], channel.channel.dest.channelId]], @@ -656,7 +889,10 @@ test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { - before: ${JSON.stringify(beforeOsmoIncomingClassTokenToChannelList)}` ); // one nft contract entry - const beforeOsmoNftContractsToClassIdList = await nftContracts(osmoClient, osmoIcs721); + const beforeOsmoNftContractsToClassIdList = await nftContracts( + osmoClient, + osmoIcs721 + ); t.deepEqual( beforeOsmoNftContractsToClassIdList, [[osmoClassId, osmoCw721]], @@ -666,9 +902,19 @@ test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { // ==== test unescrow NFT on wasm chain ==== t.log(`unescrow NFT on wasm chain`); - await adminCleanAndUnescrowNft(wasmClient, wasmIcs721, wasmAddr, tokenId, wasmCw721, wasmCw721); + await adminCleanAndUnescrowNft( + wasmClient, + wasmIcs721, + wasmAddr, + tokenId, + wasmCw721, + wasmCw721 + ); // there should be no outgoing channel entry - const afterWasmOutgoingClassTokenToChannelList = await outgoingChannels(wasmClient, wasmIcs721); + const afterWasmOutgoingClassTokenToChannelList = await outgoingChannels( + wasmClient, + wasmIcs721 + ); t.deepEqual( afterWasmOutgoingClassTokenToChannelList, [], @@ -685,12 +931,22 @@ test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { const response = await approve(osmoClient, osmoCw721, osmoIcs721, tokenId); t.log(`- response: ${JSON.stringify(response, bigIntReplacer, 2)}`); t.log(`burn NFT on osmo chain`); - await adminCleanAndBurnNft(osmoClient, osmoIcs721, osmoAddr, tokenId, osmoClassId, osmoCw721); + await adminCleanAndBurnNft( + osmoClient, + osmoIcs721, + osmoAddr, + tokenId, + osmoClassId, + osmoCw721 + ); t.log(`- response: ${JSON.stringify(response, bigIntReplacer, 2)}`); allNFTs = await allTokens(osmoClient, osmoCw721); t.is(allNFTs.tokens.length, 0); // there should be no incoming channel entry - const afterOsmoIncomingClassTokenToChannelList = await incomingChannels(osmoClient, osmoIcs721); + const afterOsmoIncomingClassTokenToChannelList = await incomingChannels( + osmoClient, + osmoIcs721 + ); t.deepEqual( afterOsmoIncomingClassTokenToChannelList, [], @@ -699,20 +955,30 @@ test.serial('admin unescrow and burn NFT: wasmd -> osmo', async (t) => { ); }); -test.serial('malicious NFT', async (t) => { +test.skip("malicious NFT", async (t) => { await standardSetup(t); - const { wasmClient, wasmAddr, wasmIcs721, wasmCw721OutgoingProxy, osmoClient, osmoAddr, osmoIcs721, osmoCw721OutgoingProxy, channel } = t.context; - const tokenId = '1'; + const { + wasmClient, + wasmAddr, + wasmIcs721, + wasmCw721OutgoingProxy, + osmoClient, + osmoAddr, + osmoIcs721, + osmoCw721OutgoingProxy, + channel, + } = t.context; + const tokenId = "1"; // instantiate malicious cw721 contract const res = await uploadAndInstantiate(wasmClient, { cw721_gas_tester: { path: MALICIOUS_CW721, instantiateMsg: { - name: 'evil', - symbol: 'evil', + name: "evil", + symbol: "evil", minter: wasmClient.senderAddress, - banned_recipient: 'banned_recipient', // panic every time, on back transfer, when ICS721 tries to transfer/unescrow NFT to this address + banned_recipient: "banned_recipient", // panic every time, on back transfer, when ICS721 tries to transfer/unescrow NFT to this address }, }, }); @@ -720,7 +986,7 @@ test.serial('malicious NFT', async (t) => { // ==== test malicious NFT transfer to osmo chain ==== await mint(wasmClient, cw721, tokenId, wasmAddr, undefined); - t.log('transferring to osmo chain'); + t.log("transferring to osmo chain"); let ibcMsg = { receiver: osmoAddr, channel_id: channel.channel.src.channelId, @@ -731,10 +997,16 @@ test.serial('malicious NFT', async (t) => { }, }, }; - let transferResponse = await sendNft(wasmClient, cw721, wasmCw721OutgoingProxy, ibcMsg, tokenId); + let transferResponse = await sendNft( + wasmClient, + cw721, + wasmCw721OutgoingProxy, + ibcMsg, + tokenId + ); t.truthy(transferResponse); - t.log('relaying packets'); + t.log("relaying packets"); let info = await channel.link.relayAll(); assertAckSuccess(info.acksFromB); @@ -750,9 +1022,9 @@ test.serial('malicious NFT', async (t) => { t.is(osmoAddr, tokenOwner.owner); // ==== test malicious NFT back transfer to banned recipient on wasm chain ==== - t.log('transferring back to wasm chain to banned recipient'); + t.log("transferring back to wasm chain to banned recipient"); ibcMsg = { - receiver: 'banned_recipient', + receiver: "banned_recipient", channel_id: channel.channel.dest.channelId, timeout: { block: { @@ -761,14 +1033,20 @@ test.serial('malicious NFT', async (t) => { }, }, }; - transferResponse = await sendNft(osmoClient, osmoCw721, osmoCw721OutgoingProxy, ibcMsg, tokenId); + transferResponse = await sendNft( + osmoClient, + osmoCw721, + osmoCw721OutgoingProxy, + ibcMsg, + tokenId + ); t.truthy(transferResponse); // before relay NFT escrowed by ICS721 tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); t.is(osmoIcs721, tokenOwner.owner); - t.log('relaying packets'); - let pending = await channel.link.getPendingPackets('B'); + t.log("relaying packets"); + let pending = await channel.link.getPendingPackets("B"); t.is(pending.length, 1); // Despite the transfer panicking, a fail ack should be returned. info = await channel.link.relayAll(); @@ -779,7 +1057,7 @@ test.serial('malicious NFT', async (t) => { t.log(`NFT #${tokenId} returned to owner`); // ==== test malicious NFT transfer to regular recipient wasm chain ==== - t.log('transferring back to wasm chain to recipient', wasmAddr); + t.log("transferring back to wasm chain to recipient", wasmAddr); ibcMsg = { receiver: wasmAddr, channel_id: channel.channel.dest.channelId, @@ -791,12 +1069,18 @@ test.serial('malicious NFT', async (t) => { }, }; - transferResponse = await sendNft(osmoClient, osmoCw721, osmoCw721OutgoingProxy, ibcMsg, tokenId); + transferResponse = await sendNft( + osmoClient, + osmoCw721, + osmoCw721OutgoingProxy, + ibcMsg, + tokenId + ); t.truthy(transferResponse); // Relay and verify we got a success - t.log('relaying packets'); - pending = await channel.link.getPendingPackets('B'); + t.log("relaying packets"); + pending = await channel.link.getPendingPackets("B"); t.is(pending.length, 1); info = await channel.link.relayAll(); assertAckSuccess(info.acksFromB); From 915e9975969b66e901d6e5416c36fdda6e56b025 Mon Sep 17 00:00:00 2001 From: mr-t Date: Tue, 13 Aug 2024 14:06:49 +0200 Subject: [PATCH 11/21] update lib bech32 v0.11 --- Cargo.lock | 14 +++++-- Cargo.toml | 2 +- .../src/testing/integration_tests.rs | 40 +++++++++++-------- .../ics721/src/testing/integration_tests.rs | 24 +++++------ packages/ics721/src/utils.rs | 7 ++-- 5 files changed, 48 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 245e55b7..25d8b5f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" @@ -144,7 +150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2685c2182624b2e9e17f7596192de49a3f86b7a0c9a5f6b25c1df5e24592e836" dependencies = [ "base64", - "bech32", + "bech32 0.9.1", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -338,7 +344,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc392a5cb7e778e3f90adbf7faa43c4db7f35b6623224b08886d796718edb875" dependencies = [ "anyhow", - "bech32", + "bech32 0.9.1", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -885,7 +891,7 @@ name = "ics721" version = "0.1.0" dependencies = [ "anyhow", - "bech32", + "bech32 0.11.0", "cosmwasm-schema", "cosmwasm-std", "cw-cii", @@ -1211,7 +1217,7 @@ name = "sg-ics721" version = "0.1.0" dependencies = [ "anyhow", - "bech32", + "bech32 0.11.0", "cosmwasm-schema", "cosmwasm-std", "cw-cii", diff --git a/Cargo.toml b/Cargo.toml index 3e55e1ae..2d4f6858 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" diff --git a/contracts/sg-ics721/src/testing/integration_tests.rs b/contracts/sg-ics721/src/testing/integration_tests.rs index 8723e3f6..73e18800 100644 --- a/contracts/sg-ics721/src/testing/integration_tests.rs +++ b/contracts/sg-ics721/src/testing/integration_tests.rs @@ -1,5 +1,5 @@ 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, Decimal, @@ -160,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(), + } } } @@ -183,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")) @@ -249,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}"), } @@ -643,7 +641,9 @@ fn test_do_instantiate_and_mint_weird_data() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://interchain.arkprotocol.io".to_string()), + 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( @@ -874,7 +874,9 @@ fn test_do_instantiate_and_mint() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://interchain.arkprotocol.io".to_string()), + 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( @@ -1915,7 +1917,9 @@ fn test_do_instantiate_and_mint_no_instantiate() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://interchain.arkprotocol.io".to_string()), + 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( @@ -2057,7 +2061,9 @@ fn test_do_instantiate_and_mint_permissions() { extension: Some(CollectionExtension { description: "description".to_string(), explicit_content: Some(false), - external_link: Some("https://interchain.arkprotocol.io".to_string()), + 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( diff --git a/packages/ics721/src/testing/integration_tests.rs b/packages/ics721/src/testing/integration_tests.rs index bba9f6d5..f9634bf2 100644 --- a/packages/ics721/src/testing/integration_tests.rs +++ b/packages/ics721/src/testing/integration_tests.rs @@ -1,5 +1,5 @@ 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, Decimal, @@ -165,12 +165,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(), + } } } @@ -188,22 +190,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")) @@ -254,7 +252,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}"), } diff --git a/packages/ics721/src/utils.rs b/packages/ics721/src/utils.rs index 4ff9fe2d..621b7175 100644 --- a/packages/ics721/src/utils.rs +++ b/packages/ics721/src/utils.rs @@ -62,11 +62,10 @@ pub fn get_collection_data(deps: &DepsMut, collection: &Addr) -> StdResult 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) } From ef5a3c4c338c88e6e8ff6666f85d1053c8c881ac Mon Sep 17 00:00:00 2001 From: mr-t Date: Tue, 13 Aug 2024 16:55:11 +0200 Subject: [PATCH 12/21] assert escrowed collection data on chain B is properly set --- ts-relayer-tests/src/cw721-utils.ts | 53 +++++++++++++++++++++++++++++ ts-relayer-tests/src/ics721.spec.ts | 47 +++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/ts-relayer-tests/src/cw721-utils.ts b/ts-relayer-tests/src/cw721-utils.ts index ce447f00..c4ca272d 100644 --- a/ts-relayer-tests/src/cw721-utils.ts +++ b/ts-relayer-tests/src/cw721-utils.ts @@ -128,6 +128,59 @@ export function ownerOf( return client.sign.queryContractSmart(cw721Contract, msg); } +export function getCollectionInfoAndExtension( + 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 getMinterOwnership( + 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 getCreatorOwnership( + 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 23ab7696..8aa52440 100644 --- a/ts-relayer-tests/src/ics721.spec.ts +++ b/ts-relayer-tests/src/ics721.spec.ts @@ -4,7 +4,16 @@ 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, + getCollectionInfoAndExtension, + getCreatorOwnership, + getMinterOwnership, + mint, + ownerOf, + sendNft, +} from "./cw721-utils"; import { adminCleanAndBurnNft, adminCleanAndUnescrowNft, @@ -169,7 +178,10 @@ const standardSetup = async (t: ExecutionContext) => { 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}`); @@ -179,7 +191,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}`); @@ -401,6 +416,32 @@ 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 getCollectionInfoAndExtension( + wasmClient, + wasmCw721 + ); + const osmoCollectionData = await getCollectionInfoAndExtension( + 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 getCreatorOwnership(osmoClient, osmoCw721); + t.is(osmoAddr, creatorOwnerShip.owner); + // assert minter is set to ics721 + const minterOwnerShip = await getMinterOwnership(osmoClient, osmoCw721); + t.is(osmoIcs721, minterOwnerShip.owner); // test back transfer NFT to wasm chain t.log(`transfering back to wasm chain via ${channel.channel.dest.channelId}`); From 3a6d97fed817b0e5a70fb2caa839b8e6fded43c1 Mon Sep 17 00:00:00 2001 From: mr-t Date: Tue, 13 Aug 2024 22:13:51 +0200 Subject: [PATCH 13/21] update cw-nfts lib --- Cargo.lock | 20 +++--- Cargo.toml | 6 +- contracts/cw721-tester/src/lib.rs | 4 +- packages/ics721/src/execute.rs | 16 ++--- packages/ics721/src/ibc.rs | 4 +- packages/ics721/src/query.rs | 4 +- packages/ics721/src/testing/contract.rs | 4 +- .../ics721/src/testing/integration_tests.rs | 62 ++++++++++--------- packages/ics721/src/utils.rs | 13 ++-- 9 files changed, 68 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25d8b5f7..3bb2b0a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -593,7 +593,7 @@ dependencies = [ [[package]] name = "cw721" version = "0.19.0" -source = "git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs#16997350c36bf6aa03c9428ce300fa48f17a0e65" +source = "git+https://github.com/public-awesome/cw-nfts?branch=minor_changes#40c9b9e8037d94a3042e6f6a89ef5b5aa23c7267" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -666,25 +666,25 @@ dependencies = [ [[package]] name = "cw721-base" version = "0.19.0" -source = "git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs#16997350c36bf6aa03c9428ce300fa48f17a0e65" +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=migrate_msgs)", + "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=migrate_msgs#16997350c36bf6aa03c9428ce300fa48f17a0e65" +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=migrate_msgs)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", "schemars", "serde", ] @@ -697,7 +697,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.2.0", "cw2 1.1.2", - "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", "cw721-metadata-onchain", "thiserror", ] @@ -905,7 +905,7 @@ dependencies = [ "cw-utils 1.0.3", "cw2 1.1.2", "cw721 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", "cw721-base 0.16.0", "cw721-base 0.19.0", "cw721-metadata-onchain", @@ -934,7 +934,7 @@ dependencies = [ "cosmwasm-storage", "cw-storage-plus 1.2.0", "cw2 1.1.2", - "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", "ics721", "ics721-types 0.1.0", "thiserror", @@ -947,7 +947,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", - "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs)", + "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=minor_changes)", "serde", "thiserror", ] @@ -1228,7 +1228,7 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw2 1.1.2", "cw721 0.18.0 (git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.1)", - "cw721 0.19.0 (git+https://github.com/public-awesome/cw-nfts?branch=migrate_msgs)", + "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.1)", "ics721", "ics721-types 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index 2d4f6858..072011d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,11 @@ 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/public-awesome/cw-nfts", branch = "migrate_msgs"} # 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-018 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.18.1", package = "cw721"} # TODO switch to version 0.18.1/0.19.0, once released cw721-016 = { version = "0.16.0", package = "cw721" } -cw721-metadata-onchain = { git = "https://github.com/public-awesome/cw-nfts", branch = "migrate_msgs", 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 = "migrate_msgs"} # 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-018 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.18.1", package = "cw721-base"} # TODO switch to version 0.18.1/0.19.0, once released cw721-base-016 = { version = "0.16.0", package = "cw721-base" } cw-ics721-incoming-proxy = { git = "https://github.com/arkprotocol/cw-ics721-proxy.git", tag = "v0.1.0" } diff --git a/contracts/cw721-tester/src/lib.rs b/contracts/cw721-tester/src/lib.rs index 4d45dfd3..c4274d75 100644 --- a/contracts/cw721-tester/src/lib.rs +++ b/contracts/cw721-tester/src/lib.rs @@ -12,8 +12,8 @@ use cw721::{ use cw721_metadata_onchain::Cw721MetadataContract; use cw_storage_plus::Item; -pub type ExecuteMsg = cw721_metadata_onchain::ExecuteMsg; -pub type QueryMsg = cw721_metadata_onchain::QueryMsg; +pub type ExecuteMsg = cw721_metadata_onchain::msg::ExecuteMsg; +pub type QueryMsg = cw721_metadata_onchain::msg::QueryMsg; #[cw_serde] pub struct InstantiateMsg { diff --git a/packages/ics721/src/execute.rs b/packages/ics721/src/execute.rs index 50ec5f8e..bd3298a6 100644 --- a/packages/ics721/src/execute.rs +++ b/packages/ics721/src/execute.rs @@ -186,7 +186,7 @@ where .querier .query_wasm_smart( child_collection.clone(), - &cw721_metadata_onchain::QueryMsg::AllNftInfo { + &cw721_metadata_onchain::msg::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -207,7 +207,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_metadata_onchain::ExecuteMsg::Burn { + msg: to_json_binary(&cw721_metadata_onchain::msg::ExecuteMsg::Burn { token_id: token_id.clone().into(), })?, funds: vec![], @@ -269,7 +269,7 @@ where .querier .query_wasm_smart( home_collection.clone(), - &cw721_metadata_onchain::QueryMsg::AllNftInfo { + &cw721_metadata_onchain::msg::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -285,7 +285,7 @@ where // transfer NFT let transfer_msg = WasmMsg::Execute { contract_addr: home_collection.to_string(), - msg: to_json_binary(&cw721_metadata_onchain::ExecuteMsg::TransferNft { + msg: to_json_binary(&cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: recipient.to_string(), token_id: token_id.clone().into(), })?, @@ -402,7 +402,7 @@ where // make sure NFT is escrowed by ics721 let UniversalAllNftInfoResponse { access, info } = deps.querier.query_wasm_smart( nft_contract, - &cw721_metadata_onchain::QueryMsg::AllNftInfo { + &cw721_metadata_onchain::msg::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -620,7 +620,7 @@ where .or_else(|| admin.clone()) .or_else(|| Some(creator.clone())) .unwrap(); - let mut instantiate_msg = cw721_metadata_onchain::InstantiateMsg { + 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 @@ -685,7 +685,7 @@ where Ok(WasmMsg::Execute { contract_addr: nft_contract.to_string(), msg: to_json_binary( - &cw721_metadata_onchain::ExecuteMsg::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: receiver.to_string(), token_id: token_id.into(), }, @@ -717,7 +717,7 @@ where // 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)?; - let msg = cw721_metadata_onchain::ExecuteMsg::Mint { + let msg = cw721_metadata_onchain::msg::ExecuteMsg::Mint { token_id: id.into(), token_uri: uri, owner: receiver.to_string(), diff --git a/packages/ics721/src/ibc.rs b/packages/ics721/src/ibc.rs index 5992759e..6bcdc1df 100644 --- a/packages/ics721/src/ibc.rs +++ b/packages/ics721/src/ibc.rs @@ -142,7 +142,7 @@ where messages.push(WasmMsg::Execute { contract_addr: nft_contract.to_string(), - msg: to_json_binary(&cw721_metadata_onchain::ExecuteMsg::Burn { + msg: to_json_binary(&cw721_metadata_onchain::msg::ExecuteMsg::Burn { token_id: token.into(), })?, funds: vec![], @@ -212,7 +212,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_metadata_onchain::ExecuteMsg::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/query.rs b/packages/ics721/src/query.rs index dea4905b..b05fee31 100644 --- a/packages/ics721/src/query.rs +++ b/packages/ics721/src/query.rs @@ -150,7 +150,7 @@ pub fn query_token_metadata( }; let UniversalAllNftInfoResponse { info, .. } = deps.querier.query_wasm_smart( nft_contract, - &cw721_metadata_onchain::QueryMsg::AllNftInfo { + &cw721_metadata_onchain::msg::QueryMsg::AllNftInfo { token_id: token_id.clone().into(), include_expired: None, }, @@ -170,7 +170,7 @@ pub fn query_owner( let nft_contract = load_nft_contract_for_class_id(deps.storage, class_id)?; let resp: cw721::msg::OwnerOfResponse = deps.querier.query_wasm_smart( nft_contract, - &cw721_metadata_onchain::QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id, include_expired: None, }, diff --git a/packages/ics721/src/testing/contract.rs b/packages/ics721/src/testing/contract.rs index 91fe4de1..ea0f9285 100644 --- a/packages/ics721/src/testing/contract.rs +++ b/packages/ics721/src/testing/contract.rs @@ -12,7 +12,7 @@ use cw721::{ CollectionExtension, DefaultOptionalCollectionExtension, DefaultOptionalNftExtension, NftExtension, RoyaltyInfo, }; -use cw721_metadata_onchain::QueryMsg; +use cw721_metadata_onchain::msg::QueryMsg; use cw_cii::ContractInstantiateInfo; use cw_ownable::Ownership; use cw_storage_plus::Map; @@ -91,7 +91,7 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { cosmwasm_std::WasmQuery::Smart { contract_addr: _, msg, - } => match from_json::(&msg).unwrap() { + } => match from_json::(&msg).unwrap() { QueryMsg::GetMinterOwnership {} => QuerierResult::Ok(ContractResult::Ok( to_json_binary(&Ownership:: { owner: Some(Addr::unchecked(OWNER_ADDR)), diff --git a/packages/ics721/src/testing/integration_tests.rs b/packages/ics721/src/testing/integration_tests.rs index f9634bf2..bb2c9812 100644 --- a/packages/ics721/src/testing/integration_tests.rs +++ b/packages/ics721/src/testing/integration_tests.rs @@ -13,7 +13,9 @@ use cw721::{ CollectionExtension, DefaultOptionalCollectionExtension, DefaultOptionalNftExtension, RoyaltyInfo, }; -use cw721_metadata_onchain::{InstantiateMsg as Cw721InstantiateMsg, QueryMsg as Cw721QueryMsg}; +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, @@ -541,7 +543,7 @@ impl Test { .wrap() .query_wasm_smart( self.source_cw721.clone(), - &cw721_metadata_onchain::QueryMsg::AllNftInfo { + &cw721_metadata_onchain::msg::QueryMsg::AllNftInfo { token_id, include_expired: None, }, @@ -556,7 +558,7 @@ impl Test { .execute_contract( self.source_cw721_owner.clone(), self.source_cw721.clone(), - &cw721_metadata_onchain::ExecuteMsg::Mint { + &cw721_metadata_onchain::msg::ExecuteMsg::Mint { token_id: self.nfts_minted.to_string(), owner: owner.to_string(), token_uri: None, @@ -786,7 +788,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -800,7 +802,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -812,7 +814,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), - &cw721_metadata_onchain::ExecuteMsg::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), token_id: "1".to_string(), }, @@ -840,7 +842,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract, - &cw721_metadata_onchain::QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -959,7 +961,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -973,7 +975,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -985,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_metadata_onchain::ExecuteMsg::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), token_id: "1".to_string(), }, @@ -1013,7 +1015,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract, - &cw721_metadata_onchain::QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1114,7 +1116,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1128,7 +1130,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1140,7 +1142,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), - &cw721_metadata_onchain::ExecuteMsg::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), // new owner token_id: "1".to_string(), }, @@ -1169,7 +1171,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract, - &cw721_metadata_onchain::QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1273,7 +1275,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1287,7 +1289,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1299,7 +1301,7 @@ fn test_do_instantiate_and_mint() { .execute_contract( test.app.api().addr_make(NFT_OWNER_TARGET_CHAIN), nft_contract.clone(), - &cw721_metadata_onchain::ExecuteMsg::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract.to_string(), // new owner token_id: "1".to_string(), }, @@ -1328,7 +1330,7 @@ fn test_do_instantiate_and_mint() { .wrap() .query_wasm_smart( nft_contract, - &cw721_metadata_onchain::QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1488,7 +1490,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .wrap() .query_wasm_smart( nft_contract_1.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1498,7 +1500,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .wrap() .query_wasm_smart( nft_contract_2.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "1".to_string(), }, ) @@ -1513,7 +1515,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .wrap() .query_wasm_smart( nft_contract_1.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1523,7 +1525,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .wrap() .query_wasm_smart( nft_contract_2.clone(), - &cw721_metadata_onchain::QueryMsg::NftInfo { + &cw721_metadata_onchain::msg::QueryMsg::NftInfo { token_id: "2".to_string(), }, ) @@ -1542,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_metadata_onchain::ExecuteMsg::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract_1.to_string(), token_id: "1".to_string(), }, @@ -1553,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_metadata_onchain::ExecuteMsg::TransferNft { + &cw721_metadata_onchain::msg::ExecuteMsg::TransferNft { recipient: nft_contract_2.to_string(), token_id: "1".to_string(), }, @@ -1593,7 +1595,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .wrap() .query_wasm_smart( nft_contract_1, - &cw721_metadata_onchain::QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1604,7 +1606,7 @@ fn test_do_instantiate_and_mint_2_different_collections() { .wrap() .query_wasm_smart( nft_contract_2, - &cw721_metadata_onchain::QueryMsg::OwnerOf { + &cw721_metadata_onchain::msg::QueryMsg::OwnerOf { token_id: "1".to_string(), include_expired: None, }, @@ -1725,7 +1727,7 @@ fn test_do_instantiate_and_mint_no_instantiate() { .wrap() .query_wasm_smart( nft_contract, - &cw721_metadata_onchain::QueryMsg::AllTokens { + &cw721_metadata_onchain::msg::QueryMsg::AllTokens { start_after: None, limit: None, }, @@ -1893,7 +1895,7 @@ fn test_proxy_authorized() { .instantiate_contract( source_cw721_id, test.app.api().addr_make("ekez"), - &cw721_metadata_onchain::InstantiateMsg { + &cw721_metadata_onchain::msg::InstantiateMsg { name: "token".to_string(), symbol: "nonfungible".to_string(), collection_info_extension: None, @@ -1917,7 +1919,7 @@ fn test_proxy_authorized() { .execute_contract( test.app.api().addr_make(COLLECTION_OWNER_SOURCE_CHAIN), source_cw721.clone(), - &cw721_metadata_onchain::ExecuteMsg::Mint { + &cw721_metadata_onchain::msg::ExecuteMsg::Mint { token_id: "1".to_string(), owner: test.ics721.to_string(), token_uri: None, diff --git a/packages/ics721/src/utils.rs b/packages/ics721/src/utils.rs index 621b7175..cfa5ccb5 100644 --- a/packages/ics721/src/utils.rs +++ b/packages/ics721/src/utils.rs @@ -8,7 +8,7 @@ pub fn get_collection_data(deps: &DepsMut, collection: &Addr) -> StdResult> = deps.querier.query_wasm_smart( collection, - &cw721_metadata_onchain::QueryMsg::GetCreatorOwnership {}, + &cw721_metadata_onchain::msg::QueryMsg::GetCreatorOwnership {}, ); let owner = match ownership_result { Ok(ownership) => ownership.owner.map(|a| a.to_string()), @@ -16,7 +16,7 @@ pub fn get_collection_data(deps: &DepsMut, collection: &Addr) -> StdResult> = deps.querier.query_wasm_smart( collection, - &cw721_metadata_onchain::QueryMsg::GetMinterOwnership {}, + &cw721_metadata_onchain::msg::QueryMsg::GetMinterOwnership {}, ); match ownership { Ok(ownership) => ownership.owner.map(|a| a.to_string()), @@ -43,11 +43,12 @@ pub fn get_collection_data(deps: &DepsMut, collection: &Addr) -> StdResult Date: Tue, 13 Aug 2024 22:19:42 +0200 Subject: [PATCH 14/21] rename --- packages/ics721/src/execute.rs | 14 ++++++-------- packages/ics721/src/query.rs | 4 ++-- packages/ics721/src/state.rs | 2 +- packages/ics721/src/testing/contract.rs | 8 ++++---- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/ics721/src/execute.rs b/packages/ics721/src/execute.rs index bd3298a6..145f0226 100644 --- a/packages/ics721/src/execute.rs +++ b/packages/ics721/src/execute.rs @@ -25,8 +25,8 @@ 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, + ClassIdInfo, CollectionData, UniversalAllNftInfoResponse, CLASS_ID_AND_NFT_CONTRACT_INFO, + CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, CW721_ADMIN, CW721_CODE_ID, INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, TOKEN_METADATA, }, @@ -69,7 +69,7 @@ where )); } - ADMIN_USED_FOR_CW721.save( + CW721_ADMIN.save( deps.storage, &msg.cw721_admin .as_ref() @@ -580,9 +580,7 @@ where }; CLASS_ID_AND_NFT_CONTRACT_INFO.save(deps.storage, &class.id, &class_id_info)?; - let cw721_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: cw721_admin.clone(), @@ -795,9 +793,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/query.rs b/packages/ics721/src/query.rs index b05fee31..e5e51fc6 100644 --- a/packages/ics721/src/query.rs +++ b/packages/ics721/src/query.rs @@ -6,7 +6,7 @@ use crate::{ helpers::get_instantiate2_address, msg::QueryMsg, state::{ - UniversalAllNftInfoResponse, ADMIN_USED_FOR_CW721, CLASS_ID_AND_NFT_CONTRACT_INFO, + UniversalAllNftInfoResponse, CW721_ADMIN, 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, }, @@ -47,7 +47,7 @@ pub trait Ics721Query { 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)?)?) + Ok(to_json_binary(&CW721_ADMIN.load(deps.storage)?)?) } QueryMsg::ContractAddrLength {} => Ok(to_json_binary( &CONTRACT_ADDR_LENGTH.may_load(deps.storage)?, diff --git a/packages/ics721/src/state.rs b/packages/ics721/src/state.rs index cdb6dc33..6ba0a508 100644 --- a/packages/ics721/src/state.rs +++ b/packages/ics721/src/state.rs @@ -48,7 +48,7 @@ pub const INCOMING_CLASS_TOKEN_TO_CHANNEL: Map<(ClassId, TokenId), String> = Map pub const 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. diff --git a/packages/ics721/src/testing/contract.rs b/packages/ics721/src/testing/contract.rs index ea0f9285..8bee86da 100644 --- a/packages/ics721/src/testing/contract.rs +++ b/packages/ics721/src/testing/contract.rs @@ -26,8 +26,8 @@ use crate::{ 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, + CollectionData, CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, CW721_ADMIN, CW721_CODE_ID, + INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, }, utils::get_collection_data, }; @@ -651,7 +651,7 @@ fn test_instantiate() { ); assert!(!PO.paused.load(&deps.storage).unwrap()); assert_eq!( - ADMIN_USED_FOR_CW721.load(&deps.storage).unwrap(), + CW721_ADMIN.load(&deps.storage).unwrap(), Some(Addr::unchecked(ADMIN_ADDR.to_string())) ); assert_eq!(CONTRACT_ADDR_LENGTH.load(&deps.storage).unwrap(), 20); @@ -726,7 +726,7 @@ fn test_migrate() { ); assert_eq!(CW721_CODE_ID.load(&deps.storage).unwrap(), 1); assert_eq!( - ADMIN_USED_FOR_CW721.load(&deps.storage).unwrap(), + CW721_ADMIN.load(&deps.storage).unwrap(), Some(Addr::unchecked("some_other_admin")) ); assert_eq!(CONTRACT_ADDR_LENGTH.load(&deps.storage).unwrap(), 20); From dcdc99c5f52c21411ad6e2b85e90aba6614d8e6e Mon Sep 17 00:00:00 2001 From: mr-t Date: Tue, 13 Aug 2024 23:50:16 +0200 Subject: [PATCH 15/21] transfer onchain metadata in case NFT is on home chain --- packages/ics721-types/src/token_types.rs | 2 +- packages/ics721/src/execute.rs | 25 ++-- packages/ics721/src/ibc.rs | 7 +- packages/ics721/src/query.rs | 13 +- packages/ics721/src/state.rs | 5 +- .../ics721/src/testing/integration_tests.rs | 2 +- packages/ics721/src/testing/mod.rs | 2 +- .../testing/{contract.rs => unit_tests.rs} | 134 +++++++++++++++++- 8 files changed, 166 insertions(+), 24 deletions(-) rename packages/ics721/src/testing/{contract.rs => unit_tests.rs} (83%) 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/src/execute.rs b/packages/ics721/src/execute.rs index 145f0226..63b0eca8 100644 --- a/packages/ics721/src/execute.rs +++ b/packages/ics721/src/execute.rs @@ -27,8 +27,8 @@ use crate::{ state::{ ClassIdInfo, CollectionData, UniversalAllNftInfoResponse, CLASS_ID_AND_NFT_CONTRACT_INFO, CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, CW721_ADMIN, CW721_CODE_ID, - INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, - OUTGOING_PROXY, PO, TOKEN_METADATA, + IBC_RECEIVE_TOKEN_METADATA, INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, + OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, }, token_types::{VoucherCreation, VoucherRedemption}, ContractError, @@ -179,7 +179,7 @@ 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 @@ -411,12 +411,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, we use the one from the cw721 contract + None => info.extension.map(|ext| to_json_binary(&ext)).transpose()?, + }; let ibc_message = NonFungibleTokenPacketData { class_id: class.id.clone(), @@ -713,7 +718,11 @@ 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, + )?; let msg = cw721_metadata_onchain::msg::ExecuteMsg::Mint { token_id: id.into(), diff --git a/packages/ics721/src/ibc.rs b/packages/ics721/src/ibc.rs index 6bcdc1df..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,7 +138,8 @@ 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(), diff --git a/packages/ics721/src/query.rs b/packages/ics721/src/query.rs index e5e51fc6..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, CW721_ADMIN, 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(&CW721_ADMIN.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. diff --git a/packages/ics721/src/state.rs b/packages/ics721/src/state.rs index 6ba0a508..21cc0495 100644 --- a/packages/ics721/src/state.rs +++ b/packages/ics721/src/state.rs @@ -40,12 +40,15 @@ 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 CW721_ADMIN: Item> = Item::new("l"); diff --git a/packages/ics721/src/testing/integration_tests.rs b/packages/ics721/src/testing/integration_tests.rs index bb2c9812..5a784336 100644 --- a/packages/ics721/src/testing/integration_tests.rs +++ b/packages/ics721/src/testing/integration_tests.rs @@ -38,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"; 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/contract.rs b/packages/ics721/src/testing/unit_tests.rs similarity index 83% rename from packages/ics721/src/testing/contract.rs rename to packages/ics721/src/testing/unit_tests.rs index 8bee86da..7903016a 100644 --- a/packages/ics721/src/testing/contract.rs +++ b/packages/ics721/src/testing/unit_tests.rs @@ -16,6 +16,7 @@ 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, @@ -27,7 +28,8 @@ use crate::{ }, state::{ CollectionData, CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, CW721_ADMIN, CW721_CODE_ID, - INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, + IBC_RECEIVE_TOKEN_METADATA, INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, + OUTGOING_PROXY, PO, }, utils::get_collection_data, }; @@ -117,6 +119,9 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { 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() }), }, @@ -326,7 +331,132 @@ fn test_receive_nft() { class_uri: None, class_data: Some(to_json_binary(&expected_class_data).unwrap()), token_ids: vec![TokenId::new(token_id)], - token_data: None, + 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(); + #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] + struct UnknownMetadata { + pub unknown: String, + } + 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(), From eb1af4d08e94881398b3bd4473acd9bce8a07de4 Mon Sep 17 00:00:00 2001 From: mr-t Date: Wed, 14 Aug 2024 00:36:40 +0200 Subject: [PATCH 16/21] mint with onchain metadata --- packages/ics721/src/execute.rs | 27 +++- packages/ics721/src/testing/unit_tests.rs | 165 ++++++++++++++++++++-- 2 files changed, 179 insertions(+), 13 deletions(-) diff --git a/packages/ics721/src/execute.rs b/packages/ics721/src/execute.rs index 63b0eca8..3b5dadfa 100644 --- a/packages/ics721/src/execute.rs +++ b/packages/ics721/src/execute.rs @@ -4,7 +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::RoyaltyInfoResponse; +use cw721::{ + msg::{NftExtensionMsg, RoyaltyInfoResponse}, + NftExtension, +}; use cw_storage_plus::Map; use ics721_types::{ ibc_types::{IbcOutgoingMsg, IbcOutgoingProxyMsg, NonFungibleTokenPacketData}, @@ -419,7 +422,7 @@ where .flatten() { Some(metadata) => Some(metadata), - // incase there is none in the storage, we use the one from the cw721 contract + // 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()?, }; @@ -724,11 +727,29 @@ where &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_metadata_onchain::msg::ExecuteMsg::Mint { token_id: id.into(), token_uri: uri, owner: receiver.to_string(), - extension: None, // TODO consider token data in NonFungibleTokenPacketData + extension, }; Ok(WasmMsg::Execute { contract_addr: nft_contract.to_string(), diff --git a/packages/ics721/src/testing/unit_tests.rs b/packages/ics721/src/testing/unit_tests.rs index 7903016a..0edfbe30 100644 --- a/packages/ics721/src/testing/unit_tests.rs +++ b/packages/ics721/src/testing/unit_tests.rs @@ -3,11 +3,12 @@ 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, WasmQuery, + Order, QuerierResult, Response, StdResult, SubMsg, Timestamp, WasmMsg, WasmQuery, }; use cw721::{ msg::{ - AllNftInfoResponse, CollectionInfoAndExtensionResponse, NftInfoResponse, NumTokensResponse, + AllNftInfoResponse, CollectionInfoAndExtensionResponse, NftExtensionMsg, NftInfoResponse, + NumTokensResponse, }, CollectionExtension, DefaultOptionalCollectionExtension, DefaultOptionalNftExtension, NftExtension, RoyaltyInfo, @@ -27,15 +28,15 @@ use crate::{ Ics721Query, }, state::{ - CollectionData, 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, + 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, TokenId}, + token_types::{ClassId, Token, TokenId}, }; const NFT_CONTRACT_1: &str = "nft1"; @@ -46,6 +47,11 @@ 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 { @@ -376,10 +382,6 @@ fn test_receive_nft() { querier.update_wasm(mock_querier); let mut deps = mock_dependencies(); - #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] - struct UnknownMetadata { - pub unknown: String, - } let token_id = "1"; IBC_RECEIVE_TOKEN_METADATA .save( @@ -645,6 +647,149 @@ fn test_receive_nft() { } } +#[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(); From 667dd69ac9ed3f8d84f3efc9775c05f168f17cfb Mon Sep 17 00:00:00 2001 From: mr-t Date: Wed, 14 Aug 2024 11:20:46 +0200 Subject: [PATCH 17/21] test v017 and v018 on receive nft --- Cargo.lock | 44 +++- Cargo.toml | 6 +- packages/ics721/Cargo.toml | 4 + packages/ics721/src/testing/unit_tests.rs | 288 ++++++++++++++++++++++ packages/ics721/src/utils.rs | 8 +- 5 files changed, 339 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3bb2b0a1..36f954a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -546,6 +546,18 @@ dependencies = [ "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" @@ -562,7 +574,7 @@ dependencies = [ [[package]] name = "cw721" version = "0.18.0" -source = "git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.1#e63a7bbb620e6ea39224bb5580967477066cb7ae" +source = "git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.0#177a993dfb5a1a3164be1baf274f43b1ca53da53" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -626,6 +638,24 @@ dependencies = [ "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", +] + [[package]] name = "cw721-base" version = "0.18.0" @@ -648,7 +678,7 @@ dependencies = [ [[package]] name = "cw721-base" version = "0.18.0" -source = "git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.1#e63a7bbb620e6ea39224bb5580967477066cb7ae" +source = "git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.0#177a993dfb5a1a3164be1baf274f43b1ca53da53" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -656,7 +686,7 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "cw721 0.18.0 (git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.1)", + "cw721 0.18.0 (git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.0)", "cw721-base 0.16.0", "schemars", "serde", @@ -905,8 +935,12 @@ dependencies = [ "cw-utils 1.0.3", "cw2 1.1.2", "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.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", @@ -1227,9 +1261,9 @@ dependencies = [ "cw-pause-once", "cw-storage-plus 1.2.0", "cw2 1.1.2", - "cw721 0.18.0 (git+https://github.com/public-awesome/cw-nfts?branch=release/v0.18.1)", + "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.1)", + "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", diff --git a/Cargo.toml b/Cargo.toml index 072011d1..15be9969 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,12 +17,14 @@ cw-paginate-storage = { version = "^2.4", git = "https://github.com/DA0-DA0/dao- cw-storage-plus = "1.1" cw2 = "1.1" 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-018 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.18.1", package = "cw721"} # TODO switch to version 0.18.1/0.19.0, once released cw721-016 = { version = "0.16.0", package = "cw721" } +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-018 = { git = "https://github.com/public-awesome/cw-nfts", branch = "release/v0.18.1", package = "cw721-base"} # 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/packages/ics721/Cargo.toml b/packages/ics721/Cargo.toml index 23b50db4..eb631aa3 100644 --- a/packages/ics721/Cargo.toml +++ b/packages/ics721/Cargo.toml @@ -16,6 +16,8 @@ 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 } @@ -32,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/testing/unit_tests.rs b/packages/ics721/src/testing/unit_tests.rs index 0edfbe30..25986327 100644 --- a/packages/ics721/src/testing/unit_tests.rs +++ b/packages/ics721/src/testing/unit_tests.rs @@ -196,6 +196,124 @@ fn mock_querier(query: &WasmQuery) -> QuerierResult { } } +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 { @@ -573,6 +691,176 @@ fn test_receive_nft() { 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(); diff --git a/packages/ics721/src/utils.rs b/packages/ics721/src/utils.rs index cfa5ccb5..5ea0c2d5 100644 --- a/packages/ics721/src/utils.rs +++ b/packages/ics721/src/utils.rs @@ -5,7 +5,7 @@ use cw_ownable::Ownership; use crate::state::{CollectionData, UniversalCollectionInfoResponse}; pub fn get_collection_data(deps: &DepsMut, collection: &Addr) -> StdResult { - // cw721 v0.19 and higher holds creator ownership in the contract + // 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 {}, @@ -13,15 +13,15 @@ pub fn get_collection_data(deps: &DepsMut, collection: &Addr) -> StdResult ownership.owner.map(|a| a.to_string()), Err(_) => { - // cw721 v0.17 and v0.18 holds minter ownership in the contract + // 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_metadata_onchain::msg::QueryMsg::GetMinterOwnership {}, + &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 + // cw721 v0.16 and lower holds minter (simple string storage) let minter_response: cw721_base_016::msg::MinterResponse = deps.querier.query_wasm_smart( collection, From 96d2d2024ba7cfc02068af060dbe91507f326546 Mon Sep 17 00:00:00 2001 From: mr-t Date: Wed, 14 Aug 2024 12:38:03 +0200 Subject: [PATCH 18/21] ts-relayer test: transfer NFT v16 --- ts-relayer-tests/src/cw721-utils.ts | 31 +- ts-relayer-tests/src/ics721.spec.ts | 515 +++++++++++++++++++++++++++- 2 files changed, 528 insertions(+), 18 deletions(-) diff --git a/ts-relayer-tests/src/cw721-utils.ts b/ts-relayer-tests/src/cw721-utils.ts index c4ca272d..5e329aff 100644 --- a/ts-relayer-tests/src/cw721-utils.ts +++ b/ts-relayer-tests/src/cw721-utils.ts @@ -128,7 +128,7 @@ export function ownerOf( return client.sign.queryContractSmart(cw721Contract, msg); } -export function getCollectionInfoAndExtension( +export function getCw721CollectionInfoAndExtension( client: CosmWasmSigner, cw721Contract: string ): Promise<{ @@ -153,7 +153,20 @@ export function getCollectionInfoAndExtension( return client.sign.queryContractSmart(cw721Contract, msg); } -export function getMinterOwnership( +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<{ @@ -167,7 +180,19 @@ export function getMinterOwnership( return client.sign.queryContractSmart(cw721Contract, msg); } -export function getCreatorOwnership( +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<{ diff --git a/ts-relayer-tests/src/ics721.spec.ts b/ts-relayer-tests/src/ics721.spec.ts index 8aa52440..50ebc502 100644 --- a/ts-relayer-tests/src/ics721.spec.ts +++ b/ts-relayer-tests/src/ics721.spec.ts @@ -7,9 +7,11 @@ import { instantiateContract } from "./controller"; import { allTokens, approve, - getCollectionInfoAndExtension, - getCreatorOwnership, - getMinterOwnership, + getCw721CollectionInfoAndExtension, + getCw721ContractInfo_v16, + getCw721CreatorOwnership, + getCw721Minter_v16, + getCw721MinterOwnership, mint, ownerOf, sendNft, @@ -45,12 +47,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; @@ -63,6 +67,7 @@ interface TestContext { const test = anyTest as TestFn; const WASM_FILE_CW721 = "./internal/cw721_metadata_onchain_v0.19.0.wasm"; +const WASM_FILE_CW721_v16 = "./internal/cw721_base_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"; @@ -90,7 +95,7 @@ const standardSetup = async (t: ExecutionContext) => { external_link: "https://interchain.arkprotocol.io", explicit_content: false, royalty_info: { - payment_address: t.context.wasmClient.senderAddress, + payment_address: wasmClient.senderAddress, share: "0.1", }, }, @@ -99,6 +104,14 @@ const standardSetup = async (t: ExecutionContext) => { withdraw_address: wasmClient.senderAddress, }, }, + cw721_v16: { + path: WASM_FILE_CW721_v16, + instantiateMsg: { + name: "ark wasm", + symbol: "ark wasm", + minter: wasmClient.senderAddress, + }, + }, cw721IncomingProxy: { path: WASM_FILE_CW721_INCOMING_PROXY, instantiateMsg: undefined, @@ -112,6 +125,7 @@ const standardSetup = async (t: ExecutionContext) => { instantiateMsg: undefined, }, }; + const osmoContracts: Record = { cw721: { path: WASM_FILE_CW721, @@ -124,7 +138,7 @@ const standardSetup = async (t: ExecutionContext) => { external_link: "https://interchain.arkprotocol.io", explicit_content: false, royalty_info: { - payment_address: t.context.osmoClient.senderAddress, + payment_address: osmoClient.senderAddress, share: "0.1", }, }, @@ -133,6 +147,14 @@ const standardSetup = async (t: ExecutionContext) => { withdraw_address: osmoClient.senderAddress, }, }, + cw721_v16: { + path: WASM_FILE_CW721_v16, + instantiateMsg: { + name: "ark osmo", + symbol: "ark osmo", + minter: osmoClient.senderAddress, + }, + }, cw721IncomingProxy: { path: WASM_FILE_CW721_INCOMING_PROXY, instantiateMsg: undefined, @@ -172,7 +194,9 @@ 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( @@ -351,7 +375,7 @@ const standardSetup = async (t: ExecutionContext) => { t.pass(); }; -test.serial("transfer NFT: wasmd -> osmo", async (t) => { +test.skip("transfer NFT: wasmd -> osmo", async (t) => { await standardSetup(t); const { @@ -407,7 +431,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 }, }); @@ -417,15 +441,15 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { tokenOwner = await ownerOf(osmoClient, osmoCw721, tokenId); t.is(osmoAddr, tokenOwner.owner); // assert escrowed collection data on chain B is properly set - const wasmCollectionData = await getCollectionInfoAndExtension( + const wasmCollectionData = await getCw721CollectionInfoAndExtension( wasmClient, wasmCw721 ); - const osmoCollectionData = await getCollectionInfoAndExtension( + const osmoCollectionData = await getCw721CollectionInfoAndExtension( osmoClient, osmoCw721 ); - osmoCollectionData.updated_at = wasmCollectionData.updated_at; // ignore updated_at + // 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 } @@ -437,10 +461,13 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { )} vs ${JSON.stringify(osmoCollectionData)}` ); // assert creator is set to osmoAddr - const creatorOwnerShip = await getCreatorOwnership(osmoClient, osmoCw721); + const creatorOwnerShip = await getCw721CreatorOwnership( + osmoClient, + osmoCw721 + ); t.is(osmoAddr, creatorOwnerShip.owner); // assert minter is set to ics721 - const minterOwnerShip = await getMinterOwnership(osmoClient, osmoCw721); + const minterOwnerShip = await getCw721MinterOwnership(osmoClient, osmoCw721); t.is(osmoIcs721, minterOwnerShip.owner); // test back transfer NFT to wasm chain @@ -659,7 +686,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 }, }); @@ -672,7 +699,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 }, }); @@ -811,6 +838,464 @@ 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); + console.log(">>>>>", minterOwnerShip); + t.is(osmoIcs721, minterOwnerShip.minter); + + // 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.skip("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { await standardSetup(t); @@ -863,7 +1348,7 @@ test.skip("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 }, }); From 5b6b6d4512d2c0589782f0f9513adb80022afdda Mon Sep 17 00:00:00 2001 From: mr-t Date: Wed, 14 Aug 2024 13:25:07 +0200 Subject: [PATCH 19/21] ts-relayer test: mint nft with onchain metadata and check it is transferred --- .../cw721_metadata_onchain_v0.16.0.wasm | Bin 0 -> 261557 bytes ts-relayer-tests/src/cw721-utils.ts | 30 ++++++++++++++++-- ts-relayer-tests/src/ics721.spec.ts | 26 ++++++++++++--- 3 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 external-wasms/cw721_metadata_onchain_v0.16.0.wasm 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 0000000000000000000000000000000000000000..5e87da2cbcf66446a794d6697a3f79090ade06c8 GIT binary patch literal 261557 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 z zV!yy@{Z@-kgkkC_>Q36Iv4vZV2R+ro5xEO=E6BmEbt`705u#`n9PJw|6_h?1HFEdW1C1LS5b@y*LGLMz>SH z6&}r!r*4Vckqbz-3Zk}b>TaD|9tP7eDpGAyKpLd&I??7*c$Pd!=4?b>j$7KjrCQ9q z9ojUuFcTiR=jO_SKkO^O=q*PJLBS3JFT?uMk1%O|R}IR*dIctpnZEN_h_N5;Lb*VgPRJ}=mNeyLSHp`FwIT16~?;Vs%xlj+K7{+wN>6 z6Pc%09K0B_K+%+Ms`N$6qK01MF2WOD>WOa#o?T~P*>y0=8rgLSQE8q@!OxC&7X>YK zY_Ty;g|zI>u3dK^g_7#n`QS>bV{vP&Y^VY;^xfI8uEH0OcW0Y|9b_>-vIntI*#aG< zm<2G$2twBNXbBX!u9jk(3ilFIwP*oIPD|2^oPUh9MWAt3X{4;?N13ZAm5e%&@4Hcx(Ru#H z>FyiZHlB3tJ<|qBKuAAJA!qc-V2|<`M?6sh@wJ@@#Pt4-o4> z{nxGN#wVx)0btu+LC{HMaHWCUEYPvN#el};uK#`FO=o?euFAUJs?+yFX7Y#M14o#ZF+b7)aIG;31D`k;&6Y8@`2ySc=4(v;NWPy6CB`#e5 zEJlr{A!zC={5X-{g$H4vU(**eiACRFeYyic+xy0_P~>xiffcc(K{|v0wQ*Rzz)OsX zk_=&YHY0YjI}=|;86~Eg1~~3$2G_0pGY~xqDAB;as*eU^{h=@X-8p0NF7i6=Z`%FoMT z*tW@lPNjT{^Y1HR@c$ajHu&p?r;0`m<`)U>d=F%s1oR9kp*l2UtD*Oy0d@!qG$L` z2AMxRu6@+NTO#2p2CeD&q^?MTPjJ1sBTh{#V)-20kh09N-HN9-3GPHK`JucsqA2z< zvBB49TkJDBNH_Kz7+b|rN-_7<+x&@}Aly@LPo#ApM?KYkhoxc~N_yfjGC~rJK&L3o zlvY_#lu+My)8z|!HZJ85b1`b9164GP0gL_yJaOuZ5zcBAp_K$EOmQs0ymBs%=idh( zeWk;ZMS1P3@m)-9RyU!*$cYKMDgq7X%s~(D5nGR}_c?p!{`AJe%~KsM+^}G7Eb`(z zRHHozEkr9&Mk8}K({Z)tve61w=6jASLKu`BA?09D?t`%s7mWTMGkpxM`u$sCTP^oEY2RaH4#q~D*{sbcFyS}Mq3LV_!f)ixmzzC65UYAP zz38+~LL~0cSdqolDmpl^BJ+Q^#B8y>Cv6OgY(?GH?WK9rv3D6y!rV?gN#9+gw4J7Bp#Z$+0!>>^||4Pc&%V!#qR-;NG z@dyRV;n}+)hvyX2aA%H%_kq=QWSR1QJlLZd3Xoz&I-?{mviCB`zLwVdTD3^eM8^{& zw`46!(9+19vGG)B6bPH$0)I@qq4{Auu@Rzj@YqjNvt1W)x2NgHwCamfVrl3P5I*61QiFdYe*R4Ot?~5xn7hYXNDN%kk7OH0M4k*u6eklA__ux)& zO>W$;Lte~T+Ce)z8#1fFJ}oOr0e7ayksMn0GP{-8$sfBUdpXN? z-Lyv4TfauN{DpHLd*5IF#^+8wZ^>4q0!vET8sp1dr;~Y{4gZ02cZPa#25qM&$lo9) zjSO#Ubeh<&yuS5_wtp!Kjuu}D=XgvVqDfArm%qVnvJR!CW+w|c+WDk(F{5Uq=Qi5a zygeS*e0lAeYnKbkvee+l2(C4(n9B{8B$N+ z&z8VO?TW#tMzN_sv2MGDGr zfBj1dC{GwD#hjEFvN!}wCb#5L@eUcSWW5aM@WFP&qmz7~cC2Yv$t`20md2Da&77ez zS19>cyV@!SySIF-=k@y$mgiOT`9^*uOY8_3$&N}tVs=yCdS}Jz&IhRG_McimZ6&JTg z83(J7@MX)Xso-FA)wz6!O1yatkXb{O*>2^S_CtFinarEJs_XzO23mUW3YV4>!IE-% z`S4TU54wxtA5uM}U5N57yWTghtrqA3@XxBNr2ULNqB>4xwSGR88hTQuytCdbZ}xAd zhohh|OJmGm=%s-z<(4fDUGhB&DxDAx>K+LJyBr&9q2C|p!?);zcPQvy=~nqel$}lB zny~JYh+pE(DY0UI%#%k18i;lHY2B<^Z)r-A39&Vp?G4$2D&C7)&xQQV%^@+HZdL2j zQMlX{KYM^RPROEBGUa$x53mo8N?Uze6@nR-L$BlR5y#Oa0dXMxG0)G`Pt?oD>t9fi z8CIMM`jWWB-$!M90{E-nX+V@1_bJ zY5+5fuiGnCxt~+A_?qJ+vf+mcfRzt_tl-f058|FJ_BQ|V=0TeXcz39aK_30k#{^or z$y;GH{lLx~{&)d%X{n}8#d=KXY!A;-)2!_4~OrH8C0^_#Emo_ylpQdR-{R9A%jUiDZXR3wRk zaOs3Ccu8cvI91QSIhH8b^hUD@EBgJ6TmW-7ruR=^RX5pNGU?_?SXiF+OppMM%oJ zXaH-DUH}6KLjVR}$#F%#pJwwpt>z7n6~a8-1`nlCZus8tP9xNvA_Z{2e<#t7#=*2$ z!Bkbj`o)gK+pKcvTN?@=D*F9vL9NE6KM;-CaJ>3r(I5C5e*9YA|0j8VgcMwOJIKwf zZivbXoHbah#HqR}P;kp{-!5OOn5PYw(FH-D|G*iZ@p^bhUe5|f*Z=ml_0o-3sOn=m zIl}1kUXqim&zcJKM2Ri*OI{}~Pn?8x{VqLK-%FAyVt=0Hndw8f9tg)i$o)Bb9@QaP zP~-w2I8cMvXspMYAZ$b>y0zzbrYRLLaRwPV-@nTHcL>IsdR$Pp)7Di0E5Owez*cu- zj8q|9jzn4t{nbQZZFv7 zU?qsVV*y+MR&i5d6kj*h9LxCLz!_^Uc*d(ne^|Dg?m^wb)MKw=^Wo4v$SS%z9a{Yn z!%N*Wl-ZifxLnx}9DFJshY<@-v9lE`b_KkGX`}61n&GALRY~(6Lh<1hqxp>x_-06+ zkqU8R2|%t0LhMR)ahzGVfj8_gb(@ZH!U(XX$2|5vQ$T&DuTM`Xq6t2=sU`id9vdIUH4ohq=laN2_|Bq-XP65^|dsXL) zTs&O@Tt-pv+cDWB|NV!MSMI(w_ zEl8b16`sqM|M|0j^uvGt_z!*h@gx23%3D!2$fO#CV~?CMg(?d-S$pB^!+O9gAg+Ey z5Q`s|u2a~DYGvTv16qteJoBM5SrF{=;R%5H8oEvu^cL`B;d$OR2J~&3+j?(9R)fV} z2m@*|8pLFbStv#G(Jr!at*vQ+^ng}c;P_OTw~tgLK#W)}LV3ux00}9y)Ei;tS@gN4 z!gz*#5g2?k`=2=oJiy(mLU*8(5a>YGp81*%A2jQP&q8VI4|ApxyUmI|iLt zDh$>kDu2B2p+Hg7AaX6B_U>#QDH&&++8CEoyWC==QNbI7FzxJUyF@c&boG**aERlI z-BCE25ApF!-GkgN9=exPj+Oo!t4G7a^wCcHSe@%Rj8U>R)uKwrh(bx%j!9R(6&UPW zQS_J|Yh$HV!z)^jDwAw5uPOtksYdIU>}lyA8ng1&_9EjjI0;MP{J()xc7?NChQG*z z;g95#r}smD_Vf#KFBqIgV1dxF_NeDb&|g4^-y|C|vxK)kMkK?f%lwf#%O?luS%*-r z8R2k7!zjbnw~zZm83<%j#*RFinymVYP#g718kqd6db?Tm;OHghyG?j%Qj&F1uT@iU zv1#2E&xgn$4kk0FlgeXB|7Q0?Vc4hD?18Z@CfK4NhdrTx&4jJ=j$BWCRQKq~)T7kJ z2iHD0ogOs$*>&2q%n&$NMVs^*+LT}uZE968(x&+rSe+DYGRK7M87$!jWzITfB6e(` zPD;~zjuHjH0iwPp6tI|8Qzk)K4a(F;B-h>W$BWQi>qGmiW^2lX%6P|yJL!F0UN|us z@N8WrQs#_r`6V|A1bheR7j{Sa8r%`wQsb_b!LG%A4Qs*^zrd4)TscBPGxHGwa+(CQ zPW;-c)Yx!zfq_GGX-alC9{3tLqTWr(5n0#*H)QA?@?agpfjIzA3P*tq;XbA;w0l1t z!k@c;cvT(_X&rP6B=Bo(piIz>&7d|>>55Tfi#YR(_#|9!e#+oMD zHDg#q7IKR?F2!YF4E;w7_;tg!H&W9dl=A^;J`!!4<}$XpS0i8x!IfbxP|5tiRn}T# zqb=TC!Ec-_bJRL>c`jKCu(eV=Lb^`@E3V{)^tof_)|yHv%`oQL1MDqS6&R(gL2Hl* z6v>I()B{Z+HBj7qOSy;$^WYDrm9u|sn_%9_PvCbL74&9Ndn2_-ZsASr(q?R67XerY2}{_8}@}xb0^0HKfK_PEkg9)SLCw-cv9XB zz()f8X9WshQUqy~MrfIi3Om`0nN=x&j|-H=+aaJugrbguK>b-9 znPIwhG%!GOc*r_H!(bAi5p58Ogqs0cW3m7}+97(4CUEN@U49U~Oq(l59Q(rq+jI{W zEs-4-8js;96J>)QyE;hK8^QIr3)hLB%1ov80aD%Y{kg$sFJ0F#p%C85g#&~WXn=wb zyh)w(FM$$J*nUg(!AMXV(%u#R5FwSw6W(=WNjBJ9t!iM1zu928)u4t$^^#DNGuff0 z7Ware%kX5q=%5ygH?fw%7}n5dRI8@wlkP07rB7)*nR&e=NLeZ|Qbo_)fK)#}i|qD^ zjlUs5adI_>LQLDw5aoafxjT7V5?pxIjV9P3MD#gIn~Vii6(tjr3&1rDdv42o%Ebjz z5cWztE%e|6XpLq!svFfM)ol{`Vs%$7IB^eqMHWA_*%NO=?^xt9mm$6kPAEm<@kXpo zYX&p0wnce)XCYJpZMsX06YmM%$3BnSLWu?q>08H81|T3C?af^9Oz*)nJsY{FJ!kDm zD^_heY3Rou{lUu_SJd=_=^;JN7Mb?MJqiX|rL~Vb0g|=`Es6m9a@K&>hOhp`;Tvn$ zxB)a5prCwHjGxT%ar|u`zh2+059yrds}{HgkElk27wzy$2RE1l%DQp`O~EFs0B31p zc}bcz5~zVY?>ireEUKC;;80f6l32tFa7N)T_QJc=v$3givutZ(w+~930Su_GjN8%= zDXKOko_MC9oC9)M>anpLCoRhu!@G#bj?>-*k0~FQl)6_KkBkPO)(A}WD19fdPjG&+r5I?CL&i8j^ICGV(E0;wq}*`%MycQC&s(wZ@uAe|pMK);8W0bJ~W z)QZI(P%LRb+%%Kv`2eklOK*fhv7Y(Rv&10G*7BZ?P%o2FDjx-GLkA&nQMwLuc`7s|WVdl;-HszBNq6-cLB&0VL9Dh7TN zQ!b>IBQ}Jf;ZH`j0q$E7qE{2B72uim@IT)hYmukmE;AHT){FucDj>{>e*1 z^^)`bA1#aCk^H_|ucToCVThhY&goJ=f>1quw-iYb-0+u9eLYOQmki2}8z zzNt?8dR7sh{Y{bPQdKB}^Df5o^l#~ba;CAH_AEb!KkFY~wr zSQ`CzIYjT;a4sz+uvVn(+O&c8q&Z?2B&PY)go~lp?s@O{P?KPaMH)ml-EaWQ6tMLJ zKW0GPW`ej1YE9S-&)J0SFei7m!F$T+6V$N zQn&3QOO)Cl5$ZzK<<7SUW#Js- z^BE>>d4bdrd?++dZd8??lv&jq6%qN*|@CUrGCKV@~hi=~Jd)*76<>;x#Z}z_5-~7#NJ~8<% zRUv$xy0-QX#%IsMh!7e%f!~wv|O5WBe6BLJDHY0)+%zBHyh!54Yq+I`h;V9io1j} zi~`)kN(fR(bO|LapOG6s(I+><&KQcQ84u(yK}JcRydb7+C{!a%Eys!gEdsj18UYy_ z642=hK^Or&(fkboJ=y#X44!WOCi%iyelKN~!N>^eA?WGM8iIl#VCWVD1?Ncwfq7~? zQ_>A2Uy2}JqM?^V5GG4#XDvY(5~e4%Aa=E+@k1Qt)g%q(s3}U)3QYPJd-X5j8;)aa zNXnTZ52c5B5I*aqAsQisiB z(M-UHR!>urA6T*Zd(mkMN0N(d-Bf+>BbUGod}+fu>e4a_(e;=y7))WtTZar_6t&ut zyV{aOC@f=Yu718~iu?#n_Im+5j9-Lu%l|ZBhX3}pzF#59SEJlE9qMqFj{pF zGj8e-NkAI_q(huFL_{6pS$??QW>pITRILSRwIpow6QO56ZDjH2Jl>iTk@sZ@8xa|w zkw1|}TvM2E)DM@57oz3mH)K=vZ4%X?JSDm(P|i1m@_Hyr=0`C;Gwv-{#~vP+^W8jS zH8#_7Y^EWJ_eCisVD%`2JE@)-Gu4zVGydZvyi(x4&;TyvX#RNM$z1{}@MgKm4~;bN zNwKKGXX9T>+DY|Fq}|XCT_cZ}ooM7uYPd{|^4;ZE%XFCiN>7Iee@1*M0_`Dw_;hFj zZQT?)^Y0tqe1ad1DfG(CnU^9G$_!>>4h>VH}E=I)#6+~=JjV`gIjKb@U?!gr0%O#dx{Yyj%(?v{8 zRuLCjMYgiN5sRa9*h>9zXG0d}obhs598T&=uFE;-=b4aLvWdch@&e^`So-+F%+`MpS=YJ4o#_~4E>H^)cGCL(Qr zD5mk@KWls}IzFDvE57xEjjeo*k418k>fOS)6i^qdMd70-d@M$M=+2G|0Uw3R8a@cY z5kA=7fc2_+SDyWiIE+5N2+UMC%Mj~Lg40FNP67!d{)=e ztAv|UZw9i32n zey=i3TRdoq%}&tdBdJ=uvn8KA+})_uaC4kbq4a7kD&X#U?sjK4;391qhhHl2YAaQd zR-<`iiPvOUDqp6OzHQ-_te3jossh*I=wxN(pw)0LJ22W&CID`5W#xnMj5m2UxJ@w| zTvrdVKxQx($nIu8@nRsK^mE3$i=R{-Q=68`jf4ES-83R^l~+0&ES;OA+AI~{Ikzcn zp$0O++4;-qW7fzH-ag0iO(503nDIdXIYWU>k}URk7u&bCjA>jHTKjEPwG)ukKl34 z!%*~Ta+a#yL9}VUC|WbDZCTp#1??q@hzKA44g2(?LM6x3@Rv-b7MUkvfY5J6j^Yh@ z+bzXy)ivVHI@erty{_UBRnZ<^Ru!CS6|y@=yQ+n`p~l=C4u%8?;4oYyEXf}i>-_~Y zF`9!!SzcglsazbwFK7tru#Out>H-0O0sj}6-;rk8j<_Wj{BSpO*``$i!GpQ@ zU#yy2VX2lXPYYl|;B68r%7Wr`!!^XgS%b8Tq;add2B`Y6^?+>8?Q?Si-Oa)`Anll^ znlDJs`5ONgWsa6r3t=LbG}Rio&YvO4uJJp1pfsJAxtv zdjKLIjR1z0n_MD6j{ei-4~7&`%4$Y=tSx1!V{=x( zXeqeD^iuP#-B$UjO51KneO-|1#3W-HRJ-oA?b#6*jomhK*}{(^yBrFIDAJS(6SZa~ zG(@dy@`WkbA``4?5o#^$F7mOcr<@yQy8clDFJywyaXLYyp3D%k{&l zmeW&rR*HJGPGMhxsJ5-Q_1$_O&%1>`A1(ZuhJ}wsVn3sW&(B%6@G(l%9d+N` z4k&DLFen(&+y>o=h2J$rbzm00>?tXu>0@^fnBrfjcrRtb#C8}Rw{GD-qB9kN#6#@7 z()FYQXt_R^yOsYy4m@S%5BgE>G-JOTh#4!Z8J48ONyIr59lS#s8hD; zwqULsM`vp0qg15)O#FSw^D;$4ib3B}ywj~>rKOpT=GUoQEt)CqpT&$X$^JTJpAoTZ z)M8zA+x?Vv_D+Vrtv23u7U4SM{Xtt3Oe1x3`T^eo>s{Tze1E4hmXo@Kj+(J*1=*Jp z?KTW5H*C$C8{@KRWT9%wTRxUEWs-|Z_8kG8ENWY7hL+Zn-)RPB@lFNM8@6gI{d>o= z(^mRBYb#xbgj>(iLdS%{PDR=ee?8anro^D%NcJ7G|6)nxv#bFB3=6BLh<=mY7><_i z6eC>YHA*$}8%j0j6qE|Vn2XCMv$ps!ZD!m6mbYsI%U9944bLlEN~7f~aN5Bf4tKYA zWu1DOdoz9CDf)#}$yUytSuU-B>yJ)AAHZ)sRtgRCKD~4N+FQ!Ex&dt7*iQA@Y0LM_ ztCnwVHot25y5%be085LwKlr~k2eUAt^b%d<)@U|*y*oVU;4m?5W@4VpK(8escXl93 zl7t=ilH>K}%}qEAI+BEhm@kdzC3<-3Qc%8`=aR>prz2(+=J`S9Tqn80w@##=OEeaz zZW6aEqSV`DKJ6LJd2;vS&$ionNjv2+#$)GAy0l|kqNE*s)WjoB^61Z=!H+C|_AJtF zMqI#rb?IZ5c0|874m5!SAil~`q0+HEM#C*qkI^2|$nmXGh|Aj<8<+2rJ89Lw(E8&1uOFP2hQB@pf^qSxMzic%j}b#NGz6 zxLGY43c}73>EkTlI7^)MC0Rd7NNhT?#aTI?>`eB^8ZB{u2YT_}I$?zYYv2Nqj~1C} zom1}_+yzCZ_CFe0fe|t?B#^qRGJps7FfOTvz>n;prR@_!;wjEpoqd949%(mcaaF)+a9fv*J zRzJLNo-*UX)Va-+HQkdMq=hH}^W%CR;p`L+i0NzxzucJ3>L0q{#oRsF!=anX-O zqU{MH7=tc&Z~*T|1hqwIjuKdF+znx;&tA&b1?sPl?)5{lK)Ni+(Ka zXs)3h^`#w==wwPuX=q1ES4JiiFOzf>&NEr_5FN!fcuf^eI?B}2>YQsw)^6$T_L5dM zr5)8Rs}wCuN3QCY>xWY99T%WNm4X-9KWJL;Q(spA#zW!#Fgv#3D5C8$p@X3vYc zUN##NcvAlcQCjq_e`7gnVLTcrn;jqYk~8V7j4A|uvhM3|gd*3?*wW)BEt;^$ZB^*d zvgRxbm-|f$c657E!F4(L%oV$Ek_4rT(u!TRMa?5@(leME<^6^tXLP)rMpoJ$Lh*Gi#n)+QvPl#~m*VT#OcLYk@Cqxwu2g)TeOhX0=FzcDmKN$; zMj#h9<%BSEKU+U%=qE$=@IoLt7?;Fl8tRW|dMv89T(329q(CW;CsT1Tg+VGR5nFFG z#u9^MiHB9Bh^}=QB)gCVY9cOFn9(QWT&^u&o)OIGbZkW}k(AUj6_}2C4yCp4K!#YP11DYEuKW-_rv$ez|6V3Pn*Vivbt6 zCxQZ``kZ0aujGjCWDc{#Gh$Z;m17tC4^m3GzV*XM^0JIwba9q<9>1#GHy zK*x>3xYTp<>qLGvunAN1FE!vob-P^&+%^%&r$d*G%$8vkwC!q05IYCxYS+QoVj}fq zNmGW<%{LS`aN%sQipvnL%v>AO{4V=RK%;&GzC~UVj-{E{<2FfIg^cr)T+x3<{J8vg z1WSuorjDp^3Z}A-!0pvE2dP3(0w8m1$S~;P5Mr1__*89+Eb8!n0Mi{xM5>IUfQ+Xh z{-z&7W)@jA_0%G?Szm<0=)4*PL1HB??Im(u+8fI;z)R4%RvqyAkWG2a5+<=7MBCKH%5X}q#?X777iU)D|Wui9*)A>CO zla@l+4CZlJ6SJ%k_HJ(mwyMkzeuj1HfDYf)mKNVX$d$z;`;E`juDC)4GwOkisLt`G zZM&Lt7kUM1FZ2pHtK$Axe)2&_AQgm zAUwezqrP9weHYw$w!M(kmc2{NL)fdfqvM~4?HI#GODk@{<64GeKc{_RI-KGut~XJ# zgtWvYb=Tu=i7T&evyS8e~hRL90KtB#Ve9&{y{p#+?~q%f3oE%Jy`h309@wW zCv)y2QiA(rX}~oPLz)pFs#Vg(aXXxQH%BeNn4klQWz5sTrXrh5TwrX6!-_$Yem;Bv zlqk=!f?2_UYU2L3h@e6U(z({>sne2}aIbs;x8j=?A6(L*1uzCNc~}ghOBZXh-%OKB z&>xM&Hb3|Zw5c^%S{jUUdI3FsneGXi(0;syGBu=%b|s1>Qj75Z*fbY}gT`s9O0it} zI3K^*I4yX_*f78@B~_V?$~UY@IYI*1mO$3;^NL=H)a}yZVy)!E`O30_h&lMz?!#uG z&(akos?EgEr-{}o7t{atdV{IiKGwEmjKwM&kM)Io;mHOr_6>su3U()v#vbI2i0_K0 z?W13v)Anx*hmrOOwV6ej3nF%sBY+d}d`tSJh;iEzPTVmBL=j3lLu!Ykk2^zZM}`Eo zzk&TEmQF;pujq8qmPeXvGlTA&QQ7*@`TztP3#BJjP7Z?X>TT}ho%SJXJug1)IMOZ-Y*S(-4-Nmp|33V8~r z8Dosjr*ZYe7Tu6Hk-@etm}i<+AADWWyKg~>fQBpMYovbozbY&4tM603=7sQO{wohw z3p%W8fg_8?{ZRf>r5cQ1^_gk#rEP)(OM|5JVDka@*Jz!MJs`fN;~Hm`VnXcDTA-#b zU?)#=SbY}m|MlV6e}e!2iUyH~^6+^0;pGm=zqQq!`C2?YCgKT0fw$GHvO7~B7&@L; z*QmyAy7Wef-F~%=?X%PYam^0@q#(v;mW;VPlV>3o#>zY}+M+IQf#g(ec;JOI#wO;{ zxZ>#O=fQD4-^r8-xy@dT-beveVU`QZUY`r*3Ve#a$Nm?;K-{tc(|{?dt7t zv{@$*Wuw^=u+gJgRIzbiXX+5EqSKlbvhwqcKAb^Xk}Ud(Uj!UV7R@UAjsw+}{c~;g zh>e)D)m9+4JIls{17FAAV)@JYTj1X^X>y&&9iNwaP6ObC=mcR$C5lFgR$ZcKlqee| z+I5MtQKHo-(Wy(2alCG&-6+wmOSBs$?CkK^(_USo(@V z_nvCjju^!%S0n>?AG7^+`R4aj#qt~9bF^wLzn;JC_wEGsIR4LD!U-hGq@i}As^Z+u{o;aNE$aTIu6?w` z{oj^)dh4;HM~~i;p*W5Pg=0-JPPmzG;*70@1H;Np9?yFq+6XuLBl@op}@n`^u?{P1o*zMF5nGtBUAA--E^yo)#?lEaZv4GuK$u~{y? zuQF^58`u~&MiOUG*c3;(sqrphV{?4Bx$!PxV@rIurSUFdgG|7NsjZE72^-hMch@xD zC2VYq@3uAGC2VYu@3uGIT?!l0puf82#$p`BVq+AJi6UGZ-(A~y=a?wMj`(gz)+Tw7HdUKPLpHSc$Fuw9#J>CCB$6kL!V9$kT9|9gm|j24iGY^C zH($qbJTBq&;fx$FCdFNBxlB9%3s&V>E-us108Bs|*z)j=cObvWE})+{!0*(rX%1hI z4IE7#`ArJTWo=VN7LrF{Wjv3BMS9NPqy3Fyw&@?rom@)DV-8C|y+$Y_K{mmc#4@B34(dH^gQJjUi*<2st&Bo*wT^nf7`ho_~BK^{}z zJXRNxd_WnjZ0G+Vu*D(d99Xm&A_JjpQb#p3rFrULokXh_yF$k{xXrR?jfxpxwV*@- z*@(Er(bi>Fp13aXf9aTdKH&dD{=b=b2!v^*G6KmVS`{2Kn805V%GkTE5YzkMs8rfK0HI>QzuI64zoFt1(v z^w-X_OI~yIDeDlt_6gyyoo%;y-MX9ksn-ZQ-J+&wpI zuYDf)Yv|m0%Z|+(b-h= z(Yk0E%5b_-w2Vc2spyk+QPP2ivyGyhj%OX{q@vH)McbhrUTPHObX6~oz8ddlSVb!OXkD}$y5V%ADCQ-#a;&ZKu%@*zs3G=3J$)A-`q6fy{n4g4pEQ3o zk2I&Y^8W>9B4SHs#sc;FoUJyAY-YnZYVuI>TTIkI^fWJdV>Zh2>R)Bzo(qy%*wD5_ z={V}^)S3a7V_&+jrz&S|M%(eR zLC6knr4I5*7=!XhS{Cr@Xr)Dib3se*R9b3D$i?S zE?1nja=$2-lUD1p@XRmi$zr8vi+EdxT+i{$QkpHZcPg+$h5b{@WMEuK9?s`pZk}AM#^I6 zu@oy%pH7RiL>Zz0)Stc%N@fa4yxK9UC}t=G(8QPXcS9FPmxXG1L}KU83&!TSfRw8( z`ajMPhzvh#DAeAb*d8@P>8zruSA8##tH6>M(4wflo40qosM41vzSexnaunux9Eu8T zo-Ry#=^MF&T&>hB^E>=aio*z{j#(;}SMPgwp9=#%3blbornS~A! zb9-Hg)U_n=6cx*PU4-A}dIO$gE-V7@pn}eI1Y$ufnLLd-cdTE0Mo+Z#-rucb=;C^o z)&h8o`^-+TjmQJ0HAJBC>qR(B3O-J{DI-mcK| zwetG(uKV}Q+pZXYaKTf`76;0C=Pr1g!@aU%&2vp;_U!$tI@F%)x!wU}b7j=J3 zO$ZXK=&FfuJUtj37=FHPPDk^ZBOX?b12Y``^)t5q7|P)@MStVY#GX)d>WtP^)R_m= z#`f?DwFggDS%`cjm~_>G=8mLzSe`6dxB{eeZr$G7yClU=V}zY zUt2>HaFz_koOU@u@xRW!0hXu)Tytt_8+TgNunux}n{`LeZso4Bw>NV~9#7VZt$$iY>Hp7DQr@d#@A`iRA9&mR06F99%Cj6HF1)5pzlA2A4hDGU3H3bV^|`eAwv`7WH-UyxYk%(sRh_P@I(PY1VU2B2)q{0a!sKQ43Nvnls*YYw zRR}bj;$NV3krZIFwHnchCJ*a)TD1`X&;Ex5Ko$0`YF60-XA^*${bCi8h%dZNlYrTpztKb}?zckf95i70dvC5kS z{rR@D7MERD$6A;f8mxrLQHimI@aN3DKo}*QHJsoPveGz5qvRKUl_JBbk}vQ`CB+yT zB|oi_v*Foa;ez_2I-~rR`=(j)lPWp4VM$GE>Rd1W{%?J=5dN($QS?5h`zQ1#4nYrw zpDPrS(`@cvs>sG2(}b)hV0n*9_BZU9=3ArWU;PRtd*S=^fy7l~u$oS>q^lE2g8z|! z(-hdIJ*C7J}W5+VH|Z@-E3M|mVvipCoykE!Hf3h5_e zNs(})TfuTM4x7K3~!5IB}Gbe z38?ALM4&|se90QY4Mr|crk?M8mPf#9wC%sSHaiz__wh)#1T$$3n?|(hnJL7^Wvnor zyi5zqBH=M^mQRB46+*3Lc$hd;6d%A;Q@48PGk5dI4N1_jAv9S6SV*+c{mJI42ZMd@<2`p(F}<~H4H*XRiVDGR2A#> zoW^FM<-4mCwnaPIN(o5P0#K<02ehdO5%6Grp_MIE zYE-V>)m`nBwn}5-FSYxBEfTalq}buR-3oK*+9 zyiV)StjVKj#Z`Sc7NTgGEImk3b--}q5CIfSP|s|1 zgc}>K3s9nt-c{p!U(BEz@x6B?;#*M0eF-%75~a*1Z{ibkXSJA;ljA?ZT(6#yRT=Te zB8+10v?XOu4@bntE9_4X<`Z35XWEtqr=}2(yT_3bW;jR?GUd=xvJBX5Pqd=5K&?ep zYgq=2R;&O|uC*h-tTV=BJ zH2c_`{3fu>WiQi>%WWDOhe&G1wQuJHGNl^O*>!?EFYdxM*M>&P<;eq5tOzL}&(5VKUZ1Px?t_Uq5s_#0yvU&*7-^gq3^DRv+Z?57j!`V=N=fovMmE3!@MN4lM(&5) zR^Vs&dv+YPOuH8M#23Qg6)k8|bFz0$1d>hfZXzg5P^VvlwW34~*4zPqv&n0YAl6vP zQS)d2UB>vC#uz`dc8ofcMcv2WVVKcC8_x`-hOJocBEz&=j0o!if4saPQZ=Xi-<6@#loaGf&t_(F`Ded2%fgetc_!m}2djs0usr-GzdcJfjNOi> z;i8x_WV2P#_A1)#oU}!vsp)Er4>hYPV>K!(A=!kb!51did}i=9fiZ!vO=BY?eL->2 zxZky$w7h11D8lh+jiDI+B3@?NG+Z$PT|GHHXI@BEakeK!^Hpo|RqW2>LmmhMH4cBJ zsB!3F<(inZRV4fYX&4lv(gFx~%?YYFavB$VMm%U*;=hg<)HBDRacw4OqzAa7ZJTIo zG9qwj%O+r&anPVSBMe3nGLh9uh`>U13^CuuL*@huKG0-Mpd@z--LiAsHD6T;C@XAx zK@{+M9-E0@S&1amTQ1WSFv|tE^2^O73l;ona9Cr^0U&x z@X+eVtS$mzxZc1=8GTT5x|6?PNq2Isia5h*%3E{O1a+#+JGG5hKpCMtG?pphpd)ky zV2#?CK5X64mIR`o%l*uIrE23HL-E6*HLGLjZ-Er0Dt%DkKj(~CP{{*HN3de7giz+Y zSgh26zILWxM@&%j$OjcCWDKW=-gd0=WxRipQ z2$|c(L-$tQd*6^{^wL6!I@(gIYEdjRO`ww^++E>lB3weR&;Lhz^z74-mczME@n965 z&`D6w_B5A`DX6?Es+sV7ffbfWM;+OYhA$cC0yKgdMr*ODzYB2fXKZ zfaxaIZ=`?a;rws&bDMs`PB{xsKey^945R5Ld0a1M!^iCcf^io;$$isJ@*@Quu~zM{ z8K24x6rnpbyV5)~Hl0G2IfKZ;De4w^0buAy>j9?zCYF-bC*js#oLKjTrF$_!ls?TXvbC?j+fE|kx?)TO8KS!o%MfxOEJfxSpb?i7KeKep=4ZDU2@^x z8F$&wfeR)w`}|JnuCVzL$;P-T-@d{S;g8f0x6Jn6t9@2rSGd(~!Th^T2NBdPv4x2S z&iZbRqkQOU5}JG{C?dA^-ePR2WV^!;6~hM$JPqA|%w@X+yKaW97=AFOzKu3yp0E^f7KjU?6Nm7%x0b zbkerNQ@zybU>N8EY0w;CQ@-nFJhv|1?$odXvF(2NRP7Jz0`$twa--@aN0AnDy3 z0BA0?ak(H`KjL{+u@UGN^kN<8WM=>%PCL5*1#L=Qn%-)%0R<5>?x4IP0By0a)&U5$ zb|U~0!22>OXuE1y2Zn2`-i;^-ho1nn`GNqtRs}ALg4hjp6#&|4U#$ZW8`n1i&}&i~ zS5HCLsR}5lwRZAduNPznZ%#g`$~f;Ze8=TI(RnA9h7(eZ%Xn)CHrEN|JI1plgjS$4 zIb`;UyN%3(IxpTf3TG2e94&UD9j1c_`d=urCm)2GEr7g2&{*NXBy2 z-p^18LjP`&PKFVs{>gTt0>*@8J}RIT^bbt*@Qo691)#!qCelp>!5wJ=SPQ8#gg8}V zXzyc72IS*+8n!!~Z~zU^(|nf@N`!_59M}VtLJarsv_1K_6lX-;DI| zlzCWCX~J$(Si?zsz&w%ak?W#-CR{K&8y`xO8~}CKcBPu0eT?yd4Ro9n%-d;04{fPu z$vw7mN8g5i8E~O*z3?tt4g69?ay5kGJg}4q{ZAI@T=fg&Y>D*WcZZgXnL;{^%IJe#LOz;=Ob6uDt(xtfSmy|UueM)Lia;awd7w^RGp17Xg~|va&pQVvF9 zz7J}Vjf|DB;*jJN^8heA2F#8DvsfN}JD8Ejoi#JTyR_B9GMvnTI9{E@5$J^heR;L` z)~Y~Z9>rW3MmN0D^<6MC;b#Nbv(mB3>^Hz$SCz@-YKJ0hT*g=^_IEZI0v!!;hNQ-i zUn=^)oKHDHoK;SoATD2x_(9Zs2d|99=p)sh|MVGVtz0iGR^;J}`Z=qgW(NPZewy3H ztmt3VPjlOV^E^DFpId4#&I$cYUK|l~ikdqA|M6lr{FPlQv9oZLvgXCn`Wg#pA1$W_ zu-1!n!2vW~E%zIH!U3DyInUAHv^&QZWReqtBsOcmczWFzwkmLuFHW!f!q!eN^2Nz@ zUjWRDj$?J*7i*kM>&9``2G(?BZR})vFqXSoC(}=g?wDfWOFklJEMmh=SI-OJ+5&NF z_s}y5F9iZaZZ>)c_msr24^8Y8skncS! z`WRMOXSanGeBgF3FDs<7+A~=yNu2fnCNEk?a-Ca_(@Wg16}D?R0g0fqftLMD7hMSI zfkn-J-@{?gy>HBR66T-`c|7@62Hy%hIaZmKk{nY2PAPdQ`6Da9j#xptMnxKbw7P)vdW+Sp5w1W)vRcV*ZbS%CMqjlqe3279P&k-9l{~?t3fLHkQ7R=jpM6 z2;@Ji@D<>6iOXUD(Lbb~Hey_ZvsLWBn9IAuChVgt&l7czIU$s!N2xt+J6jg7!OFIE1khVn%pe{2Pk{fe~m z!c;5J?N_9gKb>kt#_LxC`F}Xo3c}zk(#oGtwE{0Q%iLx{WeyG0EqN*+cJ?Nlvb);C z?#Hmxtl}-KkweS)H8>p3=@(`lzdG`z`0Ke)YPtO@(*G}c|F4YV_iVt&F4en@zA-I$ z1}zv?bcyeQ$a!|JwGNvsS;s{$_4N#&!Q^gXO?9VrK2D@dgVn=GBFmN<-$dM8Le&{V zTreTsEU;h&HXo+*8)3Fa`~=nH8kX$x0Y!m@jVOVoPAD?Zq!o_zu7WgNV6p7Y?mvr| z!G}q*3A{>4{58316oqSLQf6kd$(yn3&xjV6PNsRjMeAg#J`;IZt1Pp|W#}BaVaw2Z zIx@Y>sDop<(i%N) zPkJ8Jivt9-(({nxtZ4QU9VvkUuA$%sv(QziLt`wUUKk#46gcV%mDd+Fz@UBMZ`O=; zBjDi@XiK+1D91HL^RQ?=fXYFsURkV`ls=4;p$twI@3c5-dg69Hpd>yhd2;IKuNN)J z@5K6?JOym&M;Wj(JzNpT&!W%m3aea9pZgB;Ux@tli9x$HE~K@HrbcDCH*Dr>OS2GW zz)0A>KcIdc3h!H8Z5>qD>ki;U-*8jL-q`#I*C-CT|FZ?bC4q>K!fG_AYoQM&ZHNxUkJ(38SOt)AeHBnkzaQoe!Dp$VZb z$Jg3sPf_cyh32v(y1_eYK$f6AH~Yilr-7h50l~U<@ee2!`Z{Q@dk}jjTxy@MR%UIn zx&QGzZ%0|T7IHOmP+e>N8Sonk?Ct~}3EZLa6%rZ(e7d<8z(wtR5GM?^n8$qt9TzV*n@_1Hw(nFar zrbXid0yh<&zS&WWk|YCS#aqhnhBb=RtSU!1^*N;F-|hcgP6(nX(5COuTyZzb`T+&3B8#dl%JE zqfW1|;2eEte4Lbq_T9)r#l%GUe~15fCk_CE0c2Yp&^X5yG2(_D3{jD0&|D$T$=*n@ zQg#kISnZ)U81inH0}>h?KwjfARM3OkV{#-Irz?t&?OkEXL}Bzm1_NvkJ~UFQ9R&je z0KC;JmLP!CQLuWXR2e?TE~T%@?7#j6|4Z+gk`{C)=LFo*^d_3n>IaDE=&;_dylcDY zEUpkcc@fnQxLHHiHt~`21AHO#uCS=IiJza&R_G;pDydC9Q^(WTbfpMeq`oyR zQPD6~H+!Yk&58?Hb;>uFs#`qlq;7kOMCzHfGS#~&V%O9ZvA0#7jZ-?9j)~@M%tP*^ zVG=8iN9)kNxW}w!d@`~+DYJUM#AG~Gx_3n>Zt1}4sb4nmu$cP9BA>s$28|htG=lg_iY;&53XYek2QMr~PVJzeGRt8sj*XxYizEQ*{rz zSp`e9nG>xMpQdh*@M0*Fi7VbngaM0em>;4nI z4rT*Tx18V*gFALJtU8vef6qPeS$K_^FX6w?U5(2Y26J8NCG0~4RbB(+Y)vU3ko2|jR|UjMG1D;jpyY<$NkQKI!%H;F`9Y5;}Zpain8kO<2Uv9 z3--N$;U85Cz9ugFjKM~o=sYXeXr?`b0Rj)D%rgUmTnkHC|K$I$fcy*Tqi_qiNhUh~ z1~v)04RA*_Fe}GwA?f#+bka6E7X4`gq5t(}0#3D7?vs<<%v{-FlEwZ3lV~48sQ)MB z^rDOOgv*NaA1gMRhgHU;)f~fM7g-V~B~yCt z4@72?3#;%m(N8g1@wCh*Jm?zX)a2p>n%12OJU2Q)s3f9$DporAVw#38zkByzMfWth zY&G`1Em+>fVx0I{By+~)e3=zElo~Q8Jo8r$4ck}@;fG(h2h+j|&_V~JvhksOOau6> ziCD>(@Eji^tjZ*;nn`6I`ENG09bQU*bjlu};Xu_fJ(T=yB|Eh6gNUL>S+Td=E37J- zt7WQ*PxP>^w$ol)vo3*zzTHHVF%8iZu}~4dj~C`LOW0nMs0!^wBZAu?=vPKEG}#EI zus^f`oL{b%iL|H)%&u?Y-LVA?#EJ-9OOyT?hl8mF6{H5$%8I>ws_2obP?DA^8!4_tUn#1h$aP-#jgLIYRtymj7K*#aEHT0TG^zVH55MZXJ9sNHT6$>6~!&+1H zpaP&T$v(XdXfwhDNkJPEtZEZX2&lz+0H{!f_4oV-KBggA{u??q$_?{JfDa*p@}KSq z*K^Bms_uTJFJ;>d|L-0C-^ba?VSI{ImLFOP-^3DRSMl2~{^Z9jVGOa^ENPL7<^i@| z$qQPPdke55@Sw6&G0`vpffX^Ww(!1c%h05x4y2)4%XYwf40E8|39mUES_dHyv6^vq zCxMYz>|hKJ1uaw=yuFhDv>e&2=ddGL6h*+ZduaG02z(#u!7+mso`1CXuIUbIr&dW9 zMHhxeR#>>FlT6Zm2gkH#^Ht4)_~E}P#?~mr$fRh67auZEvR4a41HYAf-u;A#EFYc(?*Kfqso_75J3mYLk;+5iJe?;3zBm$|^I_nBfd&taOXVJr ziU-l#JAvh1iI`lg$#g_QhVj<2UR+A9dY>hb zIcMF01_ft1+4&U58q|*sOD&)wyeJHbG?O(?Cmn5LbIS2kD1c2l>>XL{a9@>u&q3!P z>d+yOL02Ctm+ zr=E!NTT?)T)C^_~lAOS-Nu12dnm7$;6XNU`ab_Y;M&rcE1x>(d)Oy@QpnwW@-)LTW ziW$);aKYCk^boSgW_Bo@%9#H~)C%KsFdMa}qRY!HHpn;h{=D zDQl=?)`%N&(pI9&#M1#5$g%$Dl^K_bw#h*uh`GT@TOz2;0f#3ZNQxc#Ofa3%A?>QG zO%f|3w&Wk-Gtd?k)?SVYCER0%*gc)Jg>?dj)d~GkzeT1nQOi6bIEJJC0=adZyHQr| zrS6}C%wzTe*7jlE;yVw;Dze+DA>V&5o1~OCX83R+88LhWA)(V$yqQ$*n2lig0reC& zZ)blS4N04BRoSFQKt`3G4g(rvkv8^5U8G0$y#*(VVCj)moi%OW0ez%a8{gMo8~4`8 zSVWYb zayV*T(Hhli*ueV6K>z>l-UYs{s?7i1mveGXa*{TM0=)q{5oil-nzU(>hFedeg-UIa zaud+@G&yb4=0+|pRfH5eiUNYlUl~O$PDk*9m;dXiqbMj|XH;-rRCIh59Q^1zFQd$V zbjHt!^!@&xwfD(B>4g#T^LPFypPjYWUTd$*v!3!dxdYKBQ`|2& zW2e-tvR1y&ffkb_C+6$Z-o?AgLR993x^(n6y}3b*EA+zwK}eK^4sT zU@XH|WKBGyuhKP(J0XsU?sJ5NRuS+Q)+TCU?fZkqdkJH1swZ$$u-HhKpCb1uZW4J1G^KpQ7%LS=4B?h!q_5hC|oXjhIlUSOmm=SInWbWT0js8#5xF%=wzzZp?&R zhG5h|KBwnu)C{XkzA&EVkWlhbvyHV%Ncu%z1p`^0)N2HLVI&ngIY|@2$iJqx)oP+* z3ec4XFu^&FmTc-VLoo?|6ZNhAt|fp!)e?;<*^{c1$&hF@O<^(_Y@8_uz)34T++>iU z!PuDf(};2`Bc|aa(Jy9f$h{DD8VmkP{1!ct&Twlwhe=v#4xB>cv`|=u#)(h}t2`>- zO5~nDvWV=U^JL43Em23wTxjRl#SjqMg zMfS%j3M<(@smT3tio!~^2Nn5voT9Lj?aPXMCQeaU$@Wb}9*$EKR=$k*c(g_UeS zQsl8XMPVh|(~3M1rzossdq$Bb;}nIJY`<3I7jcThO15XyDKWpTkQly^8?%O(x~92@ zsFyDiX~Q(h&*6V;R>9~6KNq=qiAb147rxDzGnq`lyO;rZ-)Rj}0vR7? zwr@*SnK#g*O(65N4+(6bU7$XJ>>5Btff_6C-mG~W&vHf!p2HIB-BGVI&Dy9h4@*7> z=ei!p3Rfb!Q!FnC)Y4942&hgi+GBwTkxoN7Ks5VUtIS!u*%=RoY3SVYK#^%hk}6sW z;~?7shx~a2)|`126I8Qozd{t8s@cefkr;dE8qWE!k@lVC)nrY&$t09LB{8Z21if3raVSd=ax62p zNfXIl(Im8CH%pc+KI?ru^c<;l(m2?xof3=!)*7lH1EZ6aC|^*ZjQ6?27ekxaM?Js%Dcp5Oq3FQQ6Dyqx`^@Sn7{zlVMTy2QsDRGI}=5)&%5Cr6)O5rLr${~f0 zN5L0MIEQ~a8USJ&oBa$=tQ5qMX$+GjlR=|wCX??-re6Njk<0+ROo*nd67muwNi4U61;XAmX)|$J}-TaJ=AB3pgCh5KKYj0Y(+ehiv>YWOW;h9Z&-&ZD6R1_0ysktW0Qf$YGQJ)hhi(cbx!A^0+ zSj0$r%Cd>GE8GZJ#Jr3GQ3+Jy*1%1+`yD^k;*T&wD*%)>XS#R|T;+(CbI4T1IH|dr zur)ebVJ%0soEQ4Mm)V|fpVhd>pu>wjt#;VSl8#zyIT@Z&{530Vq8d<5!czoDEol2Z zNW58m7LMiNB7`d5^W4Mk6jY+-4e*5TyaCv6jb|Fe@-)=}Xf4Pb%t1VhA8}BSW$KAI zO<_@hV8|+1Lu!#%{89Xv&lu^knr-hJm(tv{6*dtqZ$=jMfm+!5F7U$t zF+QwByP~yF40r;K!}hx>jX_wd#r*-MTSuMR%fCMpp|)g)+A zGvqRW0jY#g=7G##PgN74t^m;u7i9qEMD*AOI8o# zs9-zI#Vu4cSiUf1t2=#BA*}c)IZo89cP(H;6z-CdudZog)KGTi6jOj*IWrRGv@o0{ zdzZ%5c4es=ulPy&?@StoDS;d22jXE)gTmy8Nl;LtK#@5bD44>Pt_u{ZI{}Tk6&?W; z0-3ErtBt@s0Y$pGve3~4!^+j#$EAUCY>jb(nz(bC0fKJr^Z=Tyt}@$P-@jbd_rkG) z$CPIr6rY^y%E8Ts?Fq`WzNqzm&oDkgdmc6GG~!?-IGf{yS(XC>?8dgbi2tpuGr|;@ zEB>v?3_dSIzvTGskA8xhRPTuY^7%*wAhdT-n1R#^{;XW|->I0DU zX|YPZ7veAAbZpnUD{|5gNfMI)D0B6b^;~XKicGR*b5G9Q`pmhjL}X)z`%=!dqb>vf5@eGV zX7hz{I#rH^f3XCnlI*<%dQDSrR`DCIAz`{9+DN29*Ga9DIX|zQv@D$yIY=6YN~6qf zk54A^v6A9FWL3*WPx*I($PQ$T{QfaIqJ4dtJH%>!ktt zQLRoL^P_xc7!imb>+mq?O|y0s_doxzU^>HRu}So$wfH$Irj6XU$1pXQBOM4knb|fq zsI>w#2kNTr`WB@KgFN2QCh)5OBjIJh6*|Vmk#^ZkZL^9$QXRfVbDpb1EKiO_g=hVE zy5TnAu)tHPBAph%ppBlZl)rfwrO@j%G&L!N0P>vT(BPCx1>^sMUXY})pPR-n<4WvG z&{9tEarbNvY=uc!FNt-r*MPt=hnD*CJE6JgZA{P@7XZ<$;v+G*(v4uu1;1IvFS&kJ zwNUk&50&=Oco!6&oTLzwJ%*Q<2Na!iyFuubnE>KVA(FstQ-~1c5cjbodV&dcP5aa| ziT8_x`RKbZ=)@*}m{eVd`p0Q?CzavFIk){eyQ)2h=m6NNT-D|XDgEN(_nJfup?VW& zGZ$v;6;?XWV<$B;<|*lvIcMs8BAq^M52W{lq|XqsuMqrGSowfNhB+YnjwlKUuOJ(& z_Gq#Wqnug3FUxGfYlPe($M>Kh zgDT^>Gx=|y#A!&*(){5wc7-6KC>m+Mv3zUrjnVS<$Fr>mLS+~@ItROcT$QQT` ztpLD%Izco$+1nGJ;?E`#IxGcIGfPGl6BH>|lm<~jWG17nN-2p}hYb*~NVJeWlWp1D zB@$v7BB34O-k1dfu(XrnbBt+m8JQU01G@kEK?eEnkx+grdIzC%QnzRi%&8g5W3I}? zY!i!fM@M~dU$`(n#*BqFIHF)25V9+^L_X(edypR@lMcbL`|@LN5?djOL@|N|>Nvfm z#X<@`P(gAc8q(FIfl#AssbVCm3)N6s`lNt8Q_78 zne%63RFRN=C5s&R??#D0a-8@<@?{3SB4K}{17$_XwQ&_c(8raf3jcNj`rlCekpkF} zM&p~he=2!^j`a)f`Oqe*#*d~k!D;E7aHV~m;$abMhKpHkg0xtmfJv)R1%)&kmd9=u zkR@o`vR8NcNlvCpWsR#hE3GN2#WRX`L0aOSXyP%_xRRs;l_cx55H$t??A5r`h}4~Dr2I+ z-y7Y&GMh=tMFVMB@$tju8mi#CBu&Ow;~5Han$pRRi%^*BkUeyUjIj9JO$JMHIYWlF zYC8)Ccfw>nka!Rgq80^4MMo<2RWu(-g;w7Yc6gZKBq%=kMpvJk6Q|gearFs1RM7%G zv#miwrDw?hM^<^;Eq_>*_mJPJ{MVZ5Z`7EmlE)Q8DA9)PH^|&(S?Y=4 z0iP)jTLk}I&UOU47;k6^iHma-4#8na|RdRbsmCgd}zQlu-+^8Z)dNU)XT|hRP zGtN%y;?3}Y9kighk}u1k7|=vmi%j=Wuf$uI8`AQK`oPtU?oGL1Wdx0J?iE4f_i%)^ zsx;*>h+qD&CRm>tXE5t0x*58;asXH@;j3DA%Ys5?Bl4NLYzTfe|Z~ z12e8L#&_v>gI8UeWkal|_X2*_#Cm|Ah!Y%3f|q7;P)(7kj~3FbFBgSt=9)*49TOhN z1dHdkYd$EsixLSiJnYi`U$vPhG2&4vxF~Q6C7a;tG>R-}s0Ky=to11r7ut*Z1eXO2 z)aeihKnu&2M|vUZnv-tmDywfr*>jWns!Osu_)^yNf)PaW9ezp_E_8$iPGE#g#=V$` zIik;@(auC?G;W@wakGp_&4PA__WiLG>ajnNTs)bOi&F(0MhQhvR-OkDU&W7s{r&N6 z<7WdqzIHiJ{l=dM1$NRI=aY>9anvz#a9F}DB`8MnH}{gaSn6iWCIIxBJO&_O z2pN*Zu927&LJlXumEIjh%m|B-=~QRLL~I*Qa;%s&M5>aroUN>sF@x%)rW~QVPL--7 z30}I|OnjV(Z7d$*n6tLvH{~PvMP6Yd>(|BNox_q-oMj3u!#n}Yvfb4a8Pny(l*GFT zz!L?H4$Mwxs}oI=5m^w&5)OK+rxr9eK?g-8oaoO?YUX2bfICqUvb+mZk8HfacN_F z2(3^|Hi8*2*$BoBVi48I&^~Vmm}$^qI%i}(UKPE`Sv%cl3~@AJ>YK(=3tGj2Pc#~Rf%k z=~1nev!ciAIq*n?VC`J61PkwuBA)2Xd#Rw9;`A5Ve_jIbgbttuA8(j7j+?R_LW|@H zOVm-aH*$uDPevFLyLg0c@@xdP1Z+cQgo(X3?YmBtNg5b+b6%2}ruc$lgNTw|5f8^A z4^3W}=XqK;swqx~xFTl-7twid zy@a8a8%7!$B-+_g{+8NG-NxODcii~$25fX#?6uw$=*t;#C6Xf=@-(52R5WZ$t+bfb zbq!eunkVU10vnwS5EeIITik!+1$^h#YTbY1`UWj<(z=sf>*^)-)L@e@4qia^$#z<{ z(5lX<`>H!>89J@&z9pNmlzxDZekihI2?D*DD78UCp9_me@!@n+gUO+#b*c>#hD&

=LML-IrerM^`6ky%eb?-eDY@48QM0H-Gpka@ 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 + background_color: string | null; + animation_url: string | null; + youtube_url: string | null; + }; +}> { const msg = { nft_info: { token_id, diff --git a/ts-relayer-tests/src/ics721.spec.ts b/ts-relayer-tests/src/ics721.spec.ts index 50ebc502..240aaf69 100644 --- a/ts-relayer-tests/src/ics721.spec.ts +++ b/ts-relayer-tests/src/ics721.spec.ts @@ -13,6 +13,7 @@ import { getCw721Minter_v16, getCw721MinterOwnership, mint, + nftInfo, ownerOf, sendNft, } from "./cw721-utils"; @@ -67,7 +68,7 @@ interface TestContext { const test = anyTest as TestFn; const WASM_FILE_CW721 = "./internal/cw721_metadata_onchain_v0.19.0.wasm"; -const WASM_FILE_CW721_v16 = "./internal/cw721_base_v0.16.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"; @@ -375,7 +376,7 @@ const standardSetup = async (t: ExecutionContext) => { t.pass(); }; -test.skip("transfer NFT: wasmd -> osmo", async (t) => { +test.serial("transfer NFT: wasmd -> osmo", async (t) => { await standardSetup(t); const { @@ -470,6 +471,14 @@ test.skip("transfer NFT: wasmd -> osmo", async (t) => { 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}`); transferResponse = await sendNft( @@ -925,9 +934,16 @@ test.serial("transfer NFT v16: wasm -> osmo", async (t) => { ); // assert minter is set to ics721 const minterOwnerShip = await getCw721Minter_v16(osmoClient, osmoCw721); - console.log(">>>>>", minterOwnerShip); 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( @@ -1296,7 +1312,7 @@ test.serial("transfer NFT v16: wasm -> osmo", async (t) => { t.is(wasmAddr, tokenOwner.owner); }); -test.skip("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { +test.serial("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { await standardSetup(t); const { @@ -1481,7 +1497,7 @@ test.skip("admin unescrow and burn NFT: wasmd -> osmo", async (t) => { ); }); -test.skip("malicious NFT", async (t) => { +test.serial("malicious NFT", async (t) => { await standardSetup(t); const { wasmClient, From c80be3c1aaac12173ca7595196cc1411250ced23 Mon Sep 17 00:00:00 2001 From: mr-t Date: Wed, 14 Aug 2024 13:46:00 +0200 Subject: [PATCH 20/21] fix ts-relayer test --- ts-relayer-tests/src/ics721.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts-relayer-tests/src/ics721.spec.ts b/ts-relayer-tests/src/ics721.spec.ts index 240aaf69..49448c3d 100644 --- a/ts-relayer-tests/src/ics721.spec.ts +++ b/ts-relayer-tests/src/ics721.spec.ts @@ -450,7 +450,7 @@ test.serial("transfer NFT: wasmd -> osmo", async (t) => { osmoClient, osmoCw721 ); - // osmoCollectionData.updated_at = wasmCollectionData.updated_at; // ignore updated_at + 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 } From 2d08afed86225b60400813f667ab8b5044ff3e0a Mon Sep 17 00:00:00 2001 From: mr-t Date: Wed, 14 Aug 2024 14:19:14 +0200 Subject: [PATCH 21/21] use admin for payment address --- contracts/sg-ics721/src/execute.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/sg-ics721/src/execute.rs b/contracts/sg-ics721/src/execute.rs index 7eaebe27..ff4f90c0 100644 --- a/contracts/sg-ics721/src/execute.rs +++ b/contracts/sg-ics721/src/execute.rs @@ -93,7 +93,6 @@ impl Ics721Execute for SgIcs721Contract { if let Some(collection_data) = collection_data { instantiate_msg.name = collection_data.name; instantiate_msg.symbol = collection_data.symbol; - let admin_or_creator = admin.unwrap_or(creator.clone()); if let Some(collection_info_extension_msg) = collection_data.extension.map(|ext| sg721::CollectionInfo { creator: cw721_admin_or_ics721_admin_or_ics721_creator.clone(), @@ -103,7 +102,7 @@ impl Ics721Execute for SgIcs721Contract { explicit_content: ext.explicit_content, start_trading_time: ext.start_trading_time, royalty_info: ext.royalty_info.map(|r| RoyaltyInfoResponse { - payment_address: admin_or_creator, // r.payment_address cant be used, since it is from another chain + 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, }), })