From e96b6edb1635dd007f74cf358d286ac0d510b43e Mon Sep 17 00:00:00 2001 From: Manuel Date: Thu, 20 Jul 2023 09:44:06 -0500 Subject: [PATCH] feat(ADR-006): POAP v2 (#195) ## Description This PR updates the POAP contract to make it compliant with the ADR-006. Closes: #182 Closes: #183 Closes: #184 Closes: #185 Closes: #186 Closes: #187 Closes: #188 Closes: #189 --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/desmos-labs/desmos-contracts/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [x] included the necessary unit and integration [tests](https://github.com/desmos-labs/desmos-contracts/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed contract logic - [ ] reviewed contract security - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable) --------- Co-authored-by: Riccardo Montagnin --- .github/workflows/lint.yml | 1 + Cargo.lock | 289 ++- Cargo.toml | 16 +- README.md | 14 +- contracts/cw721-poap/.cargo/config | 5 - contracts/cw721-poap/.gitignore | 15 - contracts/cw721-poap/Cargo.toml | 48 - contracts/cw721-poap/README.md | 529 ----- contracts/cw721-poap/examples/schema.rs | 24 - .../schema/all_nft_info_response.json | 172 -- contracts/cw721-poap/schema/execute_msg.json | 355 --- .../cw721-poap/schema/nft_info_response.json | 44 - contracts/cw721-poap/src/lib.rs | 100 - contracts/poap-manager/.cargo/config | 5 - contracts/poap-manager/Cargo.toml | 50 - contracts/poap-manager/README.md | 96 - contracts/poap-manager/examples/schema.rs | 10 - .../poap-manager/schema/poap-manager.json | 293 --- contracts/poap-manager/src/contract.rs | 514 ----- contracts/poap-manager/src/error.rs | 27 - .../poap-manager/src/integration_tests.rs | 255 --- contracts/poap-manager/src/lib.rs | 9 - contracts/poap-manager/src/msg.rs | 114 - contracts/poap-manager/src/state.rs | 13 - contracts/poap-manager/src/test_utils.rs | 132 -- contracts/poap/.cargo/config | 2 +- contracts/poap/.gitignore | 6 + contracts/poap/Cargo.toml | 19 +- contracts/poap/README.md | 288 ++- contracts/poap/examples/schema.rs | 8 +- contracts/poap/schema/poap-v2.json | 1968 +++++++++++++++++ contracts/poap/schema/poap.json | 763 ------- contracts/poap/src/contract.rs | 1421 ------------ contracts/poap/src/contract_tests.rs | 1358 ++++++++++++ contracts/poap/src/cw721_test_utils.rs | 56 - contracts/poap/src/error.rs | 92 +- contracts/poap/src/execute.rs | 450 ++++ contracts/poap/src/integration_tests.rs | 473 ---- contracts/poap/src/lib.rs | 70 +- contracts/poap/src/msg.rs | 590 +++-- contracts/poap/src/query.rs | 69 + contracts/poap/src/state.rs | 185 +- contracts/poap/src/test_utils.rs | 35 - contracts/remarkables/src/contract.rs | 4 +- contracts/tips/src/contract.rs | 4 +- docs/architecture/adr-006-poap-v2.md | 2 +- 46 files changed, 4658 insertions(+), 6335 deletions(-) delete mode 100644 contracts/cw721-poap/.cargo/config delete mode 100644 contracts/cw721-poap/.gitignore delete mode 100644 contracts/cw721-poap/Cargo.toml delete mode 100644 contracts/cw721-poap/README.md delete mode 100644 contracts/cw721-poap/examples/schema.rs delete mode 100644 contracts/cw721-poap/schema/all_nft_info_response.json delete mode 100644 contracts/cw721-poap/schema/execute_msg.json delete mode 100644 contracts/cw721-poap/schema/nft_info_response.json delete mode 100644 contracts/cw721-poap/src/lib.rs delete mode 100644 contracts/poap-manager/.cargo/config delete mode 100644 contracts/poap-manager/Cargo.toml delete mode 100644 contracts/poap-manager/README.md delete mode 100644 contracts/poap-manager/examples/schema.rs delete mode 100644 contracts/poap-manager/schema/poap-manager.json delete mode 100644 contracts/poap-manager/src/contract.rs delete mode 100644 contracts/poap-manager/src/error.rs delete mode 100644 contracts/poap-manager/src/integration_tests.rs delete mode 100644 contracts/poap-manager/src/lib.rs delete mode 100644 contracts/poap-manager/src/msg.rs delete mode 100644 contracts/poap-manager/src/state.rs delete mode 100644 contracts/poap-manager/src/test_utils.rs create mode 100644 contracts/poap/schema/poap-v2.json delete mode 100644 contracts/poap/schema/poap.json delete mode 100644 contracts/poap/src/contract.rs create mode 100644 contracts/poap/src/contract_tests.rs delete mode 100644 contracts/poap/src/cw721_test_utils.rs create mode 100644 contracts/poap/src/execute.rs delete mode 100644 contracts/poap/src/integration_tests.rs create mode 100644 contracts/poap/src/query.rs delete mode 100644 contracts/poap/src/test_utils.rs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1a345dba..b3e80608 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -46,6 +46,7 @@ jobs: if: env.GIT_DIFF uses: actions-rs/tarpaulin@v0.1.3 with: + version: '0.22.0' args: '--avoid-cfg-tarpaulin' - name: Upload coverage 📤 diff --git a/Cargo.lock b/Cargo.lock index 34aadf40..ce810dfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "byteorder" version = "1.4.3" @@ -70,9 +76,9 @@ checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" [[package]] name = "cosmwasm-crypto" -version = "1.1.9" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "227315dc11f0bb22a273d0c43d3ba8ef52041c42cf959f09045388a89c57e661" +checksum = "0d076a08ec01ed23c4396aca98ec73a38fa1fee5f310465add52b4108181c7a8" dependencies = [ "digest 0.10.5", "ed25519-zebra", @@ -83,18 +89,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.1.9" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fca30d51f7e5fbfa6440d8b10d7df0231bdf77e97fd3fe5d0cb79cc4822e50c" +checksum = "dec361f3c09d7b41221948fc17be9b3c96cb58e55a02f82da36f888a651f2584" dependencies = [ "syn 1.0.104", ] [[package]] name = "cosmwasm-schema" -version = "1.2.7" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "230e5d1cefae5331db8934763c81b9c871db6a2cd899056a5694fa71d292c815" +checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -105,9 +111,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.2.7" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43dadf7c23406cb28079d69e6cb922c9c29b9157b0fe887e3b79c783b7d4bcb8" +checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" dependencies = [ "proc-macro2", "quote", @@ -116,11 +122,12 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.1.9" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13d5a84d15cf7be17dc249a21588cdb0f7ef308907c50ce2723316a7d79c3dc" +checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" dependencies = [ "base64", + "bnum", "cosmwasm-crypto", "cosmwasm-derive", "derivative", @@ -129,15 +136,15 @@ dependencies = [ "schemars", "serde", "serde-json-wasm", + "sha2 0.10.6", "thiserror", - "uint", ] [[package]] name = "cosmwasm-storage" -version = "1.1.9" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9162c3f85412914d5be2ee650ebdbf11b08e5e9acdebcf4dc03608fb01cf9676" +checksum = "7ade8cae79dc08a06bcf119c0854ffaed11bd8cb1013c6b04abfe1f51f36211e" dependencies = [ "cosmwasm-std", "serde", @@ -152,12 +159,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.8" @@ -193,6 +194,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-address-like" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451a4691083a88a3c0630a8a88799e9d4cd6679b7ce8ff22b8da2873ff31d380" +dependencies = [ + "cosmwasm-std", +] + [[package]] name = "cw-multi-test" version = "0.15.1" @@ -212,11 +222,37 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-ownable" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093dfb4520c48b5848274dd88ea99e280a04bc08729603341c7fb0d758c74321" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-address-like", + "cw-ownable-derive", + "cw-storage-plus 1.1.0", + "cw-utils 1.0.1", + "thiserror", +] + +[[package]] +name = "cw-ownable-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d3bf2e0f341bb6cc100d7d441d31cf713fbd3ce0c511f91e79f14b40a889af" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.104", +] + [[package]] name = "cw-storage-macro" -version = "0.16.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a3de01064d249a0e7028b63f8168470dac4bcd0c5dc3710d17486f843d1a8" +checksum = "95547c3d8c4a099d66a07f8a2a77347cefd2582ae6482c8ebc9a871f3fd7aef7" dependencies = [ "syn 1.0.104", ] @@ -237,6 +273,17 @@ name = "cw-storage-plus" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b6f91c0b94481a3e9ef1ceb183c37d00764f8751e39b45fc09f4d9b970d469" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-storage-plus" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" dependencies = [ "cosmwasm-std", "cw-storage-macro", @@ -274,6 +321,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 1.1.0", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.15.1" @@ -300,10 +362,37 @@ dependencies = [ "serde", ] +[[package]] +name = "cw2" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw721" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a1ea6e6277bdd6dfc043a9b1380697fe29d6e24b072597439523658d21d791" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "schemars", + "serde", +] + [[package]] name = "cw721" version = "0.16.0" -source = "git+https://github.com/desmos-labs/cw-nfts?branch=paul/update-custom-msg-query#91ac132256bdfe0414e275f093e87e5853625745" +source = "git+https://github.com/desmos-labs/cw-nfts?branch=paul/update-custom-msg-query#062d3a763ec93e6e5a477fd12a61e5faf791823b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -312,35 +401,66 @@ dependencies = [ "serde", ] +[[package]] +name = "cw721" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c4d286625ccadc957fe480dd3bdc54ada19e0e6b5b9325379db3130569e914" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.1", + "schemars", + "serde", +] + [[package]] name = "cw721-base" version = "0.16.0" -source = "git+https://github.com/desmos-labs/cw-nfts?branch=paul/update-custom-msg-query#91ac132256bdfe0414e275f093e87e5853625745" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77518e27431d43214cff4cdfbd788a7508f68d9b1f32389e6fce513e7eaccbef" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.16.0", "cw-utils 0.16.0", "cw2 0.16.0", - "cw721", + "cw721 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "serde", "thiserror", ] [[package]] -name = "cw721-poap" -version = "0.1.0" +name = "cw721-base" +version = "0.16.0" +source = "git+https://github.com/desmos-labs/cw-nfts?branch=paul/update-custom-msg-query#062d3a763ec93e6e5a477fd12a61e5faf791823b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cosmwasm-storage", - "cw-multi-test", "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", "cw2 0.16.0", - "cw721", - "cw721-base", - "desmos-bindings", + "cw721 0.16.0 (git+https://github.com/desmos-labs/cw-nfts?branch=paul/update-custom-msg-query)", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw721-base" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da518d9f68bfda7d972cbaca2e8fcf04651d0edc3de72b04ae2bcd9289c81614" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable", + "cw-storage-plus 1.1.0", + "cw-utils 1.0.1", + "cw2 1.1.0", + "cw721 0.18.0", + "cw721-base 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "serde", "thiserror", @@ -354,10 +474,10 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 0.16.0", - "cw2 0.16.0", - "cw721", - "cw721-base", + "cw-storage-plus 1.1.0", + "cw2 1.1.0", + "cw721 0.16.0 (git+https://github.com/desmos-labs/cw-nfts?branch=paul/update-custom-msg-query)", + "cw721-base 0.16.0 (git+https://github.com/desmos-labs/cw-nfts?branch=paul/update-custom-msg-query)", "desmos-bindings", "schemars", "serde", @@ -628,41 +748,18 @@ dependencies = [ [[package]] name = "poap" -version = "0.1.0" +version = "2.0.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", - "cw2 0.16.0", - "cw721", - "cw721-base", - "cw721-poap", - "desmos-bindings", - "schemars", - "serde", - "thiserror", - "url", -] - -[[package]] -name = "poap-manager" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-multi-test", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", - "cw2 0.16.0", - "cw721", - "cw721-base", - "cw721-poap", - "desmos-bindings", - "poap", + "cw-ownable", + "cw-storage-plus 1.1.0", + "cw-utils 1.0.1", + "cw2 1.1.0", + "cw721 0.18.0", + "cw721-base 0.18.0", "schemars", "serde", "thiserror", @@ -670,9 +767,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] @@ -736,11 +833,11 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", - "cw2 0.16.0", - "cw721", - "cw721-base", + "cw-storage-plus 1.1.0", + "cw-utils 1.0.1", + "cw2 1.1.0", + "cw721 0.16.0 (git+https://github.com/desmos-labs/cw-nfts?branch=paul/update-custom-msg-query)", + "cw721-base 0.16.0 (git+https://github.com/desmos-labs/cw-nfts?branch=paul/update-custom-msg-query)", "cw721-remarkables", "desmos-bindings", "schemars", @@ -812,31 +909,31 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.160" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] [[package]] name = "serde-json-wasm" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -903,8 +1000,8 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 0.16.0", - "cw2 0.16.0", + "cw-storage-plus 1.1.0", + "cw2 1.1.0", "desmos-bindings", "schemars", "serde", @@ -921,12 +1018,6 @@ dependencies = [ "der", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "subtle" version = "2.4.1" @@ -946,9 +1037,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.23" +version = "2.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" dependencies = [ "proc-macro2", "quote", @@ -972,7 +1063,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.23", + "syn 2.0.25", ] [[package]] @@ -998,9 +1089,9 @@ dependencies = [ "cosmwasm-std", "cosmwasm-storage", "cw-multi-test", - "cw-storage-plus 0.16.0", - "cw-utils 0.16.0", - "cw2 0.16.0", + "cw-storage-plus 1.1.0", + "cw-utils 1.0.1", + "cw2 1.1.0", "desmos-bindings", "schemars", "serde", @@ -1013,18 +1104,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" -[[package]] -name = "uint" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-bidi" version = "0.3.13" diff --git a/Cargo.toml b/Cargo.toml index b2d0c6f4..27de5fe4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,18 +5,18 @@ members = [ [workspace.dependencies] # Cosmwasm related dependencies -cosmwasm-std = "1.1.9" -cosmwasm-storage = "1.1.9" -cosmwasm-schema = "1.2.7" -cw-storage-plus = "0.16.0" -cw-utils = "0.16.0" -cw2 = "0.16.0" +cosmwasm-std = "1.3.0" +cosmwasm-storage = "1.3.0" +cosmwasm-schema = "1.3.0" +cw-storage-plus = "1.1.0" +cw-utils = "1.0.1" +cw2 = "1.1.0" cw-multi-test = "0.15.1" # Desmos dependencies -desmos-bindings = "1.1.1" +desmos-bindings = { version = "1.1.1", default-features = false } # Misc dependencies schemars = "0.8.12" -serde = "1.0.160" +serde = { version = "1.0.171", default-features = false } thiserror = "1.0.43" [profile.release] diff --git a/README.md b/README.md index ddfdd4dc..fbdd4bf1 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,9 @@ A collection of smart contracts built in [CosmWasm](https://www.cosmwasm.com/) f ![GitHub](https://img.shields.io/github/license/desmos-labs/desmos-contracts.svg) [![codecov](https://codecov.io/gh/desmos-labs/desmos-contracts/branch/master/graph/badge.svg?token=4M3W11FP2F)](https://codecov.io/gh/desmos-labs/desmos-contracts) -| Name | Description | -|-----------|-------------| -| [CW721-POAP](https://github.com/desmos-labs/desmos-contracts/tree/master/contracts/cw721-poap) | Customized CW721 contract to handle POAP | -| [POAP](https://github.com/desmos-labs/desmos-contracts/tree/master/contracts/poap) | Contract that implements the POAP standard as described in [ADR-001](https://github.com/desmos-labs/desmos-contracts/blob/master/docs/architecture/adr-001-poap-contract.md) | -| [POAP Manager](https://github.com/desmos-labs/desmos-contracts/tree/master/contracts/poap-manager) | Utility contract to manage the POAP minting and distribution | -| [Remarkables](https://github.com/desmos-labs/desmos-contracts/tree/master/contracts/remarkables) | Contract that implements the Remarkables standard as described in [ADR-003](https://github.com/desmos-labs/desmos-contracts/blob/master/docs/architecture/adr-003-remarkables-contract.md) | -| [CW721-Remarkables](https://github.com/desmos-labs/desmos-contracts/tree/master/contracts/cw721-remarkables) | Customized CW721 contract to handle Remarkables | -| [Tips](https://github.com/desmos-labs/desmos-contracts/tree/master/contracts/tips) | Contract to send tips to and user or to a post author | +| Name | Description | +|--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [POAP](https://github.com/desmos-labs/desmos-contracts/tree/master/contracts/poap) | Contract that implements the POAP standard as described in [ADR-006](https://github.com/desmos-labs/desmos-contracts/blob/master/docs/architecture/adr-006-poap-v2.md) | +| [Remarkables](https://github.com/desmos-labs/desmos-contracts/tree/master/contracts/remarkables) | Contract that implements the Remarkables standard as described in [ADR-003](https://github.com/desmos-labs/desmos-contracts/blob/master/docs/architecture/adr-003-remarkables-contract.md) | +| [CW721-Remarkables](https://github.com/desmos-labs/desmos-contracts/tree/master/contracts/cw721-remarkables) | Customized CW721 contract to handle Remarkables | +| [Tips](https://github.com/desmos-labs/desmos-contracts/tree/master/contracts/tips) | Contract to send tips to and user or to a post author | diff --git a/contracts/cw721-poap/.cargo/config b/contracts/cw721-poap/.cargo/config deleted file mode 100644 index 11e7d64c..00000000 --- a/contracts/cw721-poap/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" -optimize = "run-script optimize" \ No newline at end of file diff --git a/contracts/cw721-poap/.gitignore b/contracts/cw721-poap/.gitignore deleted file mode 100644 index dfdaaa6b..00000000 --- a/contracts/cw721-poap/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# Build results -/target - -# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) -.cargo-ok - -# Text file backups -**/*.rs.bk - -# macOS -.DS_Store - -# IDEs -*.iml -.idea diff --git a/contracts/cw721-poap/Cargo.toml b/contracts/cw721-poap/Cargo.toml deleted file mode 100644 index 436778ac..00000000 --- a/contracts/cw721-poap/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "cw721-poap" -version = "0.1.0" -authors = ["Paul "] -edition = "2021" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[package.metadata.scripts] -optimize = """docker run --rm -v "$(pwd)"/../..:/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.12.6 ./contracts/cw721-poap -""" - -[dependencies] -cosmwasm-std.workspace = true -cosmwasm-storage.workspace = true -cw-storage-plus.workspace = true -cw2.workspace = true -schemars.workspace = true -serde = { workspace = true, default-features = false, features = ["derive"] } -thiserror.workspace = true -desmos-bindings = { workspace = true, default-features = false, features = ["msg", "query"] } -cw721-base = { git = "https://github.com/desmos-labs/cw-nfts", features = ["library"], branch = "paul/update-custom-msg-query" } -cosmwasm-schema.workspace = true - -[dev-dependencies] -cw-multi-test.workspace = true -desmos-bindings = { workspace = true, default-features = false, features = ["mocks"]} -cw721 = { git = "https://github.com/desmos-labs/cw-nfts", branch = "paul/update-custom-msg-query" } - - diff --git a/contracts/cw721-poap/README.md b/contracts/cw721-poap/README.md deleted file mode 100644 index 2e868460..00000000 --- a/contracts/cw721-poap/README.md +++ /dev/null @@ -1,529 +0,0 @@ -# CW721 POAP contract - -Contract that defines the cw721-base contract having custom POAP `Metadata`, which is used by POAP contract. -To easily interact with the contract you can use the `cw721-poap` script available [here](https://github.com/desmos-labs/contract-utils/tree/main/utils), -otherwise you can take a look at the supported messages in the following sections. - -## Instantiate Message -Allows to initialize the contract. This message has the following parameters: -* `name`: Name of the NFT contract; -* `symbol`: Symbol of the NFT contract; -* `minter`: Address who is the only one to be able to create new NFTs. - -Here an example message to instantiate a contract: -```json -{ - "name": "test_name", - "symbol": "test", - "minter": "desmos1......" -} -``` - -## Execute Messages - -### TransferNft -Allows to move a token to another account without triggering actions. This message has the following parameters: -* `recipient`: Address where the token transfer to; -* `token_id`: Id of the token which would be transferred. - -Here an example message to transfer nft: -```json -{ - "transfer_nft": { - "recipient": "desmos1......", - "token_id": "1" - } -} -``` - -### SendNft -Allows to move a token to another contract then trigger an action. This message has the following parameters: -* `contract`: Contract address where the token transfer to; -* `token_id`: Id of the token which would be transferred; -* `msg`: Base64 encoded message to trigger on the receiver contract. - -Here an example message to send nft having a trigger message: -```json -{ - "send_nft": { - "contract": "desmos1......", - "token_id": "1", - "msg": "eyJleGVjdXRlX2V4YW1wbGUiOnt9fQ==" - } -} -``` -**Note** the msg is base64-encoded of `{"execute_example":{}}`, which is the sample execution message on the target contract. - -### Approve -Allows a user to transfer/send the token from the owner's account. This message has the following parameters: -* `spender`: Address who would be assigned as an access of the token; -* `token_id`: Id of the target token; -* `expires`: Expiration time/height of this allowance, if it is set as `null` then it has no time/height limit. - -An example message to approve a user to have the sending/transferring access to a token with an expiration height: -```json -{ - "approve": { - "spender": "desmos1......", - "token_id": "1", - "expires": { - "at_height": 1000 - } - } -} -``` - -Here an example message to approve a token to an operator with an expiration time: -```json -{ - "approve": { - "spender": "desmos1......", - "token_id": "1", - "expires": { - "at_time": "2022-01-01T00:00:00Z" - } - } -} -``` - -Here an example message to approve a token to an operator without any expiration: -```json -{ - "approve": { - "spender": "desmos1......", - "token_id": "1", - "expires": null - } -} -``` - -### Revoke -Allows to remove a previously granted approval. This message has the following parameters: -* `spender`: Address who would be revoked the permission of the given token; -* `token_id`: Id of the target token. - -Here an example meesage to revoke an operator to a token: -```json -{ - "revoke": { - "spender": "desmos1......", - "token_id": "1" - } -} -``` - -### ApproveAll -Allows to give all the tokens transferring/sendind tokens approval to an operator from the owner's account. This message has the following parameters: -* `operator`: Address who is assigned to have all the tokens approvals in the owner's account; -* `expires`: Expiration time/height of this allowance, if it is set as `null` then it has no time/height limit. - -Here an example meesage to approve an operator all the tokens with an expiration height: -```json -{ - "approve_all": { - "spender": "desmos1......", - "expires": { - "at_height": 1000 - } - } -} -``` - -Here an example meesage to approve an operator all the tokens with an expiration time: -```json -{ - "approve_all": { - "spender": "desmos1......", - "expires": { - "at_time": "2022-01-01T00:00:00Z" - } - } -} -``` - -Here an example meesage to approve an operator all the tokens without any expiration: -```json -{ - "approve_all": { - "spender": "desmos1......", - "expires": null - } -} -``` - -### RevokeAll -Allows to remove a previously granted approval all permission. This message has the following parameters: -* `operator`: Address who would be revoked operator permissions of all the tokens from the owner's account. - -Here an example meesage to revoke operator permissions to all the tokens from the owner's account: -```json -{ - "revoke": { - "spender": "desmos1......", - } -} -``` - -### Mint -Allows the minter to mint a new NFT to a user. This message has the following parameters: -* `token_id`: unique id of the NFT; -* `owner`: the owner of the newly minted NFT; -* `token_uri`: universal resource identifier for this NFT; -* `extension`: the `POAP metadata` which includes claimer of this NFT. - -Here an example meesage to mint new NFT: -```json -{ - "mint": { - "token_id": "1", - "owner": "desmos1......", - "token_uri": "ipfs://token.erc721.metadata", - "extension": { - "claimer": "desmos1......" - } - } -} -``` - -### Burn -Allows to burn an NFT the sender has access to. This message has the following parameters: -* `token_id`: Id of the token that would be burned. - -Here an example meesage to burn an NFT: -```json -{ - "burn": { - "token_id": "1" - } -} -``` - -## Query Messages - -### OwnerOf -Returns the owner of the given token, error if token does not exist. This message has the following parameters: -* `token_id`: Id of the target token; -* `include_expired`: Trigger to filter out expired approvals, unset or false will exclude expired approvals. - -Here an example meesage to query the owner of the given token: -```json -{ - "owner_of": { - "token_id": "1", - "includ_expired": true, - } -} -``` - -Response: -```json -{ - "owner": "desmos1......", - "approvals": [ - { - "spender": "desmos1......", - "expiration": { - "at_height": 1000 - } - }, - { - "spender": "desmos1......", - "expiration": { - "at_time": "2022-01-01T00:00:00Z" - } - }, - { - "spender": "desmos1......", - "expiration": { - "never": {} - } - }, - ] -} -``` - -### Approval -Returns an access owned by the given spender to the given token. This message has the following parameters: -* `token_id`: Id of the target token; -* `spender`: Address who has the sending/transferring access to the given token; -* `include_expired`: Trigger to filter out expired approvals, unset or false will exclude expired approvals. - -Here an example meesage to query the approval of the given token by a spender: -```json -{ - "approval": { - "token_id": "1", - "spender": "desmos1......", - "includ_expired": true, - } -} -``` - -Response: -```json -{ - "approval": { - "spender": "desmos1......", - "expiration": { - "at_height": 1000 - } - } -} -``` - -### Approvals -Returns approvals that a token has. This message has the following parameters: -* `token_id`: Id of the target token; -* `include_expired`: Trigger to filter out expired approvals, unset or false will exclude expired approvals. - -Here an example meesage to query the approvals of the given token: -```json -{ - "approvals": { - "token_id": "1", - "includ_expired": true, - } -} -``` - -Response: -```json -{ - "approvals": [ - { - "spender": "desmos1......", - "expiration": { - "at_height": 1000 - } - }, - { - "spender": "desmos1......", - "expiration": { - "at_time": "2022-01-01T00:00:00Z" - } - }, - { - "spender": "desmos1......", - "expiration": { - "never": {} - } - }, - ] -} -``` - -### AllOperators -Lists all operators that can access all of the owner's tokens. This message has the following parameters: -* `owner`: Address of the owner to be queried. -* `include_expired`: Trigger to filter out expired approvals, unset or false will exclude expired approvals; -* `start_after`: Position in address where tokens start after; -* `limit`: Limitation to list the number of operators, if unset would be 10 and the maximum is 100. - -Here an example meesage to query the operators of the given owner: -```json -{ - "all_operators": { - "owner": "desmos1......", - "includ_expired": true, - "start_after": "desmos1......", - "limit": 10 - } -} -``` - -Response: -```json -{ - "operators": [ - { - "spender": "desmos1......", - "expiration": { - "at_height": 1000 - } - }, - { - "spender": "desmos1......", - "expiration": { - "at_time": "2022-01-01T00:00:00Z" - } - }, - { - "spender": "desmos1......", - "expiration": { - "never": {} - } - }, - ] -} -``` - -### NumTokens -Returns total number of tokens issued. - -Here an example meesage to query total number of tokens: -```json -{ - "num_tokens": {} -} -``` - -Response: -```json -{ - "count": 1000 -} -``` - -### ContractInfo -Returns top-level metadata about the contract. - -Here an example meesage to query the contract info of the contract: -```json -{ - "contract_info": {} -} -``` - -Response: -```json -{ - "name": "test_name", - "symbol": "test_symbol" -} -``` - -### NftInfo -Returns metadata about one particular token. This message has the following parameters: -* `token_id`: Id of the target token. - -Here an example meesage to query the info of the given token: -```json -{ - "nft_info": { - "token_id": "1" - } -} -``` - -Response: -```json -{ - "token_uri": "ipfs://token.erc721.metadata", - "extension": { - "claimer": "desmos1......" - } -} -``` - -### AllNftInfo -Returns the result of both `NftInfo` and `OwnerOf`. This message has the following parameters: -* `token_id`: Id of the target token. -* `include_expired`: Trigger to filter out expired approvals, unset or false will exclude expired approvals. - -Here an example meesage to query all the info of the given token: -```json -{ - "all_nft_info": { - "token_id": "1", - "include_expired": true - } -} -``` - -Response: -```json -{ - "access": { - "owner": "desmos1......", - "approvals": [ - { - "spender": "desmos1......", - "expiration": { - "at_height": 1000 - } - }, - { - "spender": "desmos1......", - "expiration": { - "at_time": "2022-01-01T00:00:00Z" - } - }, - { - "spender": "desmos1......", - "expiration": { - "never": {} - } - }, - ], - }, - "info": { - "token_uri": "ipfs://token.erc721.metadata", - "extension": { - "claimer": "desmos1......" - } - } -} -``` - -### Tokens -Returns all tokens owned by the given address. This message has the following parameters: -* `owner`: Target address owned tokens to be queried; -* `start_after`: Position in token id where tokens start after; -* `limit`: Limitation to list the number of tokens, if unset would be 10 and the maximum is 100. - -Here an example meesage to query all the tokens owned by the given address: -```json -{ - "tokens": { - "owner": "desmos1......", - "start_after": "1", - "limit": 3 - } -} -``` - -Response: -```json -{ - "tokens": ["2", "3", "4"] -} -``` - -### AllTokens -Lists all token_ids in the contract. This message has the following parameters: -* `start_after`: Position in token id where tokens start after; -* `limit`: Limitation to list the number of tokens, if unset would be 10 and the maximum is 100. - -Here an example meesage to query all the tokens in the contract: -```json -{ - "tokens": { - "start_after": "1", - "limit": 3 - } -} -``` - -Response: -```json -{ - "tokens": ["2", "3", "4"] -} -``` - -### Minter -Returns the minter who is the one having access to mint NFT. - -Here an example meesage to query the minter of the contract: -```json -{ - "minter": {} -} -``` - -Response: -```json -{ - "minter": "desmos1......" -} -``` \ No newline at end of file diff --git a/contracts/cw721-poap/examples/schema.rs b/contracts/cw721-poap/examples/schema.rs deleted file mode 100644 index f847a7d9..00000000 --- a/contracts/cw721-poap/examples/schema.rs +++ /dev/null @@ -1,24 +0,0 @@ -use cosmwasm_schema::{export_schema_with_title, remove_schemas, schema_for}; -use cw721::{AllNftInfoResponse, NftInfoResponse}; -use cw721_poap::{ExecuteMsg, Metadata}; -use std::env::current_dir; -use std::fs::create_dir_all; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema_with_title(&schema_for!(ExecuteMsg), &out_dir, "ExecuteMsg"); - export_schema_with_title( - &schema_for!(AllNftInfoResponse), - &out_dir, - "AllNftInfoResponse", - ); - export_schema_with_title( - &schema_for!(NftInfoResponse), - &out_dir, - "NftInfoResponse", - ); -} diff --git a/contracts/cw721-poap/schema/all_nft_info_response.json b/contracts/cw721-poap/schema/all_nft_info_response.json deleted file mode 100644 index 7401f6a5..00000000 --- a/contracts/cw721-poap/schema/all_nft_info_response.json +++ /dev/null @@ -1,172 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllNftInfoResponse", - "type": "object", - "required": [ - "access", - "info" - ], - "properties": { - "access": { - "description": "Who can transfer the token", - "allOf": [ - { - "$ref": "#/definitions/OwnerOfResponse" - } - ] - }, - "info": { - "description": "Data on the token itself,", - "allOf": [ - { - "$ref": "#/definitions/NftInfoResponse_for_Metadata" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Metadata": { - "type": "object", - "required": [ - "claimer" - ], - "properties": { - "claimer": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - }, - "NftInfoResponse_for_Metadata": { - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "allOf": [ - { - "$ref": "#/definitions/Metadata" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "OwnerOfResponse": { - "type": "object", - "required": [ - "approvals", - "owner" - ], - "properties": { - "approvals": { - "description": "If set this address is approved to transfer/send the token as well", - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - }, - "owner": { - "description": "Owner of the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw721-poap/schema/execute_msg.json b/contracts/cw721-poap/schema/execute_msg.json deleted file mode 100644 index d38aac97..00000000 --- a/contracts/cw721-poap/schema/execute_msg.json +++ /dev/null @@ -1,355 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "description": "This is like Cw721ExecuteMsg but we add a Mint command for an owner to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", - "oneOf": [ - { - "description": "Transfer is a base message to move a token to another account without triggering actions", - "type": "object", - "required": [ - "transfer_nft" - ], - "properties": { - "transfer_nft": { - "type": "object", - "required": [ - "recipient", - "token_id" - ], - "properties": { - "recipient": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", - "type": "object", - "required": [ - "send_nft" - ], - "properties": { - "send_nft": { - "type": "object", - "required": [ - "contract", - "msg", - "token_id" - ], - "properties": { - "contract": { - "type": "string" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve" - ], - "properties": { - "approve": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted Approval", - "type": "object", - "required": [ - "revoke" - ], - "properties": { - "revoke": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve_all" - ], - "properties": { - "approve_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "operator": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted ApproveAll permission", - "type": "object", - "required": [ - "revoke_all" - ], - "properties": { - "revoke_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Mint a new NFT, can only be called by the contract minter", - "type": "object", - "required": [ - "mint" - ], - "properties": { - "mint": { - "$ref": "#/definitions/MintMsg_for_Metadata" - } - }, - "additionalProperties": false - }, - { - "description": "Burn an NFT the sender has access to", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Extension msg", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Metadata": { - "type": "object", - "required": [ - "claimer" - ], - "properties": { - "claimer": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - }, - "MintMsg_for_Metadata": { - "type": "object", - "required": [ - "extension", - "owner", - "token_id" - ], - "properties": { - "extension": { - "description": "Any custom extension used by this contract", - "allOf": [ - { - "$ref": "#/definitions/Metadata" - } - ] - }, - "owner": { - "description": "The owner of the newly minter NFT", - "type": "string" - }, - "token_id": { - "description": "Unique ID of the NFT", - "type": "string" - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw721-poap/schema/nft_info_response.json b/contracts/cw721-poap/schema/nft_info_response.json deleted file mode 100644 index 9ae04ea0..00000000 --- a/contracts/cw721-poap/schema/nft_info_response.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NftInfoResponse", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "allOf": [ - { - "$ref": "#/definitions/Metadata" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Metadata": { - "type": "object", - "required": [ - "claimer" - ], - "properties": { - "claimer": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - } -} diff --git a/contracts/cw721-poap/src/lib.rs b/contracts/cw721-poap/src/lib.rs deleted file mode 100644 index 6d15b4a3..00000000 --- a/contracts/cw721-poap/src/lib.rs +++ /dev/null @@ -1,100 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Empty}; -pub use cw721_base::{ContractError, InstantiateMsg, MintMsg, MinterResponse}; -use desmos_bindings::{msg::DesmosMsg, query::DesmosQuery}; - -#[cw_serde] -pub struct Metadata { - pub claimer: Addr, -} - -pub type Cw721MetadataContract<'a> = - cw721_base::Cw721Contract<'a, Metadata, Empty, Empty, DesmosMsg, DesmosQuery>; -pub type ExecuteMsg = cw721_base::ExecuteMsg; -pub type QueryMsg = cw721_base::QueryMsg; - -#[cfg(not(feature = "library"))] -pub mod entry { - use super::*; - use cosmwasm_std::entry_point; - use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - use cw2::set_contract_version; - - // Version info for migration - const CONTRACT_NAME: &str = "crates.io:cw721-poap"; - const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - - // This makes a conscious choice on the various generics used by the contract - #[entry_point] - pub fn instantiate( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, - ) -> Result, ContractError> { - let res = Cw721MetadataContract::default().instantiate(deps.branch(), env, info, msg)?; - // Explicitly set contract name and version, otherwise set to cw721-base info - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) - .map_err(ContractError::Std)?; - Ok(res) - } - - #[entry_point] - pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, - ) -> Result, ContractError> { - Cw721MetadataContract::default().execute(deps, env, info, msg) - } - - #[entry_point] - pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - Cw721MetadataContract::default().query(deps, env, msg) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{mock_env, mock_info}; - use cw721::Cw721Query; - use desmos_bindings::mocks::mock_queriers::mock_desmos_dependencies; - - const CREATOR: &str = "creator"; - - #[test] - fn use_metadata_extension() { - let mut deps = mock_desmos_dependencies(); - let contract = Cw721MetadataContract::default(); - - let info = mock_info(CREATOR, &[]); - let init_msg = InstantiateMsg { - name: "SpaceShips".to_string(), - symbol: "SPACE".to_string(), - minter: CREATOR.to_string(), - }; - contract - .instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg) - .unwrap(); - - let token_id = "Enterprise"; - let mint_msg = MintMsg { - token_id: token_id.to_string(), - owner: "john".to_string(), - token_uri: Some("https://starships.example.com/Starship/Enterprise.json".into()), - extension: Metadata { - claimer: Addr::unchecked("claimer"), - }, - }; - let exec_msg = ExecuteMsg::Mint(mint_msg.clone()); - contract - .execute(deps.as_mut(), mock_env(), info, exec_msg) - .unwrap(); - - let res = contract.nft_info(deps.as_ref(), token_id.into()).unwrap(); - assert_eq!(res.token_uri, mint_msg.token_uri); - assert_eq!(res.extension, mint_msg.extension); - } -} diff --git a/contracts/poap-manager/.cargo/config b/contracts/poap-manager/.cargo/config deleted file mode 100644 index 11e7d64c..00000000 --- a/contracts/poap-manager/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" -optimize = "run-script optimize" \ No newline at end of file diff --git a/contracts/poap-manager/Cargo.toml b/contracts/poap-manager/Cargo.toml deleted file mode 100644 index bbcaf52b..00000000 --- a/contracts/poap-manager/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "poap-manager" -version = "0.1.0" -authors = ["Paul "] -edition = "2021" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[package.metadata.scripts] -optimize = """docker run --rm -v "$(pwd)"/../..:/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.12.6 ./contracts/poap-manager -""" - -[dependencies] -cosmwasm-std.workspace = true -cosmwasm-storage.workspace = true -cw-storage-plus.workspace = true -cw2.workspace = true -schemars.workspace = true -serde = { workspace = true, default-features = false, features = ["derive"] } -thiserror.workspace = true -desmos-bindings = { workspace = true, default-features = false, features = ["profiles", "query", "msg"]} -poap = { path = "../poap", version = "0.1.0", features = ["library"]} -cw721-base = { git = "https://github.com/desmos-labs/cw-nfts", features = ["library"], branch = "paul/update-custom-msg-query" } -cw-utils.workspace = true -cw721-poap = { path = "../cw721-poap", version = "0.1.0", features = ["library"]} -cosmwasm-schema.workspace = true - -[dev-dependencies] -cw-multi-test.workspace = true -cw721 = { git = "https://github.com/desmos-labs/cw-nfts", branch = "paul/update-custom-msg-query" } -desmos-bindings = { workspace = true, default-features = false, features = ["mocks"]} - diff --git a/contracts/poap-manager/README.md b/contracts/poap-manager/README.md deleted file mode 100644 index 05a7dedb..00000000 --- a/contracts/poap-manager/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# POAP manager contract - -The controller contract of the [POAP contract](../poap/README.md) that allows users who has a Desmos profile to mint POAP NFTs. -To easily interact with the contract you can use the `poap-manager` script available [here](https://github.com/desmos-labs/contract-utils/tree/main/utils), -otherwise you can take a look at the supported messages in the following sections. - -## Instantiate Message -Allows to initialize the contract. This message has the following parameters: -* `admin`: Address of the user that has the rights to administer the contract; -* `poap_code_id`: Id of the POAP contract to be initialized together with this contract; -* `poap_instantiate_msg`: Initialization [message](../poap/README.md#instantiate_message) that will be sent to the POAP contract; - -Here an example message to instantiate the contract: -```json -{ - "admin": "desmos1......", - "poap_code_id": "1", - "poap_instantiate_msg": { - "admin": "desmos1......", - "minter": "poap_manager_contract_address", - "cw721_code_id": "2", - "cw721_instantiate_msg": { - "name": "poap_nft", - "symbol": "poap", - "minter": "poap_contract_address" - }, - "event_info": { - "creator": "desmos1......", - "start_time": "2022-12-31T10:00:00Z", - "end_time": "2022-12-31T19:00:00Z", - "per_address_limit": 1, - "poap_uri": "ipfs://poap.info" - } - } -} -``` - -## Execute Messages - -### Claim -Allows users who have a Desmos profile to claim a POAP token during the event if the mint -has been enabled. - -Here an example message to claim a POAP: -```json -{ - "claim": {} -} -``` - -### MintTo -Allows the admin to mint a POAP token to a recipient during the event if mint was enabled. This message has the following parameter: -* `recipient`: Address who will receive the minted token. - -Here an example message to mint a POAP to a user: -```json -{ - "mint_to": { - "recipient": "desmos1......" - } -} -``` - -### UpdateAdmin -Allows the contract's admin to transfer the admin rights to another user. This message has the following parameter: -* `new_admin`: Address of the new admin that will control this contract. - -Here an example message to update the contract admin: -```json -{ - "update_admin": { - "new_admin": "desmos1......" - } -} -``` - -## Query Messages - -### Config -Allows to query the config of the contract. - -Here an example message to query the config: -```json -{ - "config": {} -} -``` - -Response: -```json -{ - "admin": "desmos1......", - "poap_code_id": "1", - "poap_contract_address": "desmos1......" -} -``` \ No newline at end of file diff --git a/contracts/poap-manager/examples/schema.rs b/contracts/poap-manager/examples/schema.rs deleted file mode 100644 index 47d51e99..00000000 --- a/contracts/poap-manager/examples/schema.rs +++ /dev/null @@ -1,10 +0,0 @@ -use cosmwasm_schema::write_api; -use poap_manager::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - } -} diff --git a/contracts/poap-manager/schema/poap-manager.json b/contracts/poap-manager/schema/poap-manager.json deleted file mode 100644 index 92f0fdda..00000000 --- a/contracts/poap-manager/schema/poap-manager.json +++ /dev/null @@ -1,293 +0,0 @@ -{ - "contract_name": "poap-manager", - "contract_version": "0.1.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "admin", - "poap_code_id", - "poap_instantiate_msg" - ], - "properties": { - "admin": { - "description": "Address of who will have the right to administer the contract.", - "type": "string" - }, - "poap_code_id": { - "description": "Id of the POAP contract to be initialized along with this contract.", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "poap_instantiate_msg": { - "description": "Initialization message of the POAP contract.", - "allOf": [ - { - "$ref": "#/definitions/PoapInstantiateMsg" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "EventInfo": { - "type": "object", - "required": [ - "creator", - "end_time", - "per_address_limit", - "poap_uri", - "start_time" - ], - "properties": { - "creator": { - "description": "User that created the event.", - "type": "string" - }, - "end_time": { - "description": "Time at which the event ends.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - }, - "per_address_limit": { - "description": "Max amount of poap that a single user can mint.", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "poap_uri": { - "description": "Identifies a valid IPFS URI corresponding to where the assets and metadata of the POAPs are stored.", - "type": "string" - }, - "start_time": { - "description": "Time at which the event begins.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - } - }, - "additionalProperties": false - }, - "InstantiateMsg": { - "type": "object", - "required": [ - "minter", - "name", - "symbol" - ], - "properties": { - "minter": { - "description": "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", - "type": "string" - }, - "name": { - "description": "Name of the NFT contract", - "type": "string" - }, - "symbol": { - "description": "Symbol of the NFT contract", - "type": "string" - } - }, - "additionalProperties": false - }, - "PoapInstantiateMsg": { - "title": "InstantiateMsg", - "type": "object", - "required": [ - "admin", - "cw721_code_id", - "cw721_instantiate_msg", - "event_info", - "minter" - ], - "properties": { - "admin": { - "description": "Address of who will have the right to administer the contract.", - "type": "string" - }, - "cw721_code_id": { - "description": "Id of the CW721 contract to initialize together with this contract.", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "cw721_instantiate_msg": { - "description": "Initialization message that will be sent to the CW721 contract.", - "allOf": [ - { - "$ref": "#/definitions/InstantiateMsg" - } - ] - }, - "event_info": { - "description": "Information about the event.", - "allOf": [ - { - "$ref": "#/definitions/EventInfo" - } - ] - }, - "minter": { - "description": "Address of who can call the [`ExecuteMsg::MintTo`] other then the admin.", - "type": "string" - } - }, - "additionalProperties": false - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Allows users to claim a POAP token.", - "type": "object", - "required": [ - "claim" - ], - "properties": { - "claim": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows the contract's admin to mint a POAP for a specific recipient.", - "type": "object", - "required": [ - "mint_to" - ], - "properties": { - "mint_to": { - "type": "object", - "required": [ - "recipient" - ], - "properties": { - "recipient": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows the contract's admin to transfer the admin rights to another user.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "new_admin" - ], - "properties": { - "new_admin": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "Returns a ConfigResponse containing the configuration info of the Manager contract", - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": null, - "sudo": null, - "responses": { - "config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryConfigResponse", - "type": "object", - "required": [ - "admin", - "poap_code_id", - "poap_contract_address" - ], - "properties": { - "admin": { - "description": "Address of the contract administrator.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "poap_code_id": { - "description": "Id of the POAP contract that this contract has initialized.", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "poap_contract_address": { - "description": "Address of the POAP contract", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - } - } - } - } -} diff --git a/contracts/poap-manager/src/contract.rs b/contracts/poap-manager/src/contract.rs deleted file mode 100644 index 79011b5b..00000000 --- a/contracts/poap-manager/src/contract.rs +++ /dev/null @@ -1,514 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_binary, wasm_execute, wasm_instantiate, Addr, Deps, DepsMut, Env, MessageInfo, - QueryResponse, Reply, Response, StdResult, Storage, SubMsg, -}; -use cw2::set_contract_version; -use cw_utils::parse_reply_instantiate_data; - -use desmos_bindings::{msg::DesmosMsg, profiles::querier::ProfilesQuerier, query::DesmosQuery}; -use poap::msg::ExecuteMsg as POAPExecuteMsg; - -use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryConfigResponse, QueryMsg}; -use crate::state::{Config, CONFIG, POAP_CONTRACT_ADDRESS}; - -use std::ops::Deref; - -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:poap-manager"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -// actions for executing messages -const ACTION_INSTANTIATE: &str = "instantiate"; -const ACTION_INSTANTIATE_POAP_REPLY: &str = "instantiate_poap_reply"; -const ACTION_CLAIM: &str = "claim"; -const ACTION_MINT_TO: &str = "mint_to"; -const ACTION_UPDATE_ADMIN: &str = "update_admin"; - -// attributes for executing messages -const ATTRIBUTE_ACTION: &str = "action"; -const ATTRIBUTE_ADMIN: &str = "admin"; -const ATTRIBUTE_POAP_CODE_ID: &str = "poap_code_id"; -const ATTRIBUTE_SENDER: &str = "sender"; -const ATTRIBUTE_NEW_ADMIN: &str = "new_admin"; - -// reply ids for handling reply tasks -const INSTANTIATE_POAP_REPLY_ID: u64 = 1; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> Result, ContractError> { - msg.validate()?; - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let admin = deps.api.addr_validate(&msg.admin)?; - let config = Config { - admin: admin.clone(), - poap_code_id: msg.poap_code_id.u64(), - }; - CONFIG.save(deps.storage, &config)?; - - // assign the minter of poap contract to the manager's contract address - let mut poap_instantiate_msg = msg.poap_instantiate_msg; - poap_instantiate_msg.minter = env.contract.address.into(); - - Ok(Response::new() - .add_attribute("action", ACTION_INSTANTIATE) - .add_attribute(ATTRIBUTE_ADMIN, admin) - .add_attribute(ATTRIBUTE_POAP_CODE_ID, msg.poap_code_id) - .add_submessage(SubMsg::reply_on_success( - wasm_instantiate( - msg.poap_code_id.u64(), - &poap_instantiate_msg, - info.funds, - "poap".into(), - )?, - INSTANTIATE_POAP_REPLY_ID, - ))) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply( - deps: DepsMut, - _env: Env, - msg: Reply, -) -> Result, ContractError> { - match msg.id { - INSTANTIATE_POAP_REPLY_ID => resolve_instantiate_poap_reply(deps, msg), - _ => Err(ContractError::InvalidReplyID {}), - } -} - -fn resolve_instantiate_poap_reply( - deps: DepsMut, - msg: Reply, -) -> Result, ContractError> { - let res = parse_reply_instantiate_data(msg)?; - let address = deps.api.addr_validate(&res.contract_address)?; - POAP_CONTRACT_ADDRESS.save(deps.storage, &address)?; - Ok(Response::new().add_attribute(ATTRIBUTE_ACTION, ACTION_INSTANTIATE_POAP_REPLY)) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result, ContractError> { - match msg { - ExecuteMsg::Claim {} => execute_claim(deps, info), - ExecuteMsg::MintTo { recipient } => execute_mint_to(deps, info, recipient), - ExecuteMsg::UpdateAdmin { new_admin } => execute_update_admin(deps, info, new_admin), - } -} - -fn execute_claim( - deps: DepsMut, - info: MessageInfo, -) -> Result, ContractError> { - let poap_contract_address = POAP_CONTRACT_ADDRESS.load(deps.storage)?; - if !check_eligibility(deps, info.sender.clone()) { - return Err(ContractError::NoEligibilityError {}); - } - Ok(Response::new() - .add_attribute(ATTRIBUTE_ACTION, ACTION_CLAIM) - .add_attribute(ATTRIBUTE_SENDER, &info.sender) - .add_message(wasm_execute( - poap_contract_address, - &POAPExecuteMsg::MintTo { - recipient: info.sender.into(), - }, - info.funds, - )?)) -} - -fn check_eligibility(deps: DepsMut, user: Addr) -> bool { - ProfilesQuerier::new(deps.querier.deref()) - .query_profile(user) - .is_ok() -} - -fn execute_mint_to( - deps: DepsMut, - info: MessageInfo, - recipient: String, -) -> Result, ContractError> { - check_admin(deps.storage, &info)?; - let poap_contract_address = POAP_CONTRACT_ADDRESS.load(deps.storage)?; - deps.api.addr_validate(&recipient)?; - Ok(Response::new() - .add_attribute(ATTRIBUTE_ACTION, ACTION_MINT_TO) - .add_attribute(ATTRIBUTE_SENDER, &info.sender) - .add_message(wasm_execute( - poap_contract_address, - &POAPExecuteMsg::MintTo { recipient }, - info.funds, - )?)) -} - -fn execute_update_admin( - deps: DepsMut, - info: MessageInfo, - user: String, -) -> Result, ContractError> { - check_admin(deps.storage, &info)?; - let new_admin = deps.api.addr_validate(&user)?; - CONFIG.update(deps.storage, |mut config| -> Result<_, ContractError> { - config.admin = new_admin.clone(); - Ok(config) - })?; - Ok(Response::new() - .add_attribute(ATTRIBUTE_ACTION, ACTION_UPDATE_ADMIN) - .add_attribute(ATTRIBUTE_NEW_ADMIN, new_admin) - .add_attribute(ATTRIBUTE_SENDER, info.sender)) -} - -fn check_admin(storage: &dyn Storage, info: &MessageInfo) -> Result<(), ContractError> { - let config = CONFIG.load(storage)?; - if config.admin != info.sender { - return Err(ContractError::NotAdmin { - caller: info.sender.clone(), - }); - } - Ok(()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - } -} - -fn query_config(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - Ok(QueryConfigResponse { - admin: config.admin, - poap_code_id: config.poap_code_id, - poap_contract_address: POAP_CONTRACT_ADDRESS.load(deps.storage)?, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{mock_env, mock_info}; - use cosmwasm_std::{StdError, SubMsgResponse, SubMsgResult, Timestamp}; - use cw721_base::InstantiateMsg as Cw721InstantiateMsg; - use cw_utils::ParseReplyError; - use desmos_bindings::mocks::mock_queriers::mock_desmos_dependencies; - use poap::msg::{EventInfo, InstantiateMsg as POAPInstantiateMsg}; - - const CREATOR: &str = "desmos1nwp8gxrnmrsrzjdhvk47vvmthzxjtphgxp5ftc"; - const NEW_ADMIN: &str = "desmos1fcrca0eyvj32yeqwyqgs245gjmq4ee9vjjdlnz"; - - fn get_valid_instantiate() -> InstantiateMsg { - InstantiateMsg { - admin: "desmos1nwp8gxrnmrsrzjdhvk47vvmthzxjtphgxp5ftc".into(), - poap_code_id: 1u64.into(), - poap_instantiate_msg: POAPInstantiateMsg { - admin: CREATOR.into(), - minter: CREATOR.into(), - cw721_code_id: 2u64.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - minter: CREATOR.into(), - name: CREATOR.into(), - symbol: CREATOR.into(), - }, - event_info: EventInfo { - creator: CREATOR.into(), - start_time: Timestamp::from_seconds(10), - end_time: Timestamp::from_seconds(20), - per_address_limit: 2, - poap_uri: "ipfs://popap-uri".to_string(), - }, - }, - } - } - - fn do_instantiate(deps: DepsMut) { - let env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let valid_msg = get_valid_instantiate(); - instantiate(deps, env, info, valid_msg).unwrap(); - } - - #[test] - fn instatiate_with_invalid_msg_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let invalid_msg = InstantiateMsg { - admin: "".into(), - poap_code_id: 0u64.into(), - poap_instantiate_msg: POAPInstantiateMsg { - admin: CREATOR.into(), - minter: CREATOR.into(), - cw721_code_id: 2u64.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - minter: CREATOR.into(), - name: CREATOR.into(), - symbol: CREATOR.into(), - }, - event_info: EventInfo { - creator: CREATOR.into(), - start_time: Timestamp::from_seconds(10), - end_time: Timestamp::from_seconds(20), - per_address_limit: 2, - poap_uri: "ipfs://popap-uri".to_string(), - }, - }, - }; - assert_eq!( - instantiate(deps.as_mut(), env, info, invalid_msg).unwrap_err(), - ContractError::InvalidPOAPCodeID {}, - ) - } - - #[test] - fn instatiate_with_invalid_admin_address_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let invalid_msg = InstantiateMsg { - admin: "a".into(), - poap_code_id: 1u64.into(), - poap_instantiate_msg: POAPInstantiateMsg { - admin: CREATOR.into(), - minter: CREATOR.into(), - cw721_code_id: 2u64.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - minter: CREATOR.into(), - name: CREATOR.into(), - symbol: CREATOR.into(), - }, - event_info: EventInfo { - creator: CREATOR.into(), - start_time: Timestamp::from_seconds(10), - end_time: Timestamp::from_seconds(20), - per_address_limit: 2, - poap_uri: "ipfs://popap-uri".to_string(), - }, - }, - }; - assert_eq!( - instantiate(deps.as_mut(), env, info, invalid_msg).unwrap_err(), - ContractError::Std(StdError::generic_err( - "Invalid input: human address too short" - )) - ) - } - - #[test] - fn instatiate_properly() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - - let config = CONFIG.load(&deps.storage).unwrap(); - let expected = Config { - admin: Addr::unchecked("desmos1nwp8gxrnmrsrzjdhvk47vvmthzxjtphgxp5ftc"), - poap_code_id: 1u64, - }; - assert_eq!(config, expected) - } - - #[test] - fn poap_instantiate_with_invalid_reply_id_error() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - let env = mock_env(); - let result = reply( - deps.as_mut(), - env, - Reply { - id: 2, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }, - ); - assert_eq!(result.unwrap_err(), ContractError::InvalidReplyID {},) - } - - #[test] - fn poap_instantiate_with_invalid_instantiate_msg_error() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - let env = mock_env(); - let result = reply( - deps.as_mut(), - env, - Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }, - ); - assert_eq!( - result.unwrap_err(), - ContractError::ParseReplyError(ParseReplyError::ParseFailure( - "Missing reply data".into() - )) - ) - } - - #[test] - fn claim_with_unsupported_desmos_deps_error() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - let env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let msg = ExecuteMsg::Claim {}; - assert_eq!( - execute(deps.as_mut(), env, info, msg).unwrap_err(), - ContractError::Std(StdError::not_found("cosmwasm_std::addresses::Addr")) - ) - } - - #[test] - fn claim_properly() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - POAP_CONTRACT_ADDRESS - .save(deps.as_mut().storage, &Addr::unchecked("")) - .unwrap(); - let env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let msg = ExecuteMsg::Claim {}; - execute(deps.as_mut(), env, info, msg).unwrap(); - } - - #[test] - fn mint_to_without_permissions_error() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - POAP_CONTRACT_ADDRESS - .save(deps.as_mut().storage, &Addr::unchecked("")) - .unwrap(); - let env = mock_env(); - let info = mock_info(NEW_ADMIN, &vec![]); - let msg = ExecuteMsg::MintTo { - recipient: CREATOR.into(), - }; - assert_eq!( - ContractError::NotAdmin { - caller: Addr::unchecked(NEW_ADMIN) - }, - execute(deps.as_mut(), env, info, msg).unwrap_err(), - ) - } - - #[test] - fn mint_to_with_invalid_recipient_error() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - POAP_CONTRACT_ADDRESS - .save(deps.as_mut().storage, &Addr::unchecked("")) - .unwrap(); - let env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let msg = ExecuteMsg::MintTo { - recipient: "a".into(), - }; - assert_eq!( - execute(deps.as_mut(), env, info, msg).unwrap_err(), - ContractError::Std(StdError::generic_err( - "Invalid input: human address too short" - )) - ) - } - - #[test] - fn mint_to_properly() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - POAP_CONTRACT_ADDRESS - .save(deps.as_mut().storage, &Addr::unchecked("")) - .unwrap(); - let env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let msg = ExecuteMsg::MintTo { - recipient: CREATOR.into(), - }; - execute(deps.as_mut(), env, info, msg).unwrap(); - } - - #[test] - fn update_admin_with_invalid_new_admin_error() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - let env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let msg = ExecuteMsg::UpdateAdmin { - new_admin: "".into(), - }; - assert_eq!( - execute(deps.as_mut(), env, info, msg).unwrap_err(), - ContractError::Std(StdError::generic_err( - "Invalid input: human address too short" - )) - ) - } - - #[test] - fn update_admin_without_permission_error() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - let env = mock_env(); - let info = mock_info(NEW_ADMIN, &vec![]); - let msg = ExecuteMsg::UpdateAdmin { - new_admin: NEW_ADMIN.into(), - }; - assert_eq!( - execute(deps.as_mut(), env, info, msg).unwrap_err(), - ContractError::NotAdmin { - caller: Addr::unchecked(NEW_ADMIN) - } - ) - } - - #[test] - fn update_admin_with_invalid_address_error() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - let env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let msg = ExecuteMsg::UpdateAdmin { - new_admin: "a".into(), - }; - assert_eq!( - execute(deps.as_mut(), env, info, msg).unwrap_err(), - ContractError::Std(StdError::generic_err( - "Invalid input: human address too short" - )) - ) - } - - #[test] - fn update_admin_properly() { - let mut deps = mock_desmos_dependencies(); - do_instantiate(deps.as_mut()); - let env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let msg = ExecuteMsg::UpdateAdmin { - new_admin: NEW_ADMIN.into(), - }; - assert!(execute(deps.as_mut(), env, info, msg).is_ok()); - - let config = CONFIG.load(&deps.storage).unwrap(); - let expected = Config { - admin: Addr::unchecked(NEW_ADMIN), - poap_code_id: 1u64, - }; - assert_eq!(config, expected) - } -} diff --git a/contracts/poap-manager/src/error.rs b/contracts/poap-manager/src/error.rs deleted file mode 100644 index c3bd95c4..00000000 --- a/contracts/poap-manager/src/error.rs +++ /dev/null @@ -1,27 +0,0 @@ -use cosmwasm_std::{Addr, StdError}; -use cw_utils::ParseReplyError; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Invalid reply ID")] - InvalidReplyID {}, - - #[error("Invalid POAP code ID")] - InvalidPOAPCodeID {}, - - #[error("Instantiate POAP contract error")] - InstantiatePOAPError {}, - - #[error("Caller is not admin: {caller}")] - NotAdmin { caller: Addr }, - - #[error("{0}")] - ParseReplyError(#[from] ParseReplyError), - - #[error("No eligibility error")] - NoEligibilityError {}, -} diff --git a/contracts/poap-manager/src/integration_tests.rs b/contracts/poap-manager/src/integration_tests.rs deleted file mode 100644 index 1f9df16b..00000000 --- a/contracts/poap-manager/src/integration_tests.rs +++ /dev/null @@ -1,255 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::msg::{ExecuteMsg, InstantiateMsg, QueryConfigResponse, QueryMsg}; - use crate::test_utils::*; - use cosmwasm_std::{wasm_execute, Addr, Timestamp}; - use cw721_base::InstantiateMsg as Cw721InstantiateMsg; - use cw_multi_test::{Contract, ContractWrapper, Executor}; - use desmos_bindings::{ - mocks::mock_apps::{mock_desmos_app, mock_failing_desmos_app, DesmosApp, DesmosModule}, - msg::DesmosMsg, - query::DesmosQuery, - }; - use poap::msg::{ - EventInfo, InstantiateMsg as POAPInstantiateMsg, - QueryConfigResponse as POAPQueryConfigResponse, - QueryMintedAmountResponse as POAPQueryMintedAmountResponse, QueryMsg as POAPQueryMsg, - }; - - const ADMIN: &str = "admin"; - const RECIPIENT: &str = "recipient"; - - fn contract_poap_manager() -> Box> { - let contract = ContractWrapper::new( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ) - .with_reply(crate::contract::reply); - Box::new(contract) - } - - fn store_contracts(app: &mut DesmosApp) -> (u64, u64, u64) { - let cw721_code_id = app.store_code(CW721TestContract::success_contract()); - let poap_code_id = app.store_code(POAPTestContract::success_contract()); - let poap_manager_code_id = app.store_code(contract_poap_manager()); - (cw721_code_id, poap_code_id, poap_manager_code_id) - } - - fn get_valid_init_msg(cw721_code_id: u64, poap_code_id: u64) -> InstantiateMsg { - InstantiateMsg { - admin: ADMIN.into(), - poap_code_id: poap_code_id.into(), - poap_instantiate_msg: POAPInstantiateMsg { - admin: ADMIN.into(), - minter: "".into(), - cw721_code_id: cw721_code_id.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - minter: "".into(), - name: "test".into(), - symbol: "test".into(), - }, - event_info: EventInfo { - creator: "creator".to_string(), - start_time: Timestamp::from_seconds(10), - end_time: Timestamp::from_seconds(20), - per_address_limit: 2, - poap_uri: "ipfs://popap-uri".to_string(), - }, - }, - } - } - - fn proper_instantiate(app: &mut DesmosApp) -> (Addr, (u64, u64, u64)) { - app.update_block(|block| { - // init the time to before the event - block.time = Timestamp::from_seconds(0); - }); - let (cw721_code_id, poap_code_id, poap_manager_code_id) = store_contracts(app); - let poap_manager_contract_addr = app - .instantiate_contract( - poap_manager_code_id, - Addr::unchecked(ADMIN), - &get_valid_init_msg(cw721_code_id, poap_code_id), - &[], - "poap_manager_contract", - None, - ) - .unwrap(); - app.update_block(|block| { - // update the time to start time of event - block.time = Timestamp::from_seconds(10); - }); - ( - poap_manager_contract_addr, - (cw721_code_id, poap_code_id, poap_manager_code_id), - ) - } - - #[test] - fn instantiate_with_invalid_poap_code_id_error() { - let mut app = mock_desmos_app(); - let (cw721_code_id, poap_code_id, poap_manager_code_id) = store_contracts(&mut app); - let mut init_msg = get_valid_init_msg(cw721_code_id, poap_code_id); - // change code poap_code_id to the invalid one - init_msg.poap_code_id = cw721_code_id.into(); - let init_result = app.instantiate_contract( - poap_manager_code_id, - Addr::unchecked(ADMIN), - &init_msg, - &[], - "poap_manager_contract", - None, - ); - assert!(init_result.is_err()); - } - - #[test] - fn instantiate_with_invalid_cw721_code_id_error() { - let mut app = mock_desmos_app(); - let (cw721_code_id, poap_code_id, poap_manager_code_id) = store_contracts(&mut app); - let mut init_msg = get_valid_init_msg(cw721_code_id, poap_code_id); - // change cw721_code_id to the invalid one - init_msg.poap_instantiate_msg.cw721_code_id = poap_code_id.into(); - let init_result = app.instantiate_contract( - poap_manager_code_id, - Addr::unchecked(ADMIN), - &init_msg, - &[], - "poap_manager_contract", - None, - ); - assert!(init_result.is_err()); - } - - #[test] - fn instantiate_with_failing_poap_contract_error() { - let mut app = mock_desmos_app(); - let (cw721_code_id, _, poap_manager_code_id) = store_contracts(&mut app); - let failing_poap_code_id = app.store_code(POAPTestContract::failing_contract()); - let mut init_msg = get_valid_init_msg(cw721_code_id, failing_poap_code_id); - // change id to the failing one - init_msg.poap_code_id = failing_poap_code_id.into(); - let init_result = app.instantiate_contract( - poap_manager_code_id, - Addr::unchecked(ADMIN), - &init_msg, - &[], - "poap_manager_contract", - None, - ); - assert!(init_result.is_err()); - } - - #[test] - fn instantiate_propery() { - let mut app = mock_desmos_app(); - let (manager_addr, (_, paop_code_id, _)) = proper_instantiate(&mut app); - let querier = app.wrap(); - - // check manager config set properly - let manager_config: QueryConfigResponse = querier - .query_wasm_smart(&manager_addr, &QueryMsg::Config {}) - .unwrap(); - assert_eq!(manager_config.admin, ADMIN); - assert_eq!(manager_config.poap_code_id, paop_code_id); - - // check if poap minter is manager contract - let poap_config: POAPQueryConfigResponse = querier - .query_wasm_smart( - manager_config.poap_contract_address, - &POAPQueryMsg::Config {}, - ) - .unwrap(); - assert_eq!(manager_addr, poap_config.minter); - } - - #[test] - fn user_claim_without_profile_error() { - let mut app = mock_failing_desmos_app(); - let (manager_addr, _) = proper_instantiate(&mut app); - let result = app.execute( - Addr::unchecked(ADMIN), - wasm_execute(&manager_addr, &ExecuteMsg::Claim {}, vec![]) - .unwrap() - .into(), - ); - assert!(result.is_err()); - - // check the state of poap contract - let querier = app.wrap(); - let manager_config: QueryConfigResponse = querier - .query_wasm_smart(&manager_addr, &QueryMsg::Config {}) - .unwrap(); - let minted_amount_response: POAPQueryMintedAmountResponse = querier - .query_wasm_smart( - manager_config.poap_contract_address, - &POAPQueryMsg::MintedAmount { user: ADMIN.into() }, - ) - .unwrap(); - assert_eq!(minted_amount_response.user, ADMIN); - assert_eq!(minted_amount_response.amount, 0) - } - - #[test] - fn user_claim_poap_properly() { - let mut app = mock_desmos_app(); - let (manager_addr, _) = proper_instantiate(&mut app); - app.execute( - Addr::unchecked(ADMIN), - wasm_execute(&manager_addr, &ExecuteMsg::Claim {}, vec![]) - .unwrap() - .into(), - ) - .unwrap(); - - // check the state of poap contract - let querier = app.wrap(); - let manager_config: QueryConfigResponse = querier - .query_wasm_smart(&manager_addr, &QueryMsg::Config {}) - .unwrap(); - let minted_amount_response: POAPQueryMintedAmountResponse = querier - .query_wasm_smart( - manager_config.poap_contract_address, - &POAPQueryMsg::MintedAmount { user: ADMIN.into() }, - ) - .unwrap(); - assert_eq!(minted_amount_response.user, ADMIN); - assert_eq!(minted_amount_response.amount, 1) - } - - #[test] - fn mint_poap_to_recipient_properly() { - let mut app = mock_desmos_app(); - let (manager_addr, _) = proper_instantiate(&mut app); - app.execute( - Addr::unchecked(ADMIN), - wasm_execute( - &manager_addr, - &ExecuteMsg::MintTo { - recipient: RECIPIENT.into(), - }, - vec![], - ) - .unwrap() - .into(), - ) - .unwrap(); - - // check the state of poap contract - let querier = app.wrap(); - let manager_config: QueryConfigResponse = querier - .query_wasm_smart(&manager_addr, &QueryMsg::Config {}) - .unwrap(); - let minted_amount_response: POAPQueryMintedAmountResponse = querier - .query_wasm_smart( - manager_config.poap_contract_address, - &POAPQueryMsg::MintedAmount { - user: RECIPIENT.into(), - }, - ) - .unwrap(); - assert_eq!(minted_amount_response.user, RECIPIENT); - assert_eq!(minted_amount_response.amount, 1) - } -} diff --git a/contracts/poap-manager/src/lib.rs b/contracts/poap-manager/src/lib.rs deleted file mode 100644 index 80e7fbd2..00000000 --- a/contracts/poap-manager/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod contract; -mod error; -pub mod msg; -pub mod state; - -mod integration_tests; -mod test_utils; - -pub use crate::error::ContractError; diff --git a/contracts/poap-manager/src/msg.rs b/contracts/poap-manager/src/msg.rs deleted file mode 100644 index ea643b2f..00000000 --- a/contracts/poap-manager/src/msg.rs +++ /dev/null @@ -1,114 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Uint64}; - -use crate::error::ContractError; -use poap::msg::InstantiateMsg as POAPInstantiateMsg; - -#[cw_serde] -#[schemars(rename = "PoapManagerInstantiateMsg", title = "InstantiateMsg")] -pub struct InstantiateMsg { - /// Address of who will have the right to administer the contract. - pub admin: String, - /// Id of the POAP contract to be initialized along with this contract. - pub poap_code_id: Uint64, - /// Initialization message of the POAP contract. - pub poap_instantiate_msg: POAPInstantiateMsg, -} - -impl InstantiateMsg { - pub fn validate(&self) -> Result<(), ContractError> { - if self.poap_code_id == Uint64::zero() { - return Err(ContractError::InvalidPOAPCodeID {}); - } - Ok(()) - } -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Allows users to claim a POAP token. - Claim {}, - /// Allows the contract's admin to mint a POAP for a specific recipient. - MintTo { recipient: String }, - /// Allows the contract's admin to transfer the admin rights to another user. - UpdateAdmin { new_admin: String }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns a ConfigResponse containing the configuration info of the Manager contract - #[returns(QueryConfigResponse)] - Config {}, -} - -#[cw_serde] -pub struct QueryConfigResponse { - /// Address of the contract administrator. - pub admin: Addr, - /// Id of the POAP contract that this contract has initialized. - pub poap_code_id: u64, - /// Address of the POAP contract - pub poap_contract_address: Addr, -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::Timestamp; - use cw721_base::InstantiateMsg as Cw721InstantiateMsg; - use poap::msg::EventInfo; - - #[test] - fn instantiate_msg_with_invalid_poap_id_error() { - let msg = InstantiateMsg { - admin: "desmos1nwp8gxrnmrsrzjdhvk47vvmthzxjtphgxp5ftc".into(), - poap_code_id: 0u64.into(), - poap_instantiate_msg: POAPInstantiateMsg { - admin: "test".into(), - minter: "test".into(), - cw721_code_id: 2u64.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - minter: "".into(), - name: "test".into(), - symbol: "test".into(), - }, - event_info: EventInfo { - creator: "creator".to_string(), - start_time: Timestamp::from_seconds(10), - end_time: Timestamp::from_seconds(20), - per_address_limit: 2, - poap_uri: "ipfs://popap-uri".to_string(), - }, - }, - }; - let result = msg.validate(); - assert_eq!(result.unwrap_err(), ContractError::InvalidPOAPCodeID {},) - } - - #[test] - fn proper_instantiate_msg_no_error() { - let msg = InstantiateMsg { - admin: "desmos1nwp8gxrnmrsrzjdhvk47vvmthzxjtphgxp5ftc".into(), - poap_code_id: 1u64.into(), - poap_instantiate_msg: POAPInstantiateMsg { - admin: "test".into(), - minter: "test".into(), - cw721_code_id: 2u64.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - minter: "".into(), - name: "test".into(), - symbol: "test".into(), - }, - event_info: EventInfo { - creator: "creator".to_string(), - start_time: Timestamp::from_seconds(10), - end_time: Timestamp::from_seconds(20), - per_address_limit: 2, - poap_uri: "ipfs://popap-uri".to_string(), - }, - }, - }; - msg.validate().unwrap(); - } -} diff --git a/contracts/poap-manager/src/state.rs b/contracts/poap-manager/src/state.rs deleted file mode 100644 index f2ec89ac..00000000 --- a/contracts/poap-manager/src/state.rs +++ /dev/null @@ -1,13 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Addr; -use cw_storage_plus::Item; - -#[cw_serde] -pub struct Config { - pub admin: Addr, - pub poap_code_id: u64, -} - -pub const POAP_CONTRACT_ADDRESS: Item = Item::new("poap_contract_address"); - -pub const CONFIG: Item = Item::new("config"); diff --git a/contracts/poap-manager/src/test_utils.rs b/contracts/poap-manager/src/test_utils.rs deleted file mode 100644 index 4dd19225..00000000 --- a/contracts/poap-manager/src/test_utils.rs +++ /dev/null @@ -1,132 +0,0 @@ -#![cfg(test)] -use cosmwasm_std::{ - Binary, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, StdError, StdResult, -}; -use cw721_base::{ - ContractError as Cw721ContractError, Cw721Contract, ExecuteMsg as Cw721ExecuteMsg, - InstantiateMsg as Cw721InstantiateMsg, QueryMsg as Cw721QueryMsg, -}; -use cw721_poap::Metadata; -use cw_multi_test::{Contract, ContractWrapper}; -use desmos_bindings::{msg::DesmosMsg, query::DesmosQuery}; -use poap::{ - contract::{ - execute as poap_execute, instantiate as poap_instantiate, query as poap_query, - reply as poap_reply, - }, - msg::{ - ExecuteMsg as POAPExecuteMsg, InstantiateMsg as POAPInstantiateMsg, - QueryMsg as POAPQueryMsg, - }, - ContractError as POAPContractError, -}; - -pub struct POAPTestContract; -impl POAPTestContract { - fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: POAPInstantiateMsg, - ) -> Result, POAPContractError> { - poap_instantiate(deps, env, info, msg) - } - - fn failing_instantiate( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: Cw721InstantiateMsg, - ) -> Result, POAPContractError> { - Err(POAPContractError::Std(StdError::generic_err( - "poap initialization failed", - ))) - } - - fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: POAPExecuteMsg, - ) -> Result, POAPContractError> { - poap_execute(deps, env, info, msg) - } - - fn query(deps: Deps, env: Env, msg: POAPQueryMsg) -> StdResult { - poap_query(deps, env, msg) - } - - fn reply( - deps: DepsMut, - env: Env, - msg: Reply, - ) -> Result, POAPContractError> { - poap_reply(deps, env, msg) - } - - /// Provides an instance of a poap contract. - /// This instance can be used only during the integration tests. - pub fn success_contract() -> Box> { - let contract = ContractWrapper::new(Self::execute, Self::instantiate, Self::query) - .with_reply(Self::reply); - Box::new(contract) - } - - /// Provides an instance of a poap contract that fails during the initialization. - /// This instance can be used only during the integration tests. - pub fn failing_contract() -> Box> { - let contract = ContractWrapper::new(Self::execute, Self::failing_instantiate, Self::query); - Box::new(contract) - } -} - -pub struct CW721TestContract; -impl CW721TestContract { - fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Cw721InstantiateMsg, - ) -> Result, StdError> { - Cw721Contract::<'static, Metadata, Empty, Empty, DesmosMsg, DesmosQuery>::default() - .instantiate(deps, env, info, msg) - } - - fn failing_instantiate( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: Cw721InstantiateMsg, - ) -> Result, StdError> { - Err(StdError::generic_err("cw721 initialization failed")) - } - - fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Cw721ExecuteMsg, - ) -> Result, Cw721ContractError> { - Cw721Contract::<'static, Metadata, Empty, Empty, DesmosMsg, DesmosQuery>::default() - .execute(deps, env, info, msg) - } - - fn query(deps: Deps, env: Env, msg: Cw721QueryMsg) -> StdResult { - Cw721Contract::<'static, Metadata, Empty, Empty, DesmosMsg, DesmosQuery>::default() - .query(deps, env, msg) - } - - /// Provides an instance of a cw721 contract. - /// This instance can be used only during the integration tests. - pub fn success_contract() -> Box> { - let contract = ContractWrapper::new(Self::execute, Self::instantiate, Self::query); - Box::new(contract) - } - - /// Provides an instance of a cw721 contract that fails during the initialization. - /// This instance can be used only during the integration tests. - pub fn failing_contract() -> Box> { - let contract = ContractWrapper::new(Self::execute, Self::failing_instantiate, Self::query); - Box::new(contract) - } -} diff --git a/contracts/poap/.cargo/config b/contracts/poap/.cargo/config index 40f77659..11e7d64c 100644 --- a/contracts/poap/.cargo/config +++ b/contracts/poap/.cargo/config @@ -2,4 +2,4 @@ wasm = "build --release --target wasm32-unknown-unknown" unit-test = "test --lib" schema = "run --example schema" -optimize = "run-script optimize" +optimize = "run-script optimize" \ No newline at end of file diff --git a/contracts/poap/.gitignore b/contracts/poap/.gitignore index 889be151..3d1ac43b 100644 --- a/contracts/poap/.gitignore +++ b/contracts/poap/.gitignore @@ -10,3 +10,9 @@ # macOS .DS_Store +# IDEs +*.iml +.idea + +# Raw schemas +schema/raw diff --git a/contracts/poap/Cargo.toml b/contracts/poap/Cargo.toml index 4d207e50..629aec0e 100644 --- a/contracts/poap/Cargo.toml +++ b/contracts/poap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "poap" -version = "0.1.0" +version = "2.0.0" authors = ["Manuel "] edition = "2021" @@ -18,14 +18,13 @@ crate-type = ["cdylib", "rlib"] [features] # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports library = [] [package.metadata.scripts] -optimize = """docker run --rm -v "$(pwd)"/../..:/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ +optimize = """docker run --rm -v "$(pwd)/../..":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/contracts/poap/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.12.6 ./contracts/poap + cosmwasm/rust-optimizer:0.13.0 ./contracts/poap """ [dependencies] @@ -37,14 +36,10 @@ cw-utils.workspace = true schemars.workspace = true serde = { workspace = true, default-features = false, features = ["derive"] } thiserror.workspace = true -desmos-bindings = { workspace = true, default-features = false, features = ["query", "msg"]} -cw721-base = { git = "https://github.com/desmos-labs/cw-nfts", features = ["library"], branch = "paul/update-custom-msg-query" } -url = "2.4.0" -cw721-poap = { path = "../cw721-poap", version = "0.1.0", features = ["library"]} -cw721 = { git = "https://github.com/desmos-labs/cw-nfts", branch = "paul/update-custom-msg-query" } cosmwasm-schema.workspace = true +cw721 = { version = "0.18.0" } +cw721-base = { version = "0.18.0", features = ["library"] } +cw-ownable = { version = "0.5.1" } [dev-dependencies] cw-multi-test.workspace = true -desmos-bindings = { workspace = true, default-features = false, features = ["mocks"]} - diff --git a/contracts/poap/README.md b/contracts/poap/README.md index b388d36b..890b453b 100644 --- a/contracts/poap/README.md +++ b/contracts/poap/README.md @@ -1,267 +1,235 @@ -# POAP contract +# POAP Contract -Contract that allows users who has a Desmos profile to mint POAP nft via cw721-poap contract. -To easily interact with the contract you can use the `poap` script available [here](https://github.com/desmos-labs/contract-utils/tree/main/utils), -otherwise you can take a look at the supported messages in the following sections. +Contract that allows users to mint POAP nft. -## Instantiate Message -Allows to initialize the contract. This message has the following parameters: -* `admin`: Address of who will have the right to administer the contract; -* `minter`: Address of who can mint tokens to other users; -* `cw721_code_id`: Id of the CW721 contract to initialize together with this contract; -* `cw721_instantiate_msg`: Initialization [message](../cw721-poap/README.md#instantiate_message) that will be sent to the CW721 contract; -* `event_info`: Information about the event which is defined [here](#EventInfo). +## How to build -Here an example message to instantiate the contract: -```json -{ - "admin": "desmos1......", - "minter": "desmos1......", - "cw721_code_id": "1", - "cw721_instantiate_msg": { - "name": "poap_nft", - "symbol": "poap", - "minter": "contract_address" - }, - "event_info": { - "creator": "desmos1......", - "start_time": "2022-12-31T10:00:00Z", - "end_time": "2022-12-31T19:00:00Z", - "per_address_limit": 1, - "poap_uri": "ipfs://poap.info" - } -} +Before build the contract make sure you have: +1. Docker installed on your system; +2. The cargo `cargo-run-script` binary. +This binary can be installed with the `cargo install cargo-run-script` command. + +To build the contract from withing the contract directory run: +```shell +cargo optimize ``` -### EventInfo -Represents the information of the event. This structure has the following parameters: -* `creator`: User that created the event; -* `start_time`: Time at which the event begins in RFC3339 format (2022-12-31T10:00:00Z); -* `end_time`: Time at which the event ends in RFC3339 format (2022-12-31T10:00:00Z); -* `per_address_limit`: Max amount of poap that a single user can mint; -* `poap_uri`: Identifies a valid IPFS URI corresponding to where the assets and metadata of the POAPs are stored. +This will build the contract and store the compiled wasm code in the `artifacts` directory +located in the workspace root. -## Execute Messages +## Instantiate Message -### EnableMint -Allows the contract's admin to enable the [Mint](#Mint). +Allows to initialize the contract. This message has the following parameters: +* `name`: Name of the POAP contract; +* `symbol`: Symbol of the POAP contract; +* `metadata_uri`: The URI where users can view the associated metadata for the POAPs, ideally following the ERC-721 metadata scheme in a JSON file; +* `admin`: Who controls the contract. If not set will be used the address of who is instantiating the contract; +* `minter`: Optional address that is allowed to mint tokens on behalf of other users; +* `is_transferable`: Specifies whether each POAP can be transferred from one user to another; +* `is_mintable`: Indicates whether users can mint the POAPs; +* `mint_start_time`: Identifies the timestamp at which the minting of the POAP will be enabled. If not set, the minting is always enabled; +* `mint_end_time`: Identifies the timestamp at which the minting of the POAP will be disabled. If not set, the minting will never end. -Here an example message to enable mint: +Here an example message to instantiate the contract: ```json { - "enable_mint": {} + "name": "poap-nft", + "symbol": "poap", + "metadata_uri": "ipfs://poap_metadata", + "admin": "desmos1......", + "minter": "desmos1......", + "is_transferable": true, + "is_mintable": true } ``` -### DisableMint -Allows the contract's admin to disable the [Mint](#Mint). +## Execute messages -Here an example message to disable mint: -```json -{ - "disable_mint": {} -} -``` +This contract extends the `cw721-base` contract and so inherit all the `cw721`. +You can take a look [here](https://github.com/CosmWasm/cw-nfts/blob/main/packages/cw721/README.md#messages) to see the `cw721` messages. ### Mint -Allows users to mint a POAP token in the event period if the contract enables mint. + +Mint a new POAP for the caller. This message has the following parameters: +* `extension`: The POAP extension. Here an example message to mint a POAP: ```json { - "mint": {} + "mint": {} } ``` ### MintTo -Allows the minter to mint a POAP token to a recipient in the event period if the contract enables mint. This message has the following parameter: -* `recipient`: Address who will receive the minted token. -Here an example message to mint a POAP to a user: +Mint a new POAP for the provided users, can only be called from the contract admin or minter. +This message have the following parameters: +* `users`: List of users for whom the POAP will be mined; +* `extension`: The POAP extension. + +Here an example message to mint a POAP to two users: ```json { - "mint_to": { - "recipient": "desmos1......" - } + "mint_to": { + "users": [ + "desmos1....", + "desmos1..." + ] + } } ``` -### UpdateEventInfo -Allows the contract admin to update the event info. This message has the following parameters: -* `start_time`: New start time which will be updated; -* `end_time`: New end time which will be updated. +### Burn + +Burn a POAP the sender has access to. +This message have the following parameters: +* `token_id`: Id of the POAP that will be burned. -Here an example message to update the event information: +Here an example message to burn a POAP: ```json { - "update_event_info": { - "start_time": "2022-12-31T10:00:00Z", - "end_time": "2022-12-31T19:00:00Z" - } + "burn": { + "token_id": "1" + } } ``` -### UpdateAdmin -Allows the contract admin to update the contract admin. This message has the following parameter: -* `new_admin`: Address to be the new admin that controls this contract. +### UpdateMinter -Here an example message to update the contract admin: +Allow to update the user with the mint permissions, can only be called from the contract admin. +This message have the following parameters: +* `minter`: Address of the new minter. + +Here an example message to update the contract minter: ```json { - "update_admin": { - "new_admin": "desmos1......" - } + "update_minter": { + "minter": "desmos1..." + } } ``` -### UpdateMinter -Allows the contract admin to update the contract minter. This message has the following parameter: -* `new_minter`: Address to be the new minter that has permission to mint tokens to other users. +### SetMintable -Here an example message to update the contract minter: +Sets if the users can mint their POAP, can only be called from the contract admin. +This message have the following parameters: +* `mintable`: Boolean value that determines whether users can mint a POAP. + +Here an example message to update the POAP mintability: ```json { - "update_minter": { - "new_minter": "desmos1......" - } + "set_mintable": { + "mintable": false + } } ``` -## Query Messages +### SetTransferable -### Config -Allows to query the config of the contract. +Sets if the users can transfer their POAP, can only be called from the contract admin. +This message have the following parameters: +* `transferable`: Boolean value that determines whether users transfer their POAP. -Here an example message to query the config: +Here an example message to update the POAP transferability: ```json { - "config": {} + "set_transferable": { + "transferable": false + } } ``` -Response: +### SetMintStartEndTime + +Sets the time period of when the POAP can be minted from the users, can only be called from the contract admin. +This message have the following parameters: +* `start_time`: Identifies the timestamp at which the minting of the POAP will be enabled in nanoseconds since 1970-01-01T00:00:00Z. If not set, the minting is always enabled; +* `end_time`: Identifies the timestamp at which the minting of the POAP will be disabled in nanoseconds since 1970-01-01T00:00:00Z. If not set, the minting will never end. + +Here an example message that allow the POAP to be minted from the 2023-08-01T00:00:00Z to 2023-08-07T00:00:00Z: ```json { - "admin": "desmos1......", - "minter": "desmos1......", - "mint_enabled": true, - "per_address_limit": 1, - "cw721_code_id": "1", - "cw721_address": "desmos1......" + "set_transferable": { + "start_time": "1690848000000000000", + "end_time": "1691366400000000000" + } } ``` -### EventInfo -Allows to query the information of the event. +## Query messages + +This contract extends the `cw721-base` contract and so inherit all the `cw721`. +You can take a look [here](https://github.com/CosmWasm/cw-nfts/blob/main/packages/cw721/README.md#queries) to see the `cw721` messages. + +### Minter -Here an example message to query the event info: +Allows to query the contract minter. + +Here an example message to query the contract minter: ```json { - "event_info": {} + "minter": {} } ``` Response: ```json { - "creator": "desmos1......", - "start_time": "2022-12-31T10:00:00Z", - "end_time": "2022-12-31T19:00:00Z", - "poap_uri": "ipfs://poap.info" + "minter": "desmos1..." } ``` -### MintedAmount -Allows to query the POAP minted amount from a user. This message has the following parameter: -* `user`: Address of the target user. +### IsMintable + +Allows to query if the POAP can be minted. -Here an example message to query the event info: +Here an example message to query the POAP mintability: ```json { - "minted_amount": { - "user": "desmos1......" - } + "is_mintable": {} } ``` Response: ```json { - "user": "desmos1......", - "amount": 1, + "mintable": true } ``` -### AllNftInfo -Returns the all the information of the token. This message has the following parameters: -* `token_id`: Id of the target token; -* `include_expired`: Trigger to filter out expired approvals, unset or false will exclude expired approvals. +### IsTransferable -Here an example meesage to query all the info of the given token: +Allows to query if the POAP can be transferred. + +Here an example message to query the POAP mintability: ```json { - "all_nft_info": { - "token_id": "1", - "include_expired": true - } + "is_transferable": {} } ``` Response: ```json { - "access": { - "owner": "desmos1......", - "approvals": [ - { - "spender": "desmos1......", - "expiration": { - "at_height": 1000 - } - }, - { - "spender": "desmos1......", - "expiration": { - "at_time": "2022-01-01T00:00:00Z" - } - }, - { - "spender": "desmos1......", - "expiration": { - "never": {} - } - }, - ], - }, - "info": { - "token_uri": "ipfs://token.erc721.metadata", - "extension": { - "claimer": "desmos1......" - } - } + "transferable": true } ``` -### Tokens -Returns all tokens owned by the given address. This message has the following parameters: -* `owner`: Target address owned tokens to be queried; -* `start_after`: Position in token id where tokens start after; -* `limit`: Limitation to list the number of tokens, if unset would be 10 and the maximum is 100. +### MintStartEndTime + +Allows to query the POAP mint period. -Here an example meesage to query all the tokens owned by the given address: +Here an example message to query the POAP mint period: ```json { - "tokens": { - "owner": "desmos1......", - "start_after": "1", - "limit": 3 - } + "mint_start_end_time": {} } ``` Response: ```json { - "tokens": ["2", "3", "4"] + "start_time": "1690848000000000000", + "end_time": "1691366400000000000" } ``` \ No newline at end of file diff --git a/contracts/poap/examples/schema.rs b/contracts/poap/examples/schema.rs index 2ed0bddc..396da282 100644 --- a/contracts/poap/examples/schema.rs +++ b/contracts/poap/examples/schema.rs @@ -1,10 +1,12 @@ use cosmwasm_schema::write_api; -use poap::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use cosmwasm_std::Empty; + +use poap::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, + execute: ExecuteMsg, + query: QueryMsg, } } diff --git a/contracts/poap/schema/poap-v2.json b/contracts/poap/schema/poap-v2.json new file mode 100644 index 00000000..01da5985 --- /dev/null +++ b/contracts/poap/schema/poap-v2.json @@ -0,0 +1,1968 @@ +{ + "contract_name": "poap-v2", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "is_mintable", + "is_transferable", + "metadata_uri", + "name", + "symbol" + ], + "properties": { + "admin": { + "description": "Who controls the contract. If None will be used the address of who is instantiating the contract.", + "type": [ + "string", + "null" + ] + }, + "is_mintable": { + "description": "Indicates whether users can mint the POAPs.", + "type": "boolean" + }, + "is_transferable": { + "description": "Specifies whether each POAP can be transferred from one user to another.", + "type": "boolean" + }, + "metadata_uri": { + "description": "The URI where users can view the associated metadata for the POAPs, ideally following the ERC-721 metadata scheme in a JSON file.", + "type": "string" + }, + "mint_end_time": { + "description": "Identifies the timestamp at which the minting of the POAP will be disabled. If not set, the minting will never end.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "mint_start_time": { + "description": "Identifies the timestamp at which the minting of the POAP will be enabled. If not set, the minting is always enabled.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "minter": { + "description": "Additional address that is allowed to mint tokens on behalf of other users. If None will be used the address of who is instantiating the contract.", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "Name of the POAP contract.", + "type": "string" + }, + "symbol": { + "description": "Symbol of the POAP contract.", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This is like Cw721ExecuteMsg but we add a Mint command for an owner to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", + "oneOf": [ + { + "description": "Transfer is a base message to move a token to another account without triggering actions.", + "type": "object", + "required": [ + "transfer_nft" + ], + "properties": { + "transfer_nft": { + "type": "object", + "required": [ + "recipient", + "token_id" + ], + "properties": { + "recipient": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", + "type": "object", + "required": [ + "send_nft" + ], + "properties": { + "send_nft": { + "type": "object", + "required": [ + "contract", + "msg", + "token_id" + ], + "properties": { + "contract": { + "type": "string" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit.", + "type": "object", + "required": [ + "approve" + ], + "properties": { + "approve": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted Approval", + "type": "object", + "required": [ + "revoke" + ], + "properties": { + "revoke": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit.", + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted ApproveAll permission.", + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Mint a new POAP for the caller.", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Mint a new POAP for the provided users, can only be called from the contract minter.", + "type": "object", + "required": [ + "mint_to" + ], + "properties": { + "mint_to": { + "type": "object", + "required": [ + "extension", + "users" + ], + "properties": { + "extension": { + "$ref": "#/definitions/Empty" + }, + "users": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Burn an NFT the sender has access to.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allow to update the user with the mint permissions, can only be called from the contract admin.", + "type": "object", + "required": [ + "update_minter" + ], + "properties": { + "update_minter": { + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets if the users can mint their POAP, can only be called from the contract admin.", + "type": "object", + "required": [ + "set_mintable" + ], + "properties": { + "set_mintable": { + "type": "object", + "required": [ + "mintable" + ], + "properties": { + "mintable": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets if the users can transfer their POAP, can only be called from the contract admin.", + "type": "object", + "required": [ + "set_transferable" + ], + "properties": { + "set_transferable": { + "type": "object", + "required": [ + "transferable" + ], + "properties": { + "transferable": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets the time period of when the POAP can be minted from the users, can only be called from the contract admin.", + "type": "object", + "required": [ + "set_mint_start_end_time" + ], + "properties": { + "set_mint_start_end_time": { + "type": "object", + "properties": { + "end_time": { + "description": "Identifies the timestamp at which the minting of the POAP will be disabled. If None, the minting will never end.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "start_time": { + "description": "Identifies the timestamp at which the minting of the POAP will be enabled. If None, the minting is always enabled.", + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Extension msg.", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update the contract's ownership. The `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", + "type": "object", + "required": [ + "update_ownership" + ], + "properties": { + "update_ownership": { + "$ref": "#/definitions/Action" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Action": { + "description": "Actions that can be taken to alter the contract's ownership", + "oneOf": [ + { + "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", + "type": "object", + "required": [ + "transfer_ownership" + ], + "properties": { + "transfer_ownership": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "expiry": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", + "type": "string", + "enum": [ + "accept_ownership" + ] + }, + { + "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", + "type": "string", + "enum": [ + "renounce_ownership" + ] + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Return the owner of the given token, error if token does not exist.", + "type": "object", + "required": [ + "owner_of" + ], + "properties": { + "owner_of": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return operator that can access all of the owner's tokens.", + "type": "object", + "required": [ + "approval" + ], + "properties": { + "approval": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return approvals that a token has.", + "type": "object", + "required": [ + "approvals" + ], + "properties": { + "approvals": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return approval of a given operator for all tokens of an owner, error if not set.", + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "object", + "required": [ + "operator", + "owner" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "operator": { + "type": "string" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens.", + "type": "object", + "required": [ + "all_operators" + ], + "properties": { + "all_operators": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired items, you must set to true to see them.", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Total number of tokens issued.", + "type": "object", + "required": [ + "num_tokens" + ], + "properties": { + "num_tokens": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns top-level metadata about the contract.", + "type": "object", + "required": [ + "contract_info" + ], + "properties": { + "contract_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract.", + "type": "object", + "required": [ + "nft_info" + ], + "properties": { + "nft_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients.", + "type": "object", + "required": [ + "all_nft_info" + ], + "properties": { + "all_nft_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them.", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset.", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract.", + "type": "object", + "required": [ + "all_tokens" + ], + "properties": { + "all_tokens": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the minter.", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns if the POAP minting is enabled.", + "type": "object", + "required": [ + "is_mintable" + ], + "properties": { + "is_mintable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns if the POAP is transferable.", + "type": "object", + "required": [ + "is_transferable" + ], + "properties": { + "is_transferable": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the mint start and end time.", + "type": "object", + "required": [ + "mint_start_end_time" + ], + "properties": { + "mint_start_end_time": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Extension query.", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Query the contract's ownership information", + "type": "object", + "required": [ + "ownership" + ], + "properties": { + "ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + } + } + }, + "migrate": null, + "sudo": null, + "responses": { + "all_nft_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllNftInfoResponse_for_Empty", + "type": "object", + "required": [ + "access", + "info" + ], + "properties": { + "access": { + "description": "Who can transfer the token", + "allOf": [ + { + "$ref": "#/definitions/OwnerOfResponse" + } + ] + }, + "info": { + "description": "Data on the token itself,", + "allOf": [ + { + "$ref": "#/definitions/NftInfoResponse_for_Empty" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + }, + "additionalProperties": false + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "NftInfoResponse_for_Empty": { + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "description": "You can add any custom metadata here when you extend cw721-base", + "allOf": [ + { + "$ref": "#/definitions/Empty" + } + ] + }, + "token_uri": { + "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "OwnerOfResponse": { + "type": "object", + "required": [ + "approvals", + "owner" + ], + "properties": { + "approvals": { + "description": "If set this address is approved to transfer/send the token as well", + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + }, + "owner": { + "description": "Owner of the token", + "type": "string" + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "all_operators": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OperatorsResponse", + "type": "object", + "required": [ + "operators" + ], + "properties": { + "operators": { + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + } + }, + "additionalProperties": false, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + }, + "additionalProperties": false + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "all_tokens": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokensResponse", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "approval": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovalResponse", + "type": "object", + "required": [ + "approval" + ], + "properties": { + "approval": { + "$ref": "#/definitions/Approval" + } + }, + "additionalProperties": false, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + }, + "additionalProperties": false + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "approvals": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovalsResponse", + "type": "object", + "required": [ + "approvals" + ], + "properties": { + "approvals": { + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + } + }, + "additionalProperties": false, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + }, + "additionalProperties": false + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "contract_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContractInfoResponse", + "type": "object", + "required": [ + "name", + "symbol" + ], + "properties": { + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + } + }, + "additionalProperties": false + }, + "extension": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Null", + "type": "null" + }, + "is_mintable": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsMintableResponse", + "description": "Response to [`QueryMsg::IsMintable`].", + "type": "object", + "required": [ + "is_mintable" + ], + "properties": { + "is_mintable": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "is_transferable": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsTransferableResponse", + "description": "Response to [`QueryMsg::IsTransferable`].", + "type": "object", + "required": [ + "is_transferable" + ], + "properties": { + "is_transferable": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "mint_start_end_time": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MintStartEndTimeResponse", + "description": "Response to [`QueryMsg::MintStartEndTime`].", + "type": "object", + "properties": { + "end_time": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + }, + "start_time": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "minter": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MinterResponse", + "description": "Shows who can mint these tokens", + "type": "object", + "properties": { + "minter": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "nft_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NftInfoResponse_for_Empty", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "description": "You can add any custom metadata here when you extend cw721-base", + "allOf": [ + { + "$ref": "#/definitions/Empty" + } + ] + }, + "token_uri": { + "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + } + } + }, + "num_tokens": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NumTokensResponse", + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "operator": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OperatorResponse", + "type": "object", + "required": [ + "approval" + ], + "properties": { + "approval": { + "$ref": "#/definitions/Approval" + } + }, + "additionalProperties": false, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + }, + "additionalProperties": false + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "owner_of": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OwnerOfResponse", + "type": "object", + "required": [ + "approvals", + "owner" + ], + "properties": { + "approvals": { + "description": "If set this address is approved to transfer/send the token as well", + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + }, + "owner": { + "description": "Owner of the token", + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + }, + "additionalProperties": false + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "ownership": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Ownership_for_String", + "description": "The contract's ownership info", + "type": "object", + "properties": { + "owner": { + "description": "The contract's current owner. `None` if the ownership has been renounced.", + "type": [ + "string", + "null" + ] + }, + "pending_expiry": { + "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "pending_owner": { + "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false, + "definitions": { + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "tokens": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokensResponse", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/poap/schema/poap.json b/contracts/poap/schema/poap.json deleted file mode 100644 index 4263c4a1..00000000 --- a/contracts/poap/schema/poap.json +++ /dev/null @@ -1,763 +0,0 @@ -{ - "contract_name": "poap", - "contract_version": "0.1.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "admin", - "cw721_code_id", - "cw721_instantiate_msg", - "event_info", - "minter" - ], - "properties": { - "admin": { - "description": "Address of who will have the right to administer the contract.", - "type": "string" - }, - "cw721_code_id": { - "description": "Id of the CW721 contract to initialize together with this contract.", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "cw721_instantiate_msg": { - "description": "Initialization message that will be sent to the CW721 contract.", - "allOf": [ - { - "$ref": "#/definitions/InstantiateMsg" - } - ] - }, - "event_info": { - "description": "Information about the event.", - "allOf": [ - { - "$ref": "#/definitions/EventInfo" - } - ] - }, - "minter": { - "description": "Address of who can call the [`ExecuteMsg::MintTo`] other then the admin.", - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "EventInfo": { - "type": "object", - "required": [ - "creator", - "end_time", - "per_address_limit", - "poap_uri", - "start_time" - ], - "properties": { - "creator": { - "description": "User that created the event.", - "type": "string" - }, - "end_time": { - "description": "Time at which the event ends.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - }, - "per_address_limit": { - "description": "Max amount of poap that a single user can mint.", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "poap_uri": { - "description": "Identifies a valid IPFS URI corresponding to where the assets and metadata of the POAPs are stored.", - "type": "string" - }, - "start_time": { - "description": "Time at which the event begins.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - } - }, - "additionalProperties": false - }, - "InstantiateMsg": { - "type": "object", - "required": [ - "minter", - "name", - "symbol" - ], - "properties": { - "minter": { - "description": "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", - "type": "string" - }, - "name": { - "description": "Name of the NFT contract", - "type": "string" - }, - "symbol": { - "description": "Symbol of the NFT contract", - "type": "string" - } - }, - "additionalProperties": false - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Allows the contract's admin to enable the [`ExecuteMsg::Mint`].", - "type": "object", - "required": [ - "enable_mint" - ], - "properties": { - "enable_mint": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows the contract's admin to disable the [`ExecuteMsg::Mint`].", - "type": "object", - "required": [ - "disable_mint" - ], - "properties": { - "disable_mint": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "If the mint is enabled, allow the user to mint the poap by themself. It's disabled before the start of the event and after the event's end.", - "type": "object", - "required": [ - "mint" - ], - "properties": { - "mint": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows the contract's admin or the minter to mint a POAP for a specific recipient. It's disabled before the start of the event and after the event's end.", - "type": "object", - "required": [ - "mint_to" - ], - "properties": { - "mint_to": { - "type": "object", - "required": [ - "recipient" - ], - "properties": { - "recipient": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Message that allows the event's creator to change the time frame of the event if it's not started or finished.", - "type": "object", - "required": [ - "update_event_info" - ], - "properties": { - "update_event_info": { - "type": "object", - "required": [ - "end_time", - "start_time" - ], - "properties": { - "end_time": { - "$ref": "#/definitions/Timestamp" - }, - "start_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows the contract's admin to transfer the admin rights to another user.", - "type": "object", - "required": [ - "update_admin" - ], - "properties": { - "update_admin": { - "type": "object", - "required": [ - "new_admin" - ], - "properties": { - "new_admin": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows the contract's admin to transfer the minting rights to another user.", - "type": "object", - "required": [ - "update_minter" - ], - "properties": { - "update_minter": { - "type": "object", - "required": [ - "new_minter" - ], - "properties": { - "new_minter": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "Returns the configuration info as a [`QueryConfigResponse`].", - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the event info as a [`QueryEventInfoResponse`].", - "type": "object", - "required": [ - "event_info" - ], - "properties": { - "event_info": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the amount of poaps minted from `user` as [`QueryMintedAmountResponse`].", - "type": "object", - "required": [ - "minted_amount" - ], - "properties": { - "minted_amount": { - "type": "object", - "required": [ - "user" - ], - "properties": { - "user": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns the nft info with approvals from cw721 contract as a [`AllNftInfoResponse`]", - "type": "object", - "required": [ - "all_nft_info" - ], - "properties": { - "all_nft_info": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_expired": { - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Returns all the tokens ids owned by the given owner from cw721 contract as a [`TokensResponse`]", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "migrate": null, - "sudo": null, - "responses": { - "all_nft_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllNftInfoResponse_for_Metadata", - "type": "object", - "required": [ - "access", - "info" - ], - "properties": { - "access": { - "description": "Who can transfer the token", - "allOf": [ - { - "$ref": "#/definitions/OwnerOfResponse" - } - ] - }, - "info": { - "description": "Data on the token itself,", - "allOf": [ - { - "$ref": "#/definitions/NftInfoResponse_for_Metadata" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Metadata": { - "type": "object", - "required": [ - "claimer" - ], - "properties": { - "claimer": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - }, - "NftInfoResponse_for_Metadata": { - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "allOf": [ - { - "$ref": "#/definitions/Metadata" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "OwnerOfResponse": { - "type": "object", - "required": [ - "approvals", - "owner" - ], - "properties": { - "approvals": { - "description": "If set this address is approved to transfer/send the token as well", - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - }, - "owner": { - "description": "Owner of the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryConfigResponse", - "description": "Response to [`QueryMsg::Config`].", - "type": "object", - "required": [ - "admin", - "cw721_contract", - "cw721_contract_code", - "mint_enabled", - "minter", - "per_address_limit" - ], - "properties": { - "admin": { - "description": "Address of the contract administrator.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "cw721_contract": { - "description": "Address of the cw721 contract that this contract is using to mint the poaps.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "cw721_contract_code": { - "description": "Id of the cw721 contract that this contract has initialized.", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "mint_enabled": { - "description": "Tells if the users can execute the [`ExecuteMsg::Mint`].", - "type": "boolean" - }, - "minter": { - "description": "Address of the entity that is allowed to use [`ExecuteMsg::MintTo`].", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "per_address_limit": { - "description": "The maximus number of poap that an user can request.", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "event_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryEventInfoResponse", - "description": "Response to [`QueryMsg::EventInfo`].", - "type": "object", - "required": [ - "creator", - "end_time", - "poap_uri", - "start_time" - ], - "properties": { - "creator": { - "description": "Address of who created the event.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "end_time": { - "description": "Time at which the event ends.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - }, - "poap_uri": { - "description": "IPFS uri where the event's metadata are stored", - "type": "string" - }, - "start_time": { - "description": "Time at which the event starts.", - "allOf": [ - { - "$ref": "#/definitions/Timestamp" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "minted_amount": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMintedAmountResponse", - "description": "Response to [`QueryMsg::MintedAmount`].", - "type": "object", - "required": [ - "amount", - "user" - ], - "properties": { - "amount": { - "description": "Amount of poaps minted from the user.", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "user": { - "description": "Address for which the request was made.", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - } - } - }, - "tokens": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - } - } -} diff --git a/contracts/poap/src/contract.rs b/contracts/poap/src/contract.rs deleted file mode 100644 index 168e2aef..00000000 --- a/contracts/poap/src/contract.rs +++ /dev/null @@ -1,1421 +0,0 @@ -use crate::error::ContractError; -use crate::msg::{ - ExecuteMsg, InstantiateMsg, QueryConfigResponse, QueryEventInfoResponse, - QueryMintedAmountResponse, QueryMsg, -}; -use crate::state::{ - Config, EventInfo, CONFIG, CW721_ADDRESS, EVENT_INFO, MINTER_ADDRESS, NEXT_POAP_ID, -}; -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_binary, wasm_execute, wasm_instantiate, Addr, Binary, Deps, DepsMut, Empty, Env, - MessageInfo, Reply, Response, StdResult, SubMsg, Timestamp, -}; -use cw2::set_contract_version; -use cw721::{AllNftInfoResponse, TokensResponse}; -use cw721_base::{ - msg::ExecuteMsg as Cw721ExecuteMsg, InstantiateMsg as Cw721InstantiateMsg, MintMsg, -}; -use cw721_poap::{Metadata, QueryMsg as Cw721PoapQueryMsg}; -use cw_utils::parse_reply_instantiate_data; -use desmos_bindings::{msg::DesmosMsg, query::DesmosQuery}; -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:poap"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -// actions consts -const ACTION_ENABLE_MINT: &str = "enable_mint"; -const ACTION_DISABLE_MINT: &str = "disable_mint"; -const ACTION_MINT: &str = "mint"; -const ACTION_MINT_TO: &str = "mint_to"; -const ACTION_UPDATE_EVENT_INFO: &str = "update_event_info"; -const ACTION_UPDATE_ADMIN: &str = "update_admin"; -const ACTION_UPDATE_MINTER: &str = "update_minter"; -// response attributes -const ATTRIBUTE_ACTION: &str = "action"; -const ATTRIBUTE_SENDER: &str = "sender"; -const ATTRIBUTE_CREATOR: &str = "creator"; - -const INSTANTIATE_CW721_REPLY_ID: u64 = 1; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> Result, ContractError> { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - msg.validate()?; - - // Validate the admin address - let admin = deps.api.addr_validate(&msg.admin)?; - - // Validate the minter address - let minter = deps.api.addr_validate(&msg.minter)?; - - // Validate the creator address - let creator = deps.api.addr_validate(&msg.event_info.creator)?; - - // Check that the end time is in the future - if !msg.event_info.end_time.gt(&env.block.time) { - return Err(ContractError::EndTimeBeforeCurrentTime { - current_time: env.block.time, - end_time: msg.event_info.end_time, - }); - } - - // Check that the start time is in the future - if !msg.event_info.start_time.gt(&env.block.time) { - return Err(ContractError::StartTimeBeforeCurrentTime { - current_time: env.block.time, - start_time: msg.event_info.start_time, - }); - } - - let config = Config { - admin: admin.clone(), - minter: minter.clone(), - per_address_limit: msg.event_info.per_address_limit, - cw721_code_id: msg.cw721_code_id.u64(), - mint_enabled: false, - }; - // Save the received event info. - CONFIG.save(deps.storage, &config)?; - - let event_info = EventInfo { - creator: creator.clone(), - start_time: msg.event_info.start_time, - end_time: msg.event_info.end_time, - poap_uri: msg.event_info.poap_uri.clone(), - }; - // Save the event info - EVENT_INFO.save(deps.storage, &event_info)?; - - // Submessage to instantiate cw721 contract - let cw721_submessage = SubMsg::reply_on_success( - wasm_instantiate( - msg.cw721_code_id.into(), - &Cw721InstantiateMsg { - name: msg.cw721_instantiate_msg.name, - symbol: msg.cw721_instantiate_msg.symbol, - minter: env.contract.address.to_string(), - }, - info.funds, - "poap_cw721".to_string(), - )?, - INSTANTIATE_CW721_REPLY_ID, - ); - - Ok(Response::new() - .add_attribute(ATTRIBUTE_ACTION, "instantiate") - .add_attribute(ATTRIBUTE_CREATOR, creator) - .add_attribute("admin", admin) - .add_attribute("minter", minter) - .add_attribute("start_time", msg.event_info.start_time.to_string()) - .add_attribute("end_time", msg.event_info.end_time.to_string()) - .add_attribute( - "per_address_limit", - msg.event_info.per_address_limit.to_string(), - ) - .add_attribute("poap_uri", &msg.event_info.poap_uri) - .add_attribute("cw721_code_id", &msg.cw721_code_id.to_string()) - .add_submessage(cw721_submessage)) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result, ContractError> { - msg.validate()?; - - match msg { - ExecuteMsg::EnableMint {} => execute_set_mint_enabled(deps, info, true), - ExecuteMsg::DisableMint {} => execute_set_mint_enabled(deps, info, false), - ExecuteMsg::Mint {} => { - let recipient_addr = info.sender.clone(); - execute_mint(deps, env, info, ACTION_MINT, recipient_addr, false, false) - } - ExecuteMsg::MintTo { recipient } => { - let recipient_addr = deps.api.addr_validate(&recipient)?; - execute_mint(deps, env, info, ACTION_MINT_TO, recipient_addr, true, true) - } - ExecuteMsg::UpdateEventInfo { - start_time, - end_time, - } => execute_update_event_info(deps, env, info, start_time, end_time), - ExecuteMsg::UpdateAdmin { new_admin } => execute_update_admin(deps, info, new_admin), - ExecuteMsg::UpdateMinter { new_minter } => execute_update_minter(deps, info, new_minter), - } -} - -fn execute_set_mint_enabled( - deps: DepsMut, - info: MessageInfo, - mint_enabled: bool, -) -> Result, ContractError> { - let mut config = CONFIG.load(deps.storage)?; - - // Check that the sender is the admin - if info.sender != config.admin { - return Err(ContractError::Unauthorized {}); - } - - config.mint_enabled = mint_enabled; - // Save the new configurations - CONFIG.save(deps.storage, &config)?; - - let action = if mint_enabled { - ACTION_ENABLE_MINT - } else { - ACTION_DISABLE_MINT - }; - - Ok(Response::new() - .add_attribute(ATTRIBUTE_ACTION, action) - .add_attribute(ATTRIBUTE_SENDER, info.sender)) -} - -fn execute_mint( - deps: DepsMut, - env: Env, - info: MessageInfo, - action: &str, - recipient_addr: Addr, - bypass_mint_enable: bool, - check_authorized_to_mint: bool, -) -> Result, ContractError> { - let config = CONFIG.load(deps.storage)?; - let event_info = EVENT_INFO.load(deps.storage)?; - - // Check if the event is started - if !event_info.is_started(&env.block.time) { - return Err(ContractError::EventNotStarted { - current_time: env.block.time, - start_time: event_info.start_time, - }); - } - - // Check if the event is ended - if event_info.is_ended(&env.block.time) { - return Err(ContractError::EventTerminated { - current_time: env.block.time, - end_time: event_info.end_time, - }); - } - - // Check if the mint is enabled - if !bypass_mint_enable && !config.mint_enabled { - return Err(ContractError::MintDisabled {}); - } - - // Check if who is performing the action is the minter - if check_authorized_to_mint && info.sender != config.minter && info.sender != config.admin { - return Err(ContractError::Unauthorized {}); - } - - // Check per address limit - let recipient_mint_count = (MINTER_ADDRESS - .key(recipient_addr.clone()) - .may_load(deps.storage)?) - .unwrap_or(0); - - if recipient_mint_count >= config.per_address_limit { - return Err(ContractError::MaxPerAddressLimitExceeded { - recipient_addr: recipient_addr.to_string(), - }); - } - - // Get the next poap id - let poap_id = NEXT_POAP_ID.may_load(deps.storage)?.unwrap_or(1); - - // Create the cw721 message to send to mint the poap - let mint_msg = Cw721ExecuteMsg::::Mint(MintMsg:: { - token_id: poap_id.to_string(), - owner: recipient_addr.to_string(), - token_uri: Some(event_info.poap_uri), - extension: Metadata { - claimer: recipient_addr.clone(), - }, - }); - - let cw721_address = CW721_ADDRESS.load(deps.storage)?; - let wasm_execute_mint_msg = wasm_execute(cw721_address, &mint_msg, vec![])?; - - // Update the next poap id state - let new_poap_id = poap_id + 1; - NEXT_POAP_ID.save(deps.storage, &new_poap_id)?; - // Save the new mint count for the sender's address - let new_recipient_mint_count = recipient_mint_count + 1; - MINTER_ADDRESS.save( - deps.storage, - recipient_addr.clone(), - &new_recipient_mint_count, - )?; - - Ok(Response::new() - .add_attribute(ATTRIBUTE_ACTION, action) - .add_attribute(ATTRIBUTE_SENDER, info.sender) - .add_attribute("recipient", recipient_addr.to_string()) - .add_attribute("poap_id", poap_id.to_string()) - .add_message(wasm_execute_mint_msg)) -} - -fn execute_update_event_info( - deps: DepsMut, - env: Env, - info: MessageInfo, - start_time: Timestamp, - end_time: Timestamp, -) -> Result, ContractError> { - let mut event_info = EVENT_INFO.load(deps.storage)?; - - // Check that is the event creator that is changing the event time frame - if event_info.creator != info.sender { - return Err(ContractError::Unauthorized {}); - } - - // Check that the event is not ended - if event_info.is_ended(&env.block.time) { - return Err(ContractError::EventTerminated { - current_time: env.block.time, - end_time: event_info.end_time, - }); - } - - // Check that the event is not started - if event_info.is_started(&env.block.time) { - return Err(ContractError::EventStarted { - current_time: env.block.time, - start_time: event_info.start_time, - }); - } - - // Check that the start time is not already passed - if env.block.time.ge(&start_time) { - return Err(ContractError::StartTimeBeforeCurrentTime { - current_time: env.block.time, - start_time, - }); - } - - // Update the event info - event_info.start_time = start_time; - event_info.end_time = end_time; - EVENT_INFO.save(deps.storage, &event_info)?; - - Ok(Response::new() - .add_attribute(ATTRIBUTE_ACTION, ACTION_UPDATE_EVENT_INFO) - .add_attribute(ATTRIBUTE_SENDER, &info.sender) - .add_attribute("new_start_time", start_time.to_string()) - .add_attribute("new_end_time", end_time.to_string())) -} - -fn execute_update_admin( - deps: DepsMut, - info: MessageInfo, - admin_address: String, -) -> Result, ContractError> { - let mut config = CONFIG.load(deps.storage)?; - - // Check that the sender is the admin - if info.sender != config.admin { - return Err(ContractError::Unauthorized {}); - } - - // Update the admin address. - let new_admin = deps.api.addr_validate(&admin_address)?; - config.admin = new_admin; - CONFIG.save(deps.storage, &config)?; - - Ok(Response::new() - .add_attribute(ATTRIBUTE_ACTION, ACTION_UPDATE_ADMIN) - .add_attribute("new_admin", &admin_address)) -} - -fn execute_update_minter( - deps: DepsMut, - info: MessageInfo, - minter_address: String, -) -> Result, ContractError> { - let mut config = CONFIG.load(deps.storage)?; - - // Check that the sender is the admin - if info.sender != config.admin { - return Err(ContractError::Unauthorized {}); - } - - // Update the minter address. - let new_minter = deps.api.addr_validate(&minter_address)?; - config.minter = new_minter; - CONFIG.save(deps.storage, &config)?; - - Ok(Response::new() - .add_attribute(ATTRIBUTE_ACTION, ACTION_UPDATE_MINTER) - .add_attribute("new_minter", &minter_address)) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::EventInfo {} => to_binary(&query_event_info(deps)?), - QueryMsg::MintedAmount { user } => to_binary(&query_minted_amount(deps, user)?), - QueryMsg::AllNftInfo { - token_id, - include_expired, - } => to_binary(&query_all_nft_info(deps, token_id, include_expired)?), - QueryMsg::Tokens { - owner, - start_after, - limit, - } => to_binary(&query_tokens(deps, owner, start_after, limit)?), - } -} - -fn query_config(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let cw721_address = CW721_ADDRESS.load(deps.storage)?; - - Ok(QueryConfigResponse { - admin: config.admin, - minter: config.minter, - mint_enabled: config.mint_enabled, - per_address_limit: config.per_address_limit, - cw721_contract_code: config.cw721_code_id.into(), - cw721_contract: cw721_address, - }) -} - -fn query_event_info(deps: Deps) -> StdResult { - let event_info = EVENT_INFO.load(deps.storage)?; - - Ok(QueryEventInfoResponse { - creator: event_info.creator, - start_time: event_info.start_time, - end_time: event_info.end_time, - poap_uri: event_info.poap_uri, - }) -} - -fn query_minted_amount( - deps: Deps, - user: String, -) -> StdResult { - let user_addr = deps.api.addr_validate(&user)?; - - let minted_amount = MINTER_ADDRESS - .may_load(deps.storage, user_addr.clone())? - .unwrap_or(0); - - Ok(QueryMintedAmountResponse { - user: user_addr, - amount: minted_amount, - }) -} - -fn query_all_nft_info( - deps: Deps, - token_id: String, - include_expired: Option, -) -> StdResult> { - let cw721_address = CW721_ADDRESS.load(deps.storage)?; - deps.querier.query_wasm_smart( - cw721_address, - &Cw721PoapQueryMsg::AllNftInfo { - token_id, - include_expired, - }, - ) -} - -fn query_tokens( - deps: Deps, - owner: String, - start_after: Option, - limit: Option, -) -> StdResult { - let cw721_address = CW721_ADDRESS.load(deps.storage)?; - deps.querier.query_wasm_smart( - cw721_address, - &Cw721PoapQueryMsg::Tokens { - owner, - start_after, - limit, - }, - ) -} - -// Reply callback triggered from cw721 contract instantiation -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply( - deps: DepsMut, - _env: Env, - msg: Reply, -) -> Result, ContractError> { - if msg.id != INSTANTIATE_CW721_REPLY_ID { - return Err(ContractError::InvalidReplyID {}); - } - - let reply = parse_reply_instantiate_data(msg); - match reply { - Ok(res) => { - CW721_ADDRESS.save(deps.storage, &Addr::unchecked(res.contract_address))?; - Ok(Response::default().add_attribute(ATTRIBUTE_ACTION, "instantiate_cw721_reply")) - } - Err(_) => Err(ContractError::InstantiateCw721Error {}), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{ - get_valid_init_msg, EVENT_END_SECONDS, EVENT_START_SECONDS, INITIAL_BLOCK_TIME_SECONDS, - }; - use crate::ContractError::Unauthorized; - use cosmwasm_std::testing::{mock_env, mock_info}; - use cosmwasm_std::{DepsMut, Timestamp}; - use desmos_bindings::mocks::mock_queriers::mock_desmos_dependencies; - - const CREATOR: &str = "creator"; - const ADMIN: &str = "admin"; - const MINTER: &str = "minter"; - const USER: &str = "user"; - const FAKE_CW721_ADDRESS: &str = "cw721-contract"; - - fn do_instantiate(deps: DepsMut) { - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - - // Change block time to event start. - env.block.time = Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS); - - // Since replay is not called, fake the stored cw721 contract address. - CW721_ADDRESS - .save(deps.storage, &Addr::unchecked(FAKE_CW721_ADDRESS)) - .unwrap(); - - let msg = get_valid_init_msg(1); - assert!(instantiate(deps, env, info, msg).is_ok()); - } - - #[test] - fn instantiate_with_invalid_admin_addr_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - let mut init_msg = get_valid_init_msg(1); - init_msg.admin = "a".to_string(); - - let result = instantiate(deps.as_mut(), env, info, init_msg); - assert!(result.is_err()) - } - - #[test] - fn instantiate_with_invalid_minter_addr_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - let mut init_msg = get_valid_init_msg(1); - init_msg.minter = "a".to_string(); - - let result = instantiate(deps.as_mut(), env, info, init_msg); - assert!(result.is_err()) - } - - #[test] - fn instantiate_with_invalid_creator_addr_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - let mut init_msg = get_valid_init_msg(1); - init_msg.event_info.creator = "a".to_string(); - - let result = instantiate(deps.as_mut(), env, info, init_msg); - assert!(result.is_err()) - } - - #[test] - fn instantiate_with_event_start_before_current_time_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - let mut init_msg = get_valid_init_msg(1); - // Create a start time 200 seconds before the current block time - let start = &env.block.time.seconds() - 200; - init_msg.event_info.start_time = Timestamp::from_seconds(start); - init_msg.event_info.end_time = Timestamp::from_seconds(start + 600); - - let init_result = instantiate(deps.as_mut(), env.clone(), info, init_msg); - assert_eq!( - ContractError::StartTimeBeforeCurrentTime { - current_time: env.block.time, - start_time: Timestamp::from_seconds(start) - }, - init_result.unwrap_err() - ); - } - - #[test] - fn instantiate_with_event_start_equal_current_time_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - let mut init_msg = get_valid_init_msg(1); - - let start = env.block.time.nanos(); - init_msg.event_info.start_time = Timestamp::from_nanos(start); - init_msg.event_info.end_time = Timestamp::from_nanos(start + 600); - - let init_result = instantiate(deps.as_mut(), env, info, init_msg); - assert_eq!( - ContractError::StartTimeBeforeCurrentTime { - current_time: Timestamp::from_nanos(start), - start_time: Timestamp::from_nanos(start) - }, - init_result.unwrap_err() - ); - } - - #[test] - fn instantiate_with_event_end_before_current_time_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - let mut init_msg = get_valid_init_msg(1); - - // Create a start time 200 seconds before the current block time - let start = env.block.time.seconds() - 200; - let end = env.block.time.seconds() - 100; - init_msg.event_info.start_time = Timestamp::from_seconds(start); - // Start time 100 seconds before the current block time - init_msg.event_info.end_time = Timestamp::from_seconds(end); - - let init_result = instantiate(deps.as_mut(), env.clone(), info, init_msg); - assert_eq!( - ContractError::EndTimeBeforeCurrentTime { - current_time: env.block.time, - end_time: Timestamp::from_seconds(end) - }, - init_result.unwrap_err() - ); - } - - #[test] - fn instantiate_with_event_end_equal_current_time_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - let mut init_msg = get_valid_init_msg(1); - - let start = env.block.time.seconds() - 200; - let end = env.block.time.nanos(); - init_msg.event_info.start_time = Timestamp::from_seconds(start); - init_msg.event_info.end_time = Timestamp::from_nanos(end); - - let init_result = instantiate(deps.as_mut(), env, info, init_msg); - assert_eq!( - ContractError::EndTimeBeforeCurrentTime { - current_time: Timestamp::from_nanos(end), - end_time: Timestamp::from_nanos(end), - }, - init_result.unwrap_err() - ); - } - - #[test] - fn instantiate_with_event_start_after_end_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - let mut init_msg = get_valid_init_msg(1); - - // Create a start time 200 seconds after the current block time - let start = env.block.time.seconds() + 200; - let end = env.block.time.seconds() + 100; - init_msg.event_info.start_time = Timestamp::from_seconds(start); - // Start time 100 seconds before the event start time - init_msg.event_info.end_time = Timestamp::from_seconds(end); - - let init_result = instantiate(deps.as_mut(), env, info, init_msg); - assert_eq!( - ContractError::StartTimeAfterEndTime { - start: Timestamp::from_seconds(start), - end: Timestamp::from_seconds(end), - }, - init_result.unwrap_err() - ); - } - - #[test] - fn instantiate_with_event_start_equal_end_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - let mut init_msg = get_valid_init_msg(1); - - // Create a start time 200 seconds after the current block time - let start = env.block.time.seconds() + 200; - init_msg.event_info.start_time = Timestamp::from_seconds(start); - init_msg.event_info.end_time = Timestamp::from_seconds(start); - - let init_result = instantiate(deps.as_mut(), env, info, init_msg); - assert_eq!( - ContractError::StartTimeAfterEndTime { - start: Timestamp::from_seconds(start), - end: Timestamp::from_seconds(start), - }, - init_result.unwrap_err() - ); - } - - #[test] - fn instantiate_with_invalid_poap_uri_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - let mut init_msg = get_valid_init_msg(1); - - // Invalid uri - init_msg.event_info.poap_uri = "invalid_uri".to_string(); - - let init_result = instantiate(deps.as_mut(), env, info, init_msg); - assert!(init_result.is_err()); - } - - #[test] - fn instantiate_with_non_ipfs_poap_uri_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - let mut init_msg = get_valid_init_msg(1); - - init_msg.event_info.poap_uri = "https://random_domain.com".to_string(); - - let init_result = instantiate(deps.as_mut(), env, info, init_msg); - assert_eq!(ContractError::InvalidPoapUri {}, init_result.unwrap_err()); - } - - #[test] - fn enable_mint_properly() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::EnableMint {}; - execute(deps.as_mut(), env, info, msg).unwrap(); - - let config = CONFIG.load(&deps.storage).unwrap(); - assert_eq!(true, config.mint_enabled); - } - - #[test] - fn enable_mint_without_permission_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(USER, &vec![]); - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::EnableMint {}; - let execute_result = execute(deps.as_mut(), env, info, msg); - assert_eq!(Unauthorized {}, execute_result.unwrap_err()); - } - - #[test] - fn disable_mint_properly() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::DisableMint {}; - execute(deps.as_mut(), env, info, msg).unwrap(); - - let config = CONFIG.load(&deps.storage).unwrap(); - assert_eq!(false, config.mint_enabled); - } - - #[test] - fn normal_user_can_not_disable_mint_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(USER, &vec![]); - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::DisableMint {}; - let execute_result = execute(deps.as_mut(), env, info, msg); - assert_eq!(Unauthorized {}, execute_result.unwrap_err()); - } - - #[test] - fn creator_change_event_info_properly() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - let new_start_time = Timestamp::from_seconds(env.block.time.seconds() + 100); - let new_end_time = Timestamp::from_seconds(env.block.time.seconds() + 400); - - do_instantiate(deps.as_mut()); - - env.block.time = Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS); - - let msg = ExecuteMsg::UpdateEventInfo { - start_time: new_start_time.clone(), - end_time: new_end_time.clone(), - }; - - execute(deps.as_mut(), env, info, msg).unwrap(); - - let event_info = EVENT_INFO.load(&deps.storage).unwrap(); - assert_eq!(new_start_time, event_info.start_time); - assert_eq!(new_end_time, event_info.end_time) - } - - #[test] - fn non_creator_change_event_info_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - let info = mock_info(USER, &vec![]); - let new_start_time = Timestamp::from_seconds(env.block.time.seconds() + 100); - let new_end_time = Timestamp::from_seconds(env.block.time.seconds() + 400); - let msg = ExecuteMsg::UpdateEventInfo { - start_time: new_start_time.clone(), - end_time: new_end_time.clone(), - }; - - do_instantiate(deps.as_mut()); - - let result = execute(deps.as_mut(), env, info, msg.clone()); - // User should not be authorized to update the event info - assert_eq!(Unauthorized {}, result.unwrap_err()); - - let env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - let result = execute(deps.as_mut(), env, info, msg); - // Admin should not be authorized to update the event info - assert_eq!(Unauthorized {}, result.unwrap_err()); - } - - #[test] - fn event_info_update_after_event_started_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::UpdateEventInfo { - start_time: Timestamp::from_seconds(EVENT_START_SECONDS), - end_time: Timestamp::from_seconds(EVENT_END_SECONDS), - }; - - // Fake current time to event in progress - env.block.time = Timestamp::from_seconds(EVENT_START_SECONDS + 100); - - let result = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); - assert_eq!( - ContractError::EventStarted { - start_time: Timestamp::from_seconds(EVENT_START_SECONDS), - current_time: env.block.time.clone(), - }, - result.unwrap_err() - ); - - // Edge case current time is event start - env.block.time = Timestamp::from_seconds(EVENT_START_SECONDS); - - let result = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); - assert_eq!( - ContractError::EventStarted { - start_time: Timestamp::from_seconds(EVENT_START_SECONDS), - current_time: env.block.time.clone(), - }, - result.unwrap_err() - ); - } - - #[test] - fn event_info_update_after_event_terminated_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::UpdateEventInfo { - start_time: Timestamp::from_seconds(EVENT_START_SECONDS), - // Add 300 seconds to prevent end time to be already passed - end_time: Timestamp::from_seconds(EVENT_END_SECONDS + 300), - }; - - // Fake current time to event ended - env.block.time = Timestamp::from_seconds(EVENT_END_SECONDS + 100); - - let result = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); - assert_eq!( - ContractError::EventTerminated { - end_time: Timestamp::from_seconds(EVENT_END_SECONDS), - current_time: env.block.time.clone(), - }, - result.unwrap_err() - ); - - // Edge case current time is event end - env.block.time = Timestamp::from_seconds(EVENT_END_SECONDS); - - let result = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()); - assert_eq!( - ContractError::EventTerminated { - end_time: Timestamp::from_seconds(EVENT_END_SECONDS), - current_time: env.block.time.clone(), - }, - result.unwrap_err() - ); - } - - #[test] - fn event_info_update_properly() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::UpdateEventInfo { - start_time: Timestamp::from_seconds(EVENT_START_SECONDS), - end_time: Timestamp::from_seconds(EVENT_END_SECONDS + 300), - }; - - // Current time is before event started - env.block.time = Timestamp::from_seconds(EVENT_START_SECONDS - 100); - - execute(deps.as_mut(), env, info, msg).unwrap(); - } - - #[test] - fn event_info_start_time_equal_end_time_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - - do_instantiate(deps.as_mut()); - env.block.time = Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS); - - // Start time eq end time - let msg = ExecuteMsg::UpdateEventInfo { - start_time: Timestamp::from_seconds(EVENT_START_SECONDS), - end_time: Timestamp::from_seconds(EVENT_START_SECONDS), - }; - - let result = execute(deps.as_mut(), env.clone(), info.clone(), msg); - assert_eq!( - ContractError::StartTimeAfterEndTime { - start: Timestamp::from_seconds(EVENT_START_SECONDS), - end: Timestamp::from_seconds(EVENT_START_SECONDS) - }, - result.unwrap_err() - ); - } - - #[test] - fn event_info_start_time_after_end_time_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - - do_instantiate(deps.as_mut()); - env.block.time = Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS); - - let msg = ExecuteMsg::UpdateEventInfo { - start_time: Timestamp::from_seconds(EVENT_START_SECONDS + 100), - end_time: Timestamp::from_seconds(EVENT_START_SECONDS), - }; - - let result = execute(deps.as_mut(), env.clone(), info.clone(), msg); - assert_eq!( - ContractError::StartTimeAfterEndTime { - start: Timestamp::from_seconds(EVENT_START_SECONDS + 100), - end: Timestamp::from_seconds(EVENT_START_SECONDS) - }, - result.unwrap_err() - ); - } - - #[test] - fn event_info_start_time_before_current_time_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - - do_instantiate(deps.as_mut()); - env.block.time = Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS); - - let msg = ExecuteMsg::UpdateEventInfo { - start_time: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS - 1), - end_time: Timestamp::from_seconds(EVENT_END_SECONDS), - }; - - let result = execute(deps.as_mut(), env.clone(), info.clone(), msg); - assert_eq!( - ContractError::StartTimeBeforeCurrentTime { - start_time: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS - 1), - current_time: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS), - }, - result.unwrap_err() - ); - } - - #[test] - fn event_info_start_time_equal_current_time_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - - do_instantiate(deps.as_mut()); - env.block.time = Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS); - - let msg = ExecuteMsg::UpdateEventInfo { - start_time: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS), - end_time: Timestamp::from_seconds(EVENT_END_SECONDS), - }; - - let result = execute(deps.as_mut(), env.clone(), info.clone(), msg); - assert_eq!( - ContractError::StartTimeBeforeCurrentTime { - start_time: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS), - current_time: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS), - }, - result.unwrap_err() - ); - } - - #[test] - fn event_info_end_time_before_current_time_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - - do_instantiate(deps.as_mut()); - env.block.time = Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS); - - let msg = ExecuteMsg::UpdateEventInfo { - start_time: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS + 2), - end_time: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS - 1), - }; - - let result = execute(deps.as_mut(), env.clone(), info.clone(), msg); - assert_eq!( - ContractError::StartTimeAfterEndTime { - start: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS + 2), - end: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS - 1), - }, - result.unwrap_err() - ); - } - - #[test] - fn event_info_end_time_equal_current_time_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(CREATOR, &vec![]); - - do_instantiate(deps.as_mut()); - env.block.time = Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS); - - let msg = ExecuteMsg::UpdateEventInfo { - start_time: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS + 2), - end_time: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS), - }; - - let result = execute(deps.as_mut(), env.clone(), info.clone(), msg); - assert_eq!( - ContractError::StartTimeAfterEndTime { - start: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS + 2), - end: Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS), - }, - result.unwrap_err() - ); - } - - #[test] - fn update_admin_without_permission_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - const NEW_ADMIN: &str = "admin2"; - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::UpdateAdmin { - new_admin: NEW_ADMIN.to_string(), - }; - - let result = execute( - deps.as_mut(), - env.clone(), - mock_info(USER, &vec![]), - msg.clone(), - ); - assert_eq!(ContractError::Unauthorized {}, result.unwrap_err()); - - let result = execute( - deps.as_mut(), - env.clone(), - mock_info(CREATOR, &vec![]), - msg.clone(), - ); - assert_eq!(ContractError::Unauthorized {}, result.unwrap_err()); - } - - #[test] - fn update_admin_with_permission_properly() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - const NEW_ADMIN: &str = "admin2"; - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::UpdateAdmin { - new_admin: NEW_ADMIN.to_string(), - }; - - execute(deps.as_mut(), env.clone(), mock_info(ADMIN, &vec![]), msg).unwrap(); - - let config = CONFIG.load(&deps.storage).unwrap(); - assert_eq!(NEW_ADMIN, config.admin.as_str()); - } - - #[test] - fn update_minter_permission_error() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - const NEW_MINTER: &str = "minter2"; - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::UpdateMinter { - new_minter: NEW_MINTER.to_string(), - }; - - let result = execute( - deps.as_mut(), - env.clone(), - mock_info(USER, &vec![]), - msg.clone(), - ); - assert_eq!(ContractError::Unauthorized {}, result.unwrap_err()); - - let result = execute( - deps.as_mut(), - env.clone(), - mock_info(CREATOR, &vec![]), - msg.clone(), - ); - assert_eq!(ContractError::Unauthorized {}, result.unwrap_err()); - - let result = execute(deps.as_mut(), env.clone(), mock_info(MINTER, &vec![]), msg); - assert_eq!(ContractError::Unauthorized {}, result.unwrap_err()); - } - - #[test] - fn update_minter_with_permission_properly() { - let mut deps = mock_desmos_dependencies(); - let env = mock_env(); - const NEW_MINTER: &str = "minter2"; - - do_instantiate(deps.as_mut()); - - let msg = ExecuteMsg::UpdateMinter { - new_minter: NEW_MINTER.to_string(), - }; - - execute(deps.as_mut(), env.clone(), mock_info(ADMIN, &vec![]), msg).unwrap(); - - let config = CONFIG.load(&deps.storage).unwrap(); - assert_eq!(NEW_MINTER, config.minter.as_str()); - } - - #[test] - fn mint_with_event_not_started_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - do_instantiate(deps.as_mut()); - - env.block.time = Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS); - - // Enable mint since is disable by default. - let msg = ExecuteMsg::EnableMint {}; - execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - let msg = ExecuteMsg::Mint {}; - let info = mock_info(USER, &vec![]); - let result = execute(deps.as_mut(), env.clone(), info, msg); - - // Event is not started - assert_eq!( - ContractError::EventNotStarted { - current_time: env.block.time, - start_time: Timestamp::from_seconds(EVENT_START_SECONDS), - }, - result.unwrap_err() - ); - } - - #[test] - fn mint_with_event_terminated_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - do_instantiate(deps.as_mut()); - - // Enable mint since is disable by default. - let msg = ExecuteMsg::EnableMint {}; - execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - env.block.time = Timestamp::from_seconds(EVENT_END_SECONDS); - - let msg = ExecuteMsg::Mint {}; - let info = mock_info(USER, &vec![]); - let result = execute(deps.as_mut(), env.clone(), info, msg); - - // Event is not started - assert_eq!( - ContractError::EventTerminated { - current_time: env.block.time, - end_time: Timestamp::from_seconds(EVENT_END_SECONDS), - }, - result.unwrap_err() - ); - } - - #[test] - fn mint_without_permissions_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(USER, &vec![]); - - do_instantiate(deps.as_mut()); - - // Change current time to event start - env.block.time = Timestamp::from_seconds(EVENT_START_SECONDS); - - let msg = ExecuteMsg::Mint {}; - let result = execute(deps.as_mut(), env.clone(), info, msg); - - // Event is not started - assert_eq!(ContractError::MintDisabled {}, result.unwrap_err()); - } - - #[test] - fn mint_out_of_max_amount_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - do_instantiate(deps.as_mut()); - - // Change current time to event start - env.block.time = Timestamp::from_seconds(EVENT_START_SECONDS); - - execute(deps.as_mut(), env.clone(), info, ExecuteMsg::EnableMint {}).unwrap(); - - let info = mock_info(USER, &vec![]); - // Mint the first poap - execute( - deps.as_mut(), - env.clone(), - info.clone(), - ExecuteMsg::Mint {}, - ) - .unwrap(); - // Mint the second poap - execute( - deps.as_mut(), - env.clone(), - info.clone(), - ExecuteMsg::Mint {}, - ) - .unwrap(); - - let response = execute( - deps.as_mut(), - env.clone(), - info.clone(), - ExecuteMsg::Mint {}, - ); - assert_eq!( - ContractError::MaxPerAddressLimitExceeded { - recipient_addr: USER.to_string() - }, - response.unwrap_err() - ); - - // Ensure that mint to also fails when minting for the user - let info = mock_info(ADMIN, &vec![]); - let response = execute( - deps.as_mut(), - env.clone(), - info, - ExecuteMsg::MintTo { - recipient: USER.to_string(), - }, - ); - - assert_eq!( - ContractError::MaxPerAddressLimitExceeded { - recipient_addr: USER.to_string() - }, - response.unwrap_err() - ); - } - - #[test] - fn mint_to_out_of_max_amount_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - let info = mock_info(ADMIN, &vec![]); - - do_instantiate(deps.as_mut()); - - // Change current time to event start - env.block.time = Timestamp::from_seconds(EVENT_START_SECONDS); - - execute( - deps.as_mut(), - env.clone(), - info.clone(), - ExecuteMsg::EnableMint {}, - ) - .unwrap(); - - // Mint the first poap - execute( - deps.as_mut(), - env.clone(), - info.clone(), - ExecuteMsg::MintTo { - recipient: USER.to_string(), - }, - ) - .unwrap(); - // Mint the second and last allowed poap - execute( - deps.as_mut(), - env.clone(), - info.clone(), - ExecuteMsg::MintTo { - recipient: USER.to_string(), - }, - ) - .unwrap(); - - let response = execute( - deps.as_mut(), - env.clone(), - info.clone(), - ExecuteMsg::MintTo { - recipient: USER.to_string(), - }, - ); - // Should fail since the user have already received the max allowed poaps. - assert_eq!( - ContractError::MaxPerAddressLimitExceeded { - recipient_addr: USER.to_string() - }, - response.unwrap_err() - ); - - // Test also with Mint from use - let info = mock_info(USER, &vec![]); - let response = execute(deps.as_mut(), env.clone(), info, ExecuteMsg::Mint {}); - assert_eq!( - ContractError::MaxPerAddressLimitExceeded { - recipient_addr: USER.to_string() - }, - response.unwrap_err() - ); - } - - #[test] - fn mint_to_without_permission_error() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - - do_instantiate(deps.as_mut()); - - // Change current time to event start - env.block.time = Timestamp::from_seconds(EVENT_START_SECONDS); - - let response = execute( - deps.as_mut(), - env, - mock_info(USER, &vec![]), - ExecuteMsg::MintTo { - recipient: USER.to_string(), - }, - ); - // User should not be authorized to use the mint to action - assert_eq!(ContractError::Unauthorized {}, response.unwrap_err()); - } - - #[test] - fn mint_to_from_minter_properly() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - - do_instantiate(deps.as_mut()); - - // Change current time to event start - env.block.time = Timestamp::from_seconds(EVENT_START_SECONDS); - - // Test that minter can call mint to - execute( - deps.as_mut(), - env.clone(), - mock_info(MINTER, &vec![]), - ExecuteMsg::MintTo { - recipient: USER.to_string(), - }, - ) - .unwrap(); - } - - #[test] - fn mint_to_from_admin_properly() { - let mut deps = mock_desmos_dependencies(); - let mut env = mock_env(); - - do_instantiate(deps.as_mut()); - - // Change current time to event start - env.block.time = Timestamp::from_seconds(EVENT_START_SECONDS); - - // Test that minter can call mint to - execute( - deps.as_mut(), - env.clone(), - mock_info(ADMIN, &vec![]), - ExecuteMsg::MintTo { - recipient: USER.to_string(), - }, - ) - .unwrap(); - } -} diff --git a/contracts/poap/src/contract_tests.rs b/contracts/poap/src/contract_tests.rs new file mode 100644 index 00000000..accc2138 --- /dev/null +++ b/contracts/poap/src/contract_tests.rs @@ -0,0 +1,1358 @@ +use crate::msg::MintStartEndTimeResponse; +use crate::ContractError::{ + InvalidTimestampValues, MintDisabled, MintTimeAlreadyEnded, MintTimeNotStarted, + MintUnauthorized, Ownership, TransferDisabled, +}; +use crate::ExecuteMsg::{ + Approve, ApproveAll, Burn, Mint, MintTo, Revoke, RevokeAll, SendNft, SetMintStartEndTime, + SetMintable, SetTransferable, TransferNft, UpdateMinter, +}; +use crate::{ContractError, ExecuteMsg, Extension, InstantiateMsg, PoapContract}; +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use cosmwasm_std::{to_binary, Addr, CosmosMsg, Deps, DepsMut, Empty, Env, Timestamp, WasmMsg}; +use cw721::{Cw721Query, NftInfoResponse}; +use cw721_base::Cw721Contract; +use cw_ownable::OwnershipError; +use cw_utils::Expiration; + +const ADMIN: &str = "admin"; +const MINTER: &str = "minter"; +const USER: &str = "user"; +const CONTRACT: &str = "contract"; +const CONTRACT_NAME: &str = "Test POAP"; +const SYMBOL: &str = "TPOAP"; +const METADATA_URI: &str = "ipfs://poap-metadata"; + +fn setup_contract( + deps: DepsMut<'_>, + is_transferable: bool, + is_mintable: bool, + mint_start_time: Option, + mint_end_time: Option, +) -> PoapContract<'static, Extension, Empty, Empty, Empty> { + let contract = PoapContract::default(); + let msg = InstantiateMsg { + name: CONTRACT_NAME.to_string(), + symbol: SYMBOL.to_string(), + metadata_uri: METADATA_URI.to_string(), + admin: Some(ADMIN.to_string()), + minter: Some(MINTER.to_string()), + is_transferable, + is_mintable, + mint_start_time, + mint_end_time, + }; + let info = mock_info(ADMIN, &[]); + let res = contract.instantiate(deps, mock_env(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + contract +} + +fn mock_env_with_time(timestamp: Timestamp) -> Env { + let mut env = mock_env(); + env.block.time = timestamp; + env +} + +fn assert_poap_minted( + contract: &PoapContract, + deps: Deps, + poap_id: String, + owner: String, +) { + // Check if the poap has been minted + let poap_info = contract.cw721_base.nft_info(deps, poap_id.clone()).unwrap(); + assert_eq!( + NftInfoResponse { + token_uri: Some(METADATA_URI.to_string()), + extension: None, + }, + poap_info + ); + + // Check if the user is the owner of the poap + let owner_info = contract + .cw721_base + .owner_of(deps, mock_env(), poap_id, true) + .unwrap(); + assert_eq!(owner, owner_info.owner); +} + +#[test] +fn proper_instantiation_without_admin_and_minter() { + let mut deps = mock_dependencies(); + let contract = PoapContract::::default(); + let msg = InstantiateMsg { + name: CONTRACT_NAME.to_string(), + symbol: SYMBOL.to_string(), + metadata_uri: METADATA_URI.to_string(), + admin: None, + minter: None, + is_transferable: false, + is_mintable: true, + mint_start_time: None, + mint_end_time: None, + }; + let info = mock_info(ADMIN, &[]); + contract + .instantiate(deps.as_mut(), mock_env(), info, msg) + .unwrap(); + + // Ensure that the admin and minter are correct. + let minter = contract.minter(deps.as_ref(), mock_env()).unwrap(); + assert_eq!(None, minter.minter); + + let admin = Cw721Contract::::ownership(deps.as_ref()).unwrap(); + assert_eq!(Some(Addr::unchecked(ADMIN)), admin.owner) +} + +#[test] +fn proper_instantiation_without_minter() { + let mut deps = mock_dependencies(); + let contract = PoapContract::::default(); + let msg = InstantiateMsg { + name: CONTRACT_NAME.to_string(), + symbol: SYMBOL.to_string(), + metadata_uri: METADATA_URI.to_string(), + admin: Some(ADMIN.to_string()), + minter: None, + is_transferable: false, + is_mintable: true, + mint_start_time: None, + mint_end_time: None, + }; + let info = mock_info(MINTER, &[]); + contract + .instantiate(deps.as_mut(), mock_env(), info, msg) + .unwrap(); + + // Ensure that the admin is the value that has been passed in the InstantiateMsg message. + let admin = Cw721Contract::::ownership(deps.as_ref()).unwrap(); + assert_eq!(Some(Addr::unchecked(ADMIN)), admin.owner); + + // Check that the minter is None and didn't default to who instantiate the contract. + let minter = contract.minter(deps.as_ref(), mock_env()).unwrap(); + assert_eq!(None, minter.minter); +} + +#[test] +fn proper_instantiation() { + let mut deps = mock_dependencies(); + let contract = PoapContract::::default(); + let msg = InstantiateMsg { + name: CONTRACT_NAME.to_string(), + symbol: SYMBOL.to_string(), + metadata_uri: METADATA_URI.to_string(), + admin: Some(ADMIN.to_string()), + minter: Some(MINTER.to_string()), + is_transferable: false, + is_mintable: true, + mint_start_time: None, + mint_end_time: None, + }; + let info = mock_info(ADMIN, &[]); + contract + .instantiate(deps.as_mut(), mock_env(), info, msg) + .unwrap(); + + // Ensure that the minter is the value that has been passed in the InstantiateMsg message. + let minter = contract.minter(deps.as_ref(), mock_env()).unwrap(); + assert_eq!(Some(MINTER.to_string()), minter.minter); + + // Ensure that the admin is the value that has been passed in the InstantiateMsg message. + let admin = Cw721Contract::::ownership(deps.as_ref()).unwrap(); + assert_eq!(Some(Addr::unchecked(ADMIN)), admin.owner) +} + +#[test] +fn instantiate_with_invalid_times_fails() { + let mut deps = mock_dependencies(); + let contract = PoapContract::::default(); + let msg = InstantiateMsg { + name: CONTRACT_NAME.to_string(), + symbol: SYMBOL.to_string(), + metadata_uri: METADATA_URI.to_string(), + admin: Some(ADMIN.to_string()), + minter: Some(MINTER.to_string()), + is_transferable: false, + is_mintable: true, + mint_start_time: Some(Timestamp::from_seconds(10)), + mint_end_time: Some(Timestamp::from_seconds(1)), + }; + + let err = contract + .instantiate(deps.as_mut(), mock_env(), mock_info(ADMIN, &[]), msg) + .unwrap_err(); + assert_eq!(InvalidTimestampValues {}, err); +} + +#[test] +fn user_can_mint_if_mintable() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + let info = mock_info(USER, &[]); + let _ = contract + .execute(deps.as_mut(), mock_env(), info, Mint { extension: None }) + .unwrap(); + + assert_poap_minted(&contract, deps.as_ref(), poap_id, USER.to_string()); +} + +#[test] +fn user_cant_mint_if_not_mintable() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, false, None, None); + + // Check that the user can't mint if the poap is not mintable + let user_info = mock_info(USER, &[]); + let err = contract + .execute( + deps.as_mut(), + mock_env(), + user_info, + Mint { extension: None }, + ) + .unwrap_err(); + assert_eq!(MintDisabled {}, err); +} + +#[test] +fn minter_can_mint_if_not_mintable() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, false, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + // Check that the minter can mint if the poap is not mintable + let user_info = mock_info(MINTER, &[]); + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + user_info, + Mint { extension: None }, + ) + .unwrap(); + + assert_poap_minted(&contract, deps.as_ref(), poap_id, MINTER.to_string()); +} + +#[test] +fn admin_can_mint_if_not_mintable() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, false, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + // Check that the amin can mint if the poap is not mintable + let user_info = mock_info(ADMIN, &[]); + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + user_info, + Mint { extension: None }, + ) + .unwrap(); + + assert_poap_minted(&contract, deps.as_ref(), poap_id, ADMIN.to_string()); +} + +#[test] +fn user_cant_mint_more_then_1_poap() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, USER.to_string()); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + Mint { extension: None }, + ) + .unwrap_err(); + assert_eq!( + ContractError::PoapAlreadyMinted { + user: USER.to_string() + }, + err + ); +} + +#[test] +fn minter_cant_mint_more_then_1_poap() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(MINTER, &[]), + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, MINTER.to_string()); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(MINTER, &[]), + Mint { extension: None }, + ) + .unwrap_err(); + assert_eq!( + ContractError::PoapAlreadyMinted { + user: MINTER.to_string() + }, + err + ); +} + +#[test] +fn admin_cant_mint_more_then_1_poap() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, ADMIN.to_string()); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + Mint { extension: None }, + ) + .unwrap_err(); + assert_eq!( + ContractError::PoapAlreadyMinted { + user: ADMIN.to_string() + }, + err + ); +} + +#[test] +fn user_can_mint_after_start_time() { + let mut deps = mock_dependencies(); + let start_time = Timestamp::from_seconds(200); + let contract = setup_contract(deps.as_mut(), true, true, Some(start_time.clone()), None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let env_event_started = mock_env_with_time(start_time); + + // Check that the user can mint after the mint start time. + let user_info = mock_info(USER, &[]); + let _ = contract + .execute( + deps.as_mut(), + env_event_started, + user_info, + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, USER.to_string()); +} + +#[test] +fn user_cant_mint_before_start_time() { + let mut deps = mock_dependencies(); + + let contract = setup_contract( + deps.as_mut(), + true, + true, + Some(Timestamp::from_seconds(200)), + None, + ); + + let env_event_not_started = mock_env_with_time(Timestamp::from_seconds(199)); + + // Check that the user can't mint before the mint start time. + let user_info = mock_info(USER, &[]); + let err = contract + .execute( + deps.as_mut(), + env_event_not_started, + user_info, + Mint { extension: None }, + ) + .unwrap_err(); + assert_eq!(MintTimeNotStarted {}, err); +} + +#[test] +fn minter_can_mint_before_start_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract( + deps.as_mut(), + true, + true, + Some(Timestamp::from_seconds(200)), + None, + ); + let env_event_not_started = mock_env_with_time(Timestamp::from_seconds(199)); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + // Check that the minter can mint before the mint start time. + let user_info = mock_info(MINTER, &[]); + let _ = contract + .execute( + deps.as_mut(), + env_event_not_started, + user_info, + Mint { extension: None }, + ) + .unwrap(); + + assert_poap_minted(&contract, deps.as_ref(), poap_id, MINTER.to_string()) +} + +#[test] +fn admin_can_mint_before_start_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract( + deps.as_mut(), + true, + true, + Some(Timestamp::from_seconds(200)), + None, + ); + let env_event_not_started = mock_env_with_time(Timestamp::from_seconds(199)); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + // Check that the admin can mint before the mint start time. + let user_info = mock_info(ADMIN, &[]); + let _ = contract + .execute( + deps.as_mut(), + env_event_not_started, + user_info, + Mint { extension: None }, + ) + .unwrap(); + + assert_poap_minted(&contract, deps.as_ref(), poap_id, ADMIN.to_string()) +} + +#[test] +fn user_can_mint_before_end_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract( + deps.as_mut(), + true, + true, + None, + Some(Timestamp::from_seconds(200)), + ); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let env_before_end_time = mock_env_with_time(Timestamp::from_seconds(199)); + + // Check that the user can mint before the mint end time. + let user_info = mock_info(USER, &[]); + let _ = contract + .execute( + deps.as_mut(), + env_before_end_time, + user_info, + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, USER.to_string()); +} + +#[test] +fn user_cant_mint_after_end_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract( + deps.as_mut(), + true, + true, + None, + Some(Timestamp::from_seconds(200)), + ); + let env_after_end_time = mock_env_with_time(Timestamp::from_seconds(200)); + + // Check that the user can't mint after the mint end time. + let user_info = mock_info(USER, &[]); + let err = contract + .execute( + deps.as_mut(), + env_after_end_time, + user_info, + Mint { extension: None }, + ) + .unwrap_err(); + assert_eq!(MintTimeAlreadyEnded {}, err); +} + +#[test] +fn minter_can_mint_after_end_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract( + deps.as_mut(), + true, + true, + None, + Some(Timestamp::from_seconds(200)), + ); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let env_after_end_time = mock_env_with_time(Timestamp::from_seconds(200)); + + // Check that the minter can mint after the mint end time. + let user_info = mock_info(MINTER, &[]); + let _ = contract + .execute( + deps.as_mut(), + env_after_end_time, + user_info, + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, MINTER.to_string()); +} + +#[test] +fn admin_can_mint_after_end_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract( + deps.as_mut(), + true, + true, + None, + Some(Timestamp::from_seconds(200)), + ); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let env_after_end_time = mock_env_with_time(Timestamp::from_seconds(200)); + + // Check that the admin can mint after the mint end time. + let user_info = mock_info(ADMIN, &[]); + let _ = contract + .execute( + deps.as_mut(), + env_after_end_time, + user_info, + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, ADMIN.to_string()); +} + +#[test] +fn user_can_mint_during_mint_time() { + let mut deps = mock_dependencies(); + + let contract = setup_contract( + deps.as_mut(), + true, + true, + Some(Timestamp::from_seconds(100)), + Some(Timestamp::from_seconds(200)), + ); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + // Check that user can mint during mint time + let user_info = mock_info(USER, &[]); + let _ = contract + .execute( + deps.as_mut(), + mock_env_with_time(Timestamp::from_seconds(150)), + user_info, + Mint { extension: None }, + ) + .unwrap(); + + assert_poap_minted(&contract, deps.as_ref(), poap_id, USER.to_string()); +} + +#[test] +fn user_cant_mint_outside_mint_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract( + deps.as_mut(), + true, + true, + Some(Timestamp::from_seconds(100)), + Some(Timestamp::from_seconds(200)), + ); + + // Check that user can't mint before start time + let user_info = mock_info(USER, &[]); + let err = contract + .execute( + deps.as_mut(), + mock_env_with_time(Timestamp::from_seconds(99)), + user_info, + Mint { extension: None }, + ) + .unwrap_err(); + assert_eq!(MintTimeNotStarted {}, err); + + // Check that user can't mint after end time + let user_info = mock_info(USER, &[]); + let err = contract + .execute( + deps.as_mut(), + mock_env_with_time(Timestamp::from_seconds(200)), + user_info, + Mint { extension: None }, + ) + .unwrap_err(); + assert_eq!(MintTimeAlreadyEnded {}, err); +} + +#[test] +fn minter_can_mint_outside_mint_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract( + deps.as_mut(), + true, + true, + Some(Timestamp::from_seconds(100)), + Some(Timestamp::from_seconds(200)), + ); + + // Check that minter can mint before start time + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let user_info = mock_info(MINTER, &[]); + let _ = contract + .execute( + deps.as_mut(), + mock_env_with_time(Timestamp::from_seconds(99)), + user_info.clone(), + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted( + &contract, + deps.as_ref(), + poap_id.clone(), + MINTER.to_string(), + ); + + // Burn the minted POAP. + let _ = contract + .execute( + deps.as_mut(), + mock_env_with_time(Timestamp::from_seconds(99)), + user_info.clone(), + Burn { token_id: poap_id }, + ) + .unwrap(); + + // Check that minter can mint after end time + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let _ = contract + .execute( + deps.as_mut(), + mock_env_with_time(Timestamp::from_seconds(200)), + user_info, + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, MINTER.to_string()); +} + +#[test] +fn admin_can_mint_outside_mint_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract( + deps.as_mut(), + true, + true, + Some(Timestamp::from_seconds(100)), + Some(Timestamp::from_seconds(200)), + ); + + // Check that admin can mint before start time + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let user_info = mock_info(ADMIN, &[]); + let _ = contract + .execute( + deps.as_mut(), + mock_env_with_time(Timestamp::from_seconds(99)), + user_info.clone(), + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id.clone(), ADMIN.to_string()); + + // Burn the minted POAP. + let _ = contract + .execute( + deps.as_mut(), + mock_env_with_time(Timestamp::from_seconds(99)), + user_info.clone(), + Burn { token_id: poap_id }, + ) + .unwrap(); + + // Check that admin can mint after end time + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let _ = contract + .execute( + deps.as_mut(), + mock_env_with_time(Timestamp::from_seconds(200)), + user_info, + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, ADMIN.to_string()); +} + +#[test] +fn user_cant_mint_for_other_users() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + MintTo { + users: vec![MINTER.to_string()], + extension: None, + }, + ) + .unwrap_err(); + assert_eq!(MintUnauthorized {}, err); +} + +#[test] +fn minter_can_mint_for_other_users() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(MINTER, &[]), + MintTo { + users: vec![USER.to_string()], + extension: None, + }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, USER.to_string()); +} + +#[test] +fn admin_can_mint_for_other_users() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + MintTo { + users: vec![USER.to_string()], + extension: None, + }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, USER.to_string()); +} + +#[test] +fn admin_can_mint_for_other_users_if_minter_is_none() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + // Remove the minter. + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + UpdateMinter { minter: None }, + ) + .unwrap(); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + MintTo { + users: vec![USER.to_string()], + extension: None, + }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, USER.to_string()); +} + +#[test] +fn minter_cant_mint_more_than_one_poap_for_a_user() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(MINTER, &[]), + MintTo { + users: vec![USER.to_string()], + extension: None, + }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, USER.to_string()); + + // Try to mint a second poap for USER. + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(MINTER, &[]), + MintTo { + users: vec![USER.to_string()], + extension: None, + }, + ) + .unwrap_err(); + assert_eq!( + ContractError::PoapAlreadyMinted { + user: USER.to_string() + }, + err + ); +} + +#[test] +fn admin_cant_mint_more_than_one_poap_for_a_user() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + MintTo { + users: vec![USER.to_string()], + extension: None, + }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, USER.to_string()); + + // Try to mint a second poap for USER. + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + MintTo { + users: vec![USER.to_string()], + extension: None, + }, + ) + .unwrap_err(); + assert_eq!( + ContractError::PoapAlreadyMinted { + user: USER.to_string() + }, + err + ); +} + +#[test] +fn can_transfer_if_transferable() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + // Mint a poap + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let info = mock_info(USER, &[]); + let _ = contract + .execute(deps.as_mut(), mock_env(), info, Mint { extension: None }) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id.clone(), USER.to_string()); + + // Transfer the poap to another user + let info = mock_info(USER, &[]); + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + info, + TransferNft { + recipient: ADMIN.to_string(), + token_id: poap_id.clone(), + }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id, ADMIN.to_string()); +} + +#[test] +fn cant_transfer_if_not_transferable() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), false, true, None, None); + + // Mint a poap + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let info = mock_info(USER, &[]); + let _ = contract + .execute(deps.as_mut(), mock_env(), info, Mint { extension: None }) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id.clone(), USER.to_string()); + + // Transfer the poap to another user + let info = mock_info(USER, &[]); + let err = contract + .execute( + deps.as_mut(), + mock_env(), + info, + TransferNft { + recipient: ADMIN.to_string(), + token_id: poap_id, + }, + ) + .unwrap_err(); + assert_eq!(TransferDisabled {}, err); +} + +#[test] +fn can_send_if_transferable() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + // Mint a poap + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let info = mock_info(USER, &[]); + let _ = contract + .execute(deps.as_mut(), mock_env(), info, Mint { extension: None }) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id.clone(), USER.to_string()); + + // Send the poap to a contract + let inner_msg = WasmMsg::Execute { + contract_addr: "another_contract".into(), + msg: to_binary("You now also have the growing power").unwrap(), + funds: vec![], + }; + let msg: CosmosMsg = CosmosMsg::Wasm(inner_msg); + let info = mock_info(USER, &[]); + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + info, + SendNft { + contract: CONTRACT.to_string(), + msg: to_binary(&msg).unwrap(), + token_id: poap_id.clone(), + }, + ) + .unwrap(); +} + +#[test] +fn cant_send_if_not_transferable() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), false, true, None, None); + + // Mint a poap + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let info = mock_info(USER, &[]); + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + info, + ExecuteMsg::Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id.clone(), USER.to_string()); + + // Send the poap to a contract + let inner_msg = WasmMsg::Execute { + contract_addr: "another_contract".into(), + msg: to_binary("You now also have the growing power").unwrap(), + funds: vec![], + }; + let msg: CosmosMsg = CosmosMsg::Wasm(inner_msg); + let info = mock_info(USER, &[]); + let err = contract + .execute( + deps.as_mut(), + mock_env(), + info, + SendNft { + contract: CONTRACT.to_string(), + msg: to_binary(&msg).unwrap(), + token_id: poap_id.clone(), + }, + ) + .unwrap_err(); + assert_eq!(TransferDisabled {}, err); +} + +#[test] +fn only_admin_can_update_the_minter() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + UpdateMinter { + minter: Some(USER.to_string()), + }, + ) + .unwrap_err(); + assert_eq!(Ownership(OwnershipError::NotOwner), err); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(MINTER, &[]), + UpdateMinter { + minter: Some(USER.to_string()), + }, + ) + .unwrap_err(); + assert_eq!(Ownership(OwnershipError::NotOwner), err); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + UpdateMinter { + minter: Some(USER.to_string()), + }, + ) + .unwrap(); + + let minter = contract.minter(deps.as_ref(), mock_env()).unwrap(); + assert_eq!(USER.to_string(), minter.minter.unwrap()); +} + +#[test] +fn only_admin_can_set_mintable() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + SetMintable { mintable: false }, + ) + .unwrap_err(); + assert_eq!(Ownership(OwnershipError::NotOwner), err); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(MINTER, &[]), + SetMintable { mintable: false }, + ) + .unwrap_err(); + assert_eq!(Ownership(OwnershipError::NotOwner), err); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + SetMintable { mintable: false }, + ) + .unwrap(); + + let is_mintable = contract.is_mintable(deps.as_ref(), mock_env()).unwrap(); + assert_eq!(false, is_mintable.is_mintable); +} + +#[test] +fn only_admin_can_set_transferable() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + SetTransferable { + transferable: false, + }, + ) + .unwrap_err(); + assert_eq!(Ownership(OwnershipError::NotOwner), err); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(MINTER, &[]), + SetTransferable { + transferable: false, + }, + ) + .unwrap_err(); + assert_eq!(Ownership(OwnershipError::NotOwner), err); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + SetTransferable { + transferable: false, + }, + ) + .unwrap(); + + let is_transferable = contract.is_transferable(deps.as_ref(), mock_env()).unwrap(); + assert_eq!(false, is_transferable.is_transferable); +} + +#[test] +fn only_admin_can_set_start_end_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + SetMintStartEndTime { + start_time: Some(Timestamp::from_seconds(1)), + end_time: Some(Timestamp::from_seconds(200)), + }, + ) + .unwrap_err(); + assert_eq!(Ownership(OwnershipError::NotOwner), err); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(MINTER, &[]), + SetMintStartEndTime { + start_time: Some(Timestamp::from_seconds(1)), + end_time: Some(Timestamp::from_seconds(200)), + }, + ) + .unwrap_err(); + assert_eq!(Ownership(OwnershipError::NotOwner), err); + + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + SetMintStartEndTime { + start_time: Some(Timestamp::from_seconds(1)), + end_time: Some(Timestamp::from_seconds(200)), + }, + ) + .unwrap(); + + let start_end_time = contract + .mint_start_end_time(deps.as_ref(), mock_env()) + .unwrap(); + assert_eq!( + MintStartEndTimeResponse { + start_time: Some(Timestamp::from_seconds(1)), + end_time: Some(Timestamp::from_seconds(200)) + }, + start_end_time + ); +} + +#[test] +fn start_time_cant_be_higher_equal_then_end_time() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + SetMintStartEndTime { + start_time: Some(Timestamp::from_seconds(200)), + end_time: Some(Timestamp::from_seconds(200)), + }, + ) + .unwrap_err(); + assert_eq!(InvalidTimestampValues {}, err); + + let err = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(ADMIN, &[]), + SetMintStartEndTime { + start_time: Some(Timestamp::from_seconds(201)), + end_time: Some(Timestamp::from_seconds(200)), + }, + ) + .unwrap_err(); + assert_eq!(InvalidTimestampValues {}, err); +} + +#[test] +fn can_burn_their_poap() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + // Compute the id of the poap that will be minted + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id.clone(), USER.to_string()); + + // Try to burn the minted poap + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + Burn { token_id: poap_id }, + ) + .unwrap(); + + // Check that the poap has been burned + let token_count = contract.cw721_base.token_count(&deps.storage).unwrap(); + assert_eq!(0, token_count); +} + +#[test] +fn can_approve_and_revoke() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + // Compute the id of the poap that will be minted + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + // Mint a poap + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id.clone(), USER.to_string()); + + // Test Approve message + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + Approve { + token_id: poap_id.clone(), + spender: MINTER.to_string(), + expires: Some(Expiration::Never {}), + }, + ) + .unwrap(); + + // Test Revoke message + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + Revoke { + token_id: poap_id, + spender: MINTER.to_string(), + }, + ) + .unwrap(); +} + +#[test] +fn can_approve_all_and_revoke_all() { + let mut deps = mock_dependencies(); + let contract = setup_contract(deps.as_mut(), true, true, None, None); + + // Compute the id of the poap that will be minted + let poap_id = contract.generate_poap_id(&deps.storage).unwrap(); + // Mint a poap + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + Mint { extension: None }, + ) + .unwrap(); + assert_poap_minted(&contract, deps.as_ref(), poap_id.clone(), USER.to_string()); + + // Test ApproveAll message + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + ApproveAll { + operator: MINTER.to_string(), + expires: Some(Expiration::Never {}), + }, + ) + .unwrap(); + + // Test RevokeAll message + let _ = contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(USER, &[]), + RevokeAll { + operator: MINTER.to_string(), + }, + ) + .unwrap(); +} diff --git a/contracts/poap/src/cw721_test_utils.rs b/contracts/poap/src/cw721_test_utils.rs deleted file mode 100644 index 8ff30a0f..00000000 --- a/contracts/poap/src/cw721_test_utils.rs +++ /dev/null @@ -1,56 +0,0 @@ -use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdError, StdResult}; -use cw721_base::{ - ContractError as Cw721ContractError, Cw721Contract, ExecuteMsg as Cw721ExecuteMsg, - InstantiateMsg as Cw721InstantiateMsg, QueryMsg as Cw721QueryMsg, -}; -use cw721_poap::Metadata; -use cw_multi_test::{Contract, ContractWrapper}; -use desmos_bindings::{msg::DesmosMsg, query::DesmosQuery}; - -fn cw721_execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Cw721ExecuteMsg, -) -> Result, Cw721ContractError> { - Cw721Contract::<'static, Metadata, Empty, Empty, DesmosMsg, DesmosQuery>::default() - .execute(deps, env, info, msg) -} - -fn cw721_instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Cw721InstantiateMsg, -) -> Result, StdError> { - Cw721Contract::<'static, Metadata, Empty, Empty, DesmosMsg, DesmosQuery>::default() - .instantiate(deps, env, info, msg) -} - -fn failing_cw721_instantiate( - _deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: Cw721InstantiateMsg, -) -> Result, StdError> { - Err(StdError::generic_err("cw721 initialization failed")) -} - -fn cw721_query(deps: Deps, env: Env, msg: Cw721QueryMsg) -> StdResult { - Cw721Contract::<'static, Metadata, Empty, Empty, DesmosMsg, DesmosQuery>::default() - .query(deps, env, msg) -} - -/// Provides an instance of a cw721 contract. -/// This instance can be used only during the integration tests. -pub fn contract_cw721() -> Box> { - let contract = ContractWrapper::new(cw721_execute, cw721_instantiate, cw721_query); - Box::new(contract) -} - -/// Provides an instance of a cw721 contract that fails during the initialization. -/// This instance can be used only during the integration tests. -pub fn failing_cw721() -> Box> { - let contract = ContractWrapper::new(cw721_execute, failing_cw721_instantiate, cw721_query); - Box::new(contract) -} diff --git a/contracts/poap/src/error.rs b/contracts/poap/src/error.rs index 0ade7ff3..1ba1ccd8 100644 --- a/contracts/poap/src/error.rs +++ b/contracts/poap/src/error.rs @@ -1,62 +1,62 @@ -use cosmwasm_std::{StdError, Timestamp}; +use cosmwasm_std::StdError; +use cw721_base::ContractError as Cw721BaseContractError; +use cw_ownable::OwnershipError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] pub enum ContractError { - #[error("{0}")] + #[error(transparent)] Std(#[from] StdError), - #[error("Unauthorized")] - Unauthorized {}, + #[error(transparent)] + Ownership(#[from] OwnershipError), - #[error("Invalid reply ID")] - InvalidReplyID {}, + #[error(transparent)] + Version(#[from] cw2::VersionError), - #[error("Instantiate cw721 error")] - InstantiateCw721Error {}, + #[error("token_id already claimed")] + Claimed {}, - #[error("The start time ({start}) is after the end time ({end})")] - StartTimeAfterEndTime { start: Timestamp, end: Timestamp }, + #[error("Cannot set approval that is already expired")] + Expired {}, - #[error("Event start time is before current time: {current_time} start: {start_time}")] - StartTimeBeforeCurrentTime { - current_time: Timestamp, - start_time: Timestamp, - }, + #[error("Approval not found for: {spender}")] + ApprovalNotFound { spender: String }, - #[error("Event end time is before current time: {current_time} end: {end_time}")] - EndTimeBeforeCurrentTime { - current_time: Timestamp, - end_time: Timestamp, - }, + #[error("Transfer is not allowed")] + TransferDisabled {}, - #[error("Invalid poap URI (must be an IPFS URI)")] - InvalidPoapUri {}, + #[error("Mint is not allowed")] + MintDisabled {}, - #[error("Invalid per address limit value")] - InvalidPerAddressLimit {}, + #[error("{user} already owns a POAP")] + PoapAlreadyMinted { user: String }, - #[error("Mint operation is disabled")] - MintDisabled {}, + #[error("You don't have the permission to mint")] + MintUnauthorized {}, + + #[error("Can't mint: minting period not started yet")] + MintTimeNotStarted {}, + + #[error("Can't mint: minting period already ended")] + MintTimeAlreadyEnded {}, + + #[error("Start time must be smaller than end time")] + InvalidTimestampValues {}, +} - #[error("Minting limit reached for {recipient_addr}")] - MaxPerAddressLimitExceeded { recipient_addr: String }, - - #[error("Event started, current time: {current_time}, start: {start_time}")] - EventStarted { - current_time: Timestamp, - start_time: Timestamp, - }, - - #[error("Event not started, current time: {current_time}, start time: {start_time}")] - EventNotStarted { - current_time: Timestamp, - start_time: Timestamp, - }, - - #[error("Event terminated, current time: {current_time} end time: {end_time}")] - EventTerminated { - current_time: Timestamp, - end_time: Timestamp, - }, +impl From for ContractError { + #[cfg(not(tarpaulin_include))] + fn from(error: Cw721BaseContractError) -> Self { + match error { + Cw721BaseContractError::Std(e) => ContractError::Std(e), + Cw721BaseContractError::Ownership(e) => ContractError::Ownership(e), + Cw721BaseContractError::Version(e) => ContractError::Version(e), + Cw721BaseContractError::Claimed {} => ContractError::Claimed {}, + Cw721BaseContractError::Expired {} => ContractError::Expired {}, + Cw721BaseContractError::ApprovalNotFound { spender } => { + ContractError::ApprovalNotFound { spender } + } + } + } } diff --git a/contracts/poap/src/execute.rs b/contracts/poap/src/execute.rs new file mode 100644 index 00000000..8f03402b --- /dev/null +++ b/contracts/poap/src/execute.rs @@ -0,0 +1,450 @@ +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg}; +use crate::state::PoapContract; +use cosmwasm_std::{ + Addr, Binary, CustomMsg, DepsMut, Env, MessageInfo, Response, StdResult, Storage, Timestamp, +}; +use cw721::Cw721Execute; +pub use cw721_base::{ + entry::{execute as _execute, query as _query}, + Cw721Contract, Extension, InstantiateMsg as Cw721BaseInstantiateMsg, MinterResponse, +}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::fmt::Debug; + +impl<'a, T, C, E, Q> PoapContract<'a, T, C, E, Q> +where + T: Serialize + DeserializeOwned + Clone + Debug, + C: CustomMsg, + E: CustomMsg, + Q: CustomMsg, +{ + pub fn instantiate( + &self, + mut deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, + ) -> Result, ContractError> { + // Instantiate the base cw721 that we are extending. + let cw721_instantiate_msg = Cw721BaseInstantiateMsg { + name: msg.name, + symbol: msg.symbol, + // Here we pass the admin as minter since the minter is the admin in the cw721-base. + minter: msg.admin.unwrap_or(info.sender.to_string()), + }; + self.cw721_base + .instantiate(deps.branch(), env, info.clone(), cw721_instantiate_msg)?; + + // Save the poap metadata uri + self.metadata_uri.save(deps.storage, &msg.metadata_uri)?; + + // Get the minter address or fallback to the sender address. + let minter = msg + .minter + .map(|minter| deps.api.addr_validate(&minter)) + .transpose()?; + self.minter.save(deps.storage, &minter)?; + self.is_transferable + .save(deps.storage, &msg.is_transferable)?; + self.is_mintable.save(deps.storage, &msg.is_mintable)?; + + if msg.mint_start_time.is_some() + && msg.mint_end_time.is_some() + && msg.mint_start_time.unwrap() >= msg.mint_end_time.unwrap() + { + return Err(ContractError::InvalidTimestampValues {}); + } + + self.mint_start_time + .save(deps.storage, &msg.mint_start_time)?; + self.mint_end_time.save(deps.storage, &msg.mint_end_time)?; + + Ok(Response::default()) + } + + pub fn execute( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, + ) -> Result, ContractError> { + match msg { + ExecuteMsg::TransferNft { + recipient, + token_id, + } => self.transfer_poap(deps, env, info, recipient, token_id), + ExecuteMsg::SendNft { + contract, + msg, + token_id, + } => self.send_poap(deps, env, info, contract, token_id, msg), + ExecuteMsg::UpdateMinter { minter } => self.update_minter(deps, env, info, minter), + ExecuteMsg::SetMintable { mintable } => self.set_mintable(deps, env, info, mintable), + ExecuteMsg::SetTransferable { transferable } => { + self.set_transferable(deps, env, info, transferable) + } + ExecuteMsg::SetMintStartEndTime { + start_time, + end_time, + } => self.set_mint_start_end_time(deps, env, info, start_time, end_time), + ExecuteMsg::Mint { extension } => self.mint(deps, env, info, extension), + ExecuteMsg::MintTo { extension, users } => { + self.mint_to(deps, env, info, users, extension) + } + _ => self + .cw721_base + .execute(deps, env, info, msg.into()) + .map_err(|e| e.into()), + } + } +} + +impl<'a, T, C, E, Q> PoapContract<'a, T, C, E, Q> +where + T: Serialize + DeserializeOwned + Clone + Debug, + C: CustomMsg, + E: CustomMsg, + Q: CustomMsg, +{ + /// Transfer a POAP to another user. + /// * `recipient` - Address of the user that will receive the POAP. + /// * `token_id` - Id of the POAP to transfer. + pub fn transfer_poap( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: String, + token_id: String, + ) -> Result, ContractError> { + // Check if the transfer is allowed. + self.assert_is_transferable(deps.storage)?; + + return self + .cw721_base + .transfer_nft(deps, env, info, recipient, token_id) + .map_err(|e| e.into()); + } + + /// Send a POAP to a contract and trigger an action on the contract. + /// * `contract` - Address of the contract that will receive the POAP. + /// * `token_id` - Id of the POAP to transfer. + /// * `msg` - Message that the recipient contract will execute. + pub fn send_poap( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + contract: String, + token_id: String, + msg: Binary, + ) -> Result, ContractError> { + // Check if the transfer is allowed. + self.assert_is_transferable(deps.storage)?; + + return self + .cw721_base + .send_nft(deps, env, info, contract, token_id, msg) + .map_err(|e| e.into()); + } + + /// Updates who have the minting permissions, this action can be executed only from + /// the contract admin. + /// * `minter` - The new minter address. + pub fn update_minter( + &self, + deps: DepsMut, + _env: Env, + info: MessageInfo, + minter: Option, + ) -> Result, ContractError> { + self.assert_is_admin(deps.storage, &info.sender)?; + let new_minter = minter.map(|m| deps.api.addr_validate(&m)).transpose()?; + self.minter.save(deps.storage, &new_minter)?; + + Ok(Response::new() + .add_attribute("action", "update_minter") + .add_attribute("sender", info.sender) + .add_attribute( + "new_minter", + new_minter.map_or_else(|| "none".to_string(), |minter| minter.to_string()), + )) + } + + /// Updates the POAP mintability, this action can be executed only from + /// the contract admin. + /// * `mintable` - true if the POAP can be minted from the users, false otherwise. + pub fn set_mintable( + &self, + deps: DepsMut, + _env: Env, + info: MessageInfo, + mintable: bool, + ) -> Result, ContractError> { + self.assert_is_admin(deps.storage, &info.sender)?; + self.is_mintable.save(deps.storage, &mintable)?; + + Ok(Response::new() + .add_attribute("action", "set_mintable") + .add_attribute("sender", info.sender) + .add_attribute("mintable", mintable.to_string())) + } + + /// Sets if the users are allowed to transfer their POAP, this action can be executed only from + /// the contract admin. + /// * `transferable` - true if the POAP can be transferred, false otherwise. + pub fn set_transferable( + &self, + deps: DepsMut, + _env: Env, + info: MessageInfo, + transferable: bool, + ) -> Result, ContractError> { + self.assert_is_admin(deps.storage, &info.sender)?; + self.is_transferable.save(deps.storage, &transferable)?; + + Ok(Response::new() + .add_attribute("action", "set_transferable") + .add_attribute("sender", info.sender) + .add_attribute("transferable", transferable.to_string())) + } + + /// Sets the time period on which the minting is allowed, this action can be executed only from + /// the contract admin. + /// * `start_time` - Timestamp at which the minting of the POAP will be enabled. + /// If None, the minting is always enabled. + /// * `end_time` - Timestamp at which the minting of the POAP will be disabled. + /// If not set, the minting will never end. + pub fn set_mint_start_end_time( + &self, + deps: DepsMut, + _env: Env, + info: MessageInfo, + start_time: Option, + end_time: Option, + ) -> Result, ContractError> { + self.assert_is_admin(deps.storage, &info.sender)?; + + // Ensure that if we have both start time and end time the start time is lower then + // the end time. + if start_time.is_some() && end_time.is_some() && start_time.unwrap() >= end_time.unwrap() { + return Err(ContractError::InvalidTimestampValues {}); + } + + // Update the start and end time + self.mint_start_time.save(deps.storage, &start_time)?; + self.mint_end_time.save(deps.storage, &end_time)?; + + Ok(Response::new() + .add_attribute("action", "set_mint_start_end_time") + .add_attribute("sender", info.sender) + .add_attribute( + "start_time", + start_time.map_or_else(|| "none".to_string(), |t| t.to_string()), + ) + .add_attribute( + "end_time", + end_time.map_or_else(|| "none".to_string(), |t| t.to_string()), + )) + } + + /// Mint a POAP to the user that is calling this action. + pub fn mint( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + extension: T, + ) -> Result, ContractError> { + self.assert_user_can_mint(deps.storage, &info.sender, &env)?; + let token_id = self.mint_to_user(deps.storage, &info.sender, extension)?; + + Ok(Response::new() + .add_attribute("action", "mint") + .add_attribute("owner", info.sender) + .add_attribute("token_id", token_id)) + } + + /// Mint a POAP to a list of user, this action can be executed only from the contract minter. + /// * `users` - List of users for whom the POAP will be minted. + pub fn mint_to( + &self, + deps: DepsMut, + _env: Env, + info: MessageInfo, + users: Vec, + extension: T, + ) -> Result, ContractError> { + // Check if the sender is the admin or the minter. + let can_mint = self.assert_is_minter(deps.storage, &info.sender).is_ok() + || self.assert_is_admin(deps.storage, &info.sender).is_ok(); + + if !can_mint { + return Err(ContractError::MintUnauthorized {}); + } + + let mut minted_tokens = Vec::::with_capacity(users.len()); + for user in users { + let user_addr = deps.api.addr_validate(&user)?; + minted_tokens.push(self.mint_to_user(deps.storage, &user_addr, extension.clone())?); + } + + Ok(Response::new() + .add_attribute("action", "mint_to") + .add_attribute("minter", info.sender) + .add_attribute("token_ids", minted_tokens.join(", "))) + } +} + +// Utility functions +impl<'a, T, C, E, Q> PoapContract<'a, T, C, E, Q> +where + T: Serialize + DeserializeOwned + Clone + Debug, + C: CustomMsg, + E: CustomMsg, + Q: CustomMsg, +{ + /// Computes the id of the next POAP to mint. + pub fn generate_poap_id(&self, storage: &dyn Storage) -> StdResult { + Ok(format!("{}", 1 + self.cw721_base.token_count(storage)?)) + } + + /// Mint a POAP to an user. + /// * `owner` - User for whom the POAP will be minted. + pub fn mint_to_user( + &self, + storage: &mut dyn Storage, + owner: &Addr, + extension: T, + ) -> Result { + // Check if this user have already minted a poap. + self.assert_user_dont_own_a_poap(storage, &owner)?; + + // Create the token + let token = cw721_base::state::TokenInfo { + owner: owner.clone(), + approvals: vec![], + token_uri: Some(self.metadata_uri.load(storage)?), + extension, + }; + + // Generate the token id + let token_id = self.generate_poap_id(storage)?; + self.cw721_base + .tokens + .update(storage, &token_id, |old| match old { + Some(_) => Err(ContractError::Claimed {}), + None => Ok(token), + })?; + + self.cw721_base.increment_tokens(storage)?; + + Ok(token_id) + } + + /// Asserts that the provided address is the contract admin. + /// * `sender` - Address that will be checked. + pub fn assert_is_admin( + &self, + storage: &dyn Storage, + sender: &Addr, + ) -> Result<(), ContractError> { + cw_ownable::assert_owner(storage, sender).map_err(|e| ContractError::Ownership(e)) + } + + /// Asserts that the provided address is the contract minter. + /// * `sender` - Address that will be checked. + pub fn assert_is_minter( + &self, + storage: &dyn Storage, + sender: &Addr, + ) -> Result<(), ContractError> { + let minter = self.minter.load(storage)?; + if minter.is_none() || minter.unwrap().ne(sender) { + return Err(ContractError::MintUnauthorized {}); + } + + Ok(()) + } + + /// Check whether a user owns a POAP. + /// * `user` - Address that will be checked. + pub fn assert_user_dont_own_a_poap( + &self, + storage: &dyn Storage, + user: &Addr, + ) -> Result<(), ContractError> { + // Check if this user have already minted a poap. + if !self + .cw721_base + .tokens + .idx + .owner + .prefix(user.clone()) + .is_empty(storage) + { + return Err(ContractError::PoapAlreadyMinted { + user: user.to_string(), + }); + } + + return Ok(()); + } + + /// Asserts if an user can mint a POAP. + /// * `user` - Address that will be checked. + pub fn assert_user_can_mint( + &self, + storage: &dyn Storage, + user: &Addr, + env: &Env, + ) -> Result<(), ContractError> { + // Check if the user is the minter. + if self.assert_is_minter(storage, user).is_ok() { + // The minter can always perform the mint operation. + return Ok(()); + } + + // Check if the user is the admin. + if self.assert_is_admin(storage, user).is_ok() { + // The admin can always perform the mint operation. + return Ok(()); + } + + // Check if mint is enabled + if !self.is_mintable.load(storage)? { + return Err(ContractError::MintDisabled {}); + } + + // Check if we have a mint start time + if let Some(start_time) = self.mint_start_time.load(storage)? { + // Check if the event has started. + if start_time.gt(&env.block.time) { + return Err(ContractError::MintTimeNotStarted {}); + } + } + + // Check if we have a mint end time + if let Some(end_time) = self.mint_end_time.load(storage)? { + // Check if the event is still in progress. + if env.block.time.ge(&end_time) { + return Err(ContractError::MintTimeAlreadyEnded {}); + } + } + + Ok(()) + } + + /// Asserts if the transfer is allowed. + pub fn assert_is_transferable(&self, storage: &dyn Storage) -> Result<(), ContractError> { + let is_transferable = self.is_transferable.load(storage)?; + if !is_transferable { + return Err(ContractError::TransferDisabled {}); + } + + Ok(()) + } +} diff --git a/contracts/poap/src/integration_tests.rs b/contracts/poap/src/integration_tests.rs deleted file mode 100644 index 179bdad0..00000000 --- a/contracts/poap/src/integration_tests.rs +++ /dev/null @@ -1,473 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::cw721_test_utils; - use crate::msg::{ - ExecuteMsg, QueryConfigResponse, QueryEventInfoResponse, QueryMintedAmountResponse, - QueryMsg, - }; - use crate::test_utils::{ - get_valid_init_msg, ADMIN, CREATOR, EVENT_END_SECONDS, EVENT_START_SECONDS, - INITIAL_BLOCK_TIME_SECONDS, MINTER, POAP_URI, USER, - }; - use cosmwasm_std::{Addr, Empty, Timestamp, Uint64}; - use cw721::{AllNftInfoResponse, NftInfoResponse, OwnerOfResponse, TokensResponse}; - use cw721_base::{MinterResponse, QueryMsg as Cw721QueryMsg}; - use cw721_poap::Metadata; - - use cw_multi_test::{Contract, ContractWrapper, Executor}; - use desmos_bindings::{ - mocks::mock_apps::{mock_desmos_app, DesmosApp}, - msg::DesmosMsg, - query::DesmosQuery, - }; - - fn contract_poap() -> Box> { - let contract = ContractWrapper::new( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ) - .with_reply(crate::contract::reply); - Box::new(contract) - } - - fn mock_app() -> DesmosApp { - mock_desmos_app() - } - - /// Uploads the contracts to the app. - /// Returns a pair of ids where the first refers to the cw721 - /// and the second to the poap. - fn store_contracts(app: &mut DesmosApp) -> (u64, u64) { - let cw721_code_id = app.store_code(cw721_test_utils::contract_cw721()); - let poap_code_id = app.store_code(contract_poap()); - - (cw721_code_id, poap_code_id) - } - - fn proper_instantiate() -> (DesmosApp, Addr) { - let mut app = mock_app(); - app.update_block(|block_info| { - block_info.time = Timestamp::from_seconds(INITIAL_BLOCK_TIME_SECONDS) - }); - let (cw721_code_id, poap_code_id) = store_contracts(&mut app); - let msg = get_valid_init_msg(cw721_code_id); - - let poap_contract_addr = app - .instantiate_contract( - poap_code_id, - Addr::unchecked(ADMIN), - &msg, - &[], - "poap_contract", - None, - ) - .unwrap(); - - (app, poap_contract_addr) - } - - #[test] - fn instantiate_with_invalid_cw721_code_id_error() { - let mut app = mock_app(); - let (cw721_code_id, poap_code_id) = store_contracts(&mut app); - let mut init_msg = get_valid_init_msg(cw721_code_id); - - init_msg.cw721_code_id = 42u64.into(); - - let init_result = app.instantiate_contract( - poap_code_id, - Addr::unchecked(ADMIN), - &init_msg, - &[], - "poap_contract", - None, - ); - assert!(init_result.is_err()); - } - - #[test] - fn instantiate_with_failing_cw721_contract_error() { - let mut app = mock_app(); - let (cw721_code_id, poap_code_id) = store_contracts(&mut app); - let failing_cw721_code_id = app.store_code(cw721_test_utils::failing_cw721()); - let mut init_msg = get_valid_init_msg(cw721_code_id); - - init_msg.cw721_code_id = failing_cw721_code_id.into(); - - let init_result = app.instantiate_contract( - poap_code_id, - Addr::unchecked(ADMIN), - &init_msg, - &[], - "poap_contract", - None, - ); - assert!(init_result.is_err()); - } - - #[test] - fn proper_contracts_instantiation() { - let (app, poap_contract_addr) = proper_instantiate(); - - let querier = app.wrap(); - - let poap_config: QueryConfigResponse = querier - .query_wasm_smart(&poap_contract_addr, &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(Addr::unchecked(ADMIN), poap_config.admin); - assert_eq!(Addr::unchecked(MINTER), poap_config.minter); - assert_eq!(false, poap_config.mint_enabled); - // 1 since is the first uploaded. - assert_eq!(Uint64::new(1), poap_config.cw721_contract_code); - - let poap_event_info: QueryEventInfoResponse = querier - .query_wasm_smart(&poap_contract_addr, &QueryMsg::EventInfo {}) - .unwrap(); - - assert_eq!(Addr::unchecked(CREATOR), poap_event_info.creator); - assert_eq!( - Timestamp::from_seconds(EVENT_START_SECONDS), - poap_event_info.start_time - ); - assert_eq!( - Timestamp::from_seconds(EVENT_END_SECONDS), - poap_event_info.end_time - ); - assert_eq!(POAP_URI, poap_event_info.poap_uri.as_str()); - - let cw721_minter_response: MinterResponse = querier - .query_wasm_smart( - &poap_config.cw721_contract, - &Cw721QueryMsg::::Minter {}, - ) - .unwrap(); - - // The cw721 minter should be the poap contract address. - assert_eq!(poap_contract_addr.to_string(), cw721_minter_response.minter) - } - - #[test] - fn mint_with_permission_properly() { - let (mut app, poap_contract_addr) = proper_instantiate(); - - // Change the chain time so that the event is started - app.update_block(|block_info| { - block_info.time = Timestamp::from_seconds(EVENT_START_SECONDS) - }); - - // Enable mint - let msg = ExecuteMsg::EnableMint {}; - app.execute_contract( - Addr::unchecked(ADMIN), - poap_contract_addr.clone(), - &msg, - &vec![], - ) - .unwrap(); - - // Mint should work since the event is started and the user is allowed to mint - let msg = ExecuteMsg::Mint {}; - app.execute_contract( - Addr::unchecked(USER), - poap_contract_addr.clone(), - &msg, - &vec![], - ) - .unwrap(); - - let querier = app.wrap(); - let response: QueryMintedAmountResponse = querier - .query_wasm_smart( - &poap_contract_addr, - &QueryMsg::MintedAmount { - user: USER.to_string(), - }, - ) - .unwrap(); - - assert_eq!(Addr::unchecked(USER), response.user); - assert_eq!(1, response.amount); - - let config: QueryConfigResponse = querier - .query_wasm_smart(&poap_contract_addr, &QueryMsg::Config {}) - .unwrap(); - - let querier = app.wrap(); - let response: TokensResponse = querier - .query_wasm_smart( - config.cw721_contract.as_str(), - &Cw721QueryMsg::::Tokens { - owner: USER.to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_eq!(1, response.tokens.len()); - - let minted_nft_info: NftInfoResponse = querier - .query_wasm_smart( - config.cw721_contract.as_str(), - &Cw721QueryMsg::::NftInfo { - token_id: "1".to_string(), - }, - ) - .unwrap(); - assert_eq!( - Metadata { - claimer: Addr::unchecked(USER) - }, - minted_nft_info.extension - ) - } - - #[test] - fn mint_to_with_permission_properly() { - let (mut app, poap_contract_addr) = proper_instantiate(); - - // Change the chain time so that the event is started - app.update_block(|block_info| { - block_info.time = Timestamp::from_seconds(EVENT_START_SECONDS) - }); - - // Enable mint - let msg = ExecuteMsg::EnableMint {}; - app.execute_contract( - Addr::unchecked(ADMIN), - poap_contract_addr.clone(), - &msg, - &vec![], - ) - .unwrap(); - - // Mint should work since the event is started and the user is allowed to mint - let msg = ExecuteMsg::MintTo { - recipient: USER.to_string(), - }; - app.execute_contract( - Addr::unchecked(ADMIN), - poap_contract_addr.clone(), - &msg, - &vec![], - ) - .unwrap(); - - let querier = app.wrap(); - let response: QueryMintedAmountResponse = querier - .query_wasm_smart( - &poap_contract_addr, - &QueryMsg::MintedAmount { - user: USER.to_string(), - }, - ) - .unwrap(); - - assert_eq!(Addr::unchecked(USER), response.user); - assert_eq!(1, response.amount); - - let config: QueryConfigResponse = querier - .query_wasm_smart(&poap_contract_addr, &QueryMsg::Config {}) - .unwrap(); - - let querier = app.wrap(); - let response: TokensResponse = querier - .query_wasm_smart( - config.cw721_contract.as_str(), - &Cw721QueryMsg::::Tokens { - owner: USER.to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_eq!(1, response.tokens.len()); - - let minted_nft_info: NftInfoResponse = querier - .query_wasm_smart( - config.cw721_contract.as_str(), - &Cw721QueryMsg::::NftInfo { - token_id: "1".to_string(), - }, - ) - .unwrap(); - assert_eq!( - Metadata { - claimer: Addr::unchecked(USER) - }, - minted_nft_info.extension - ) - } - - #[test] - fn query_tokens() { - let (mut app, poap_contract_addr) = proper_instantiate(); - - // Change the chain time so that the event is started - app.update_block(|block_info| { - block_info.time = Timestamp::from_seconds(EVENT_START_SECONDS) - }); - - // Enable mint - let msg = ExecuteMsg::EnableMint {}; - app.execute_contract( - Addr::unchecked(ADMIN), - poap_contract_addr.clone(), - &msg, - &vec![], - ) - .unwrap(); - - // Mint should work since the event is started and the user is allowed to mint - let msg = ExecuteMsg::MintTo { - recipient: USER.to_string(), - }; - app.execute_contract( - Addr::unchecked(ADMIN), - poap_contract_addr.clone(), - &msg, - &vec![], - ) - .unwrap(); - - let querier = app.wrap(); - let response: QueryMintedAmountResponse = querier - .query_wasm_smart( - &poap_contract_addr, - &QueryMsg::MintedAmount { - user: USER.to_string(), - }, - ) - .unwrap(); - - assert_eq!(Addr::unchecked(USER), response.user); - assert_eq!(1, response.amount); - - let config: QueryConfigResponse = querier - .query_wasm_smart(&poap_contract_addr, &QueryMsg::Config {}) - .unwrap(); - - let querier = app.wrap(); - let response: TokensResponse = querier - .query_wasm_smart( - &poap_contract_addr, - &QueryMsg::Tokens { - owner: USER.to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap(); - let cw721_response: TokensResponse = querier - .query_wasm_smart( - config.cw721_contract.as_str(), - &Cw721QueryMsg::::Tokens { - owner: USER.to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap(); - assert_eq!(cw721_response, response); - assert_eq!(1, response.tokens.len()); - } - - #[test] - fn query_nft_info() { - let (mut app, poap_contract_addr) = proper_instantiate(); - - // Change the chain time so that the event is started - app.update_block(|block_info| { - block_info.time = Timestamp::from_seconds(EVENT_START_SECONDS) - }); - - // Enable mint - let msg = ExecuteMsg::EnableMint {}; - app.execute_contract( - Addr::unchecked(ADMIN), - poap_contract_addr.clone(), - &msg, - &vec![], - ) - .unwrap(); - - // Mint should work since the event is started and the user is allowed to mint - let msg = ExecuteMsg::MintTo { - recipient: USER.to_string(), - }; - app.execute_contract( - Addr::unchecked(ADMIN), - poap_contract_addr.clone(), - &msg, - &vec![], - ) - .unwrap(); - - let querier = app.wrap(); - let response: QueryMintedAmountResponse = querier - .query_wasm_smart( - &poap_contract_addr, - &QueryMsg::MintedAmount { - user: USER.to_string(), - }, - ) - .unwrap(); - - assert_eq!(Addr::unchecked(USER), response.user); - assert_eq!(1, response.amount); - - let config: QueryConfigResponse = querier - .query_wasm_smart(&poap_contract_addr, &QueryMsg::Config {}) - .unwrap(); - - let querier = app.wrap(); - let response: TokensResponse = querier - .query_wasm_smart( - &poap_contract_addr, - &QueryMsg::Tokens { - owner: USER.to_string(), - start_after: None, - limit: None, - }, - ) - .unwrap(); - assert_eq!(1, response.tokens.len()); - let cw721_response: AllNftInfoResponse = querier - .query_wasm_smart( - config.cw721_contract.as_str(), - &Cw721QueryMsg::::AllNftInfo { - token_id: "1".to_string(), - include_expired: None, - }, - ) - .unwrap(); - let response: AllNftInfoResponse = querier - .query_wasm_smart( - &poap_contract_addr, - &QueryMsg::AllNftInfo { - token_id: "1".to_string(), - include_expired: None, - }, - ) - .unwrap(); - assert_eq!(cw721_response, response); - assert_eq!( - AllNftInfoResponse { - access: OwnerOfResponse { - owner: USER.to_string(), - approvals: vec![] - }, - info: NftInfoResponse { - token_uri: Some(POAP_URI.to_string()), - extension: Metadata { - claimer: Addr::unchecked(USER) - }, - } - }, - response - ); - } -} diff --git a/contracts/poap/src/lib.rs b/contracts/poap/src/lib.rs index b4ae5bc5..a6b5bebe 100644 --- a/contracts/poap/src/lib.rs +++ b/contracts/poap/src/lib.rs @@ -1,11 +1,69 @@ -pub mod contract; #[cfg(test)] -mod cw721_test_utils; -mod error; -mod integration_tests; +mod contract_tests; +pub mod error; +mod execute; pub mod msg; +mod query; pub mod state; -#[cfg(test)] -mod test_utils; + +use cosmwasm_std::Empty; pub use crate::error::ContractError; +pub use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +pub use crate::state::PoapContract; + +// This type is re-exported so that contracts interacting with this +// one don't need a direct dependency on cw721_base to use the API. +pub use cw721_base::MinterResponse; + +// These types are re-exported so that contracts interacting with this +// one don't need a direct dependency on cw_ownable to use the API. +// +// `Action` is used in `ExecuteMsg::UpdateOwnership`, `Ownership` is +// used in `QueryMsg::Ownership`, and `OwnershipError` is used in +// `ContractError::Ownership`. +pub use cw_ownable::{Action, Ownership, OwnershipError}; + +// This is a simple type to let us handle empty extensions +pub type Extension = Option; + +// Version info for migration +pub const CONTRACT_NAME: &str = "crates.io:poap-v2"; +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub mod entry { + use super::*; + #[cfg(not(feature = "library"))] + use cosmwasm_std::entry_point; + use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; + + #[cfg_attr(not(feature = "library"), entry_point)] + pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let tract = PoapContract::::default(); + tract.instantiate(deps, env, info, msg) + } + + #[cfg_attr(not(feature = "library"), entry_point)] + pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, + ) -> Result { + let tract = PoapContract::::default(); + tract.execute(deps, env, info, msg) + } + + #[cfg_attr(not(feature = "library"), entry_point)] + pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + let tract = PoapContract::::default(); + tract.query(deps, env, msg) + } +} diff --git a/contracts/poap/src/msg.rs b/contracts/poap/src/msg.rs index 59ea9f99..456a40e8 100644 --- a/contracts/poap/src/msg.rs +++ b/contracts/poap/src/msg.rs @@ -1,350 +1,332 @@ -use crate::ContractError; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Timestamp, Uint64}; -use cw721::{AllNftInfoResponse, TokensResponse}; -use cw721_base::InstantiateMsg as Cw721InstantiateMsg; -use cw721_poap::Metadata; -use url::Url; +use cosmwasm_std::{Binary, Timestamp}; +use cw721_base::{ExecuteMsg as Cw721BaseExecuteMsg, QueryMsg as Cw721BaseQueryMsg}; +use cw_ownable::{cw_ownable_execute, cw_ownable_query}; +use cw_utils::Expiration; +use schemars::JsonSchema; +use std::fmt::Debug; #[cw_serde] -#[schemars(rename = "PoapInstantiateMsg", title = "InstantiateMsg")] pub struct InstantiateMsg { - /// Address of who will have the right to administer the contract. - pub admin: String, - /// Address of who can call the [`ExecuteMsg::MintTo`] other then the admin. - pub minter: String, - /// Id of the CW721 contract to initialize together with this contract. - pub cw721_code_id: Uint64, - /// Initialization message that will be sent to the CW721 contract. - pub cw721_instantiate_msg: Cw721InstantiateMsg, - /// Information about the event. - pub event_info: EventInfo, + /// Name of the POAP contract. + pub name: String, + /// Symbol of the POAP contract. + pub symbol: String, + /// The URI where users can view the associated metadata for the POAPs, + /// ideally following the ERC-721 metadata scheme in a JSON file. + pub metadata_uri: String, + /// Who controls the contract. + /// If None will be used the address of who is instantiating the contract. + pub admin: Option, + /// Additional address that is allowed to mint tokens on behalf of other users. + pub minter: Option, + /// Specifies whether each POAP can be transferred from one user to another. + pub is_transferable: bool, + /// Indicates whether users can mint the POAPs. + pub is_mintable: bool, + /// Identifies the timestamp at which the minting of the POAP will be enabled. + /// If not set, the minting is always enabled. + pub mint_start_time: Option, + /// Identifies the timestamp at which the minting of the POAP will be disabled. + /// If not set, the minting will never end. + pub mint_end_time: Option, } +/// This is like Cw721ExecuteMsg but we add a Mint command for an owner +/// to make this stand-alone. You will likely want to remove mint and +/// use other control logic in any contract that inherits this. +#[cw_ownable_execute] #[cw_serde] -pub struct EventInfo { - /// User that created the event. - pub creator: String, - /// Time at which the event begins. - pub start_time: Timestamp, - /// Time at which the event ends. - pub end_time: Timestamp, - /// Max amount of poap that a single user can mint. - pub per_address_limit: u32, - /// Identifies a valid IPFS URI corresponding to where the assets and metadata of the POAPs are stored. - pub poap_uri: String, -} - -#[cw_serde] -pub enum ExecuteMsg { - /// Allows the contract's admin to enable the [`ExecuteMsg::Mint`]. - EnableMint {}, - /// Allows the contract's admin to disable the [`ExecuteMsg::Mint`]. - DisableMint {}, - /// If the mint is enabled, allow the user to mint the poap by themself. - /// It's disabled before the start of the event and after the event's end. - Mint {}, - /// Allows the contract's admin or the minter to mint a POAP for a specific recipient. - /// It's disabled before the start of the event and after the event's end. - MintTo { recipient: String }, - /// Message that allows the event's creator to change the time frame of the event - /// if it's not started or finished. - UpdateEventInfo { - start_time: Timestamp, - end_time: Timestamp, +pub enum ExecuteMsg { + /// Transfer is a base message to move a token to another account without triggering actions. + TransferNft { recipient: String, token_id: String }, + /// Send is a base message to transfer a token to a contract and trigger an action + /// on the receiving contract. + SendNft { + contract: String, + token_id: String, + msg: Binary, }, - /// Allows the contract's admin to transfer the admin rights to another user. - UpdateAdmin { new_admin: String }, - /// Allows the contract's admin to transfer the minting rights to another user. - UpdateMinter { new_minter: String }, + /// Allows operator to transfer / send the token from the owner's account. + /// If expiration is set, then this allowance has a time/height limit. + Approve { + spender: String, + token_id: String, + expires: Option, + }, + /// Remove previously granted Approval + Revoke { spender: String, token_id: String }, + /// Allows operator to transfer / send any token from the owner's account. + /// If expiration is set, then this allowance has a time/height limit. + ApproveAll { + operator: String, + expires: Option, + }, + /// Remove previously granted ApproveAll permission. + RevokeAll { operator: String }, + /// Mint a new POAP for the caller. + Mint { extension: T }, + /// Mint a new POAP for the provided users, can + /// only be called from the contract minter. + MintTo { users: Vec, extension: T }, + /// Burn a POAP the sender has access to. + Burn { token_id: String }, + /// Allow to update the user with the mint permissions, + /// can only be called from the contract admin. + UpdateMinter { minter: Option }, + /// Sets if the users can mint their POAP, + /// can only be called from the contract admin. + SetMintable { mintable: bool }, + /// Sets if the users can transfer their POAP, + /// can only be called from the contract admin. + SetTransferable { transferable: bool }, + /// Sets the time period of when the POAP can be minted from + /// the users, can only be called from the contract admin. + SetMintStartEndTime { + /// Identifies the timestamp at which the minting of the POAP will be enabled. + /// If None, the minting is always enabled. + start_time: Option, + /// Identifies the timestamp at which the minting of the POAP will be disabled. + /// If None, the minting will never end. + end_time: Option, + }, + /// Extension msg. + Extension { msg: E }, } +#[cw_ownable_query] #[cw_serde] #[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the configuration info as a [`QueryConfigResponse`]. - #[returns(QueryConfigResponse)] - Config {}, - /// Returns the event info as a [`QueryEventInfoResponse`]. - #[returns(QueryEventInfoResponse)] - EventInfo {}, - /// Returns the amount of poaps minted from `user` as [`QueryMintedAmountResponse`]. - #[returns(QueryMintedAmountResponse)] - MintedAmount { user: String }, - /// Returns the nft info with approvals from cw721 contract as a [`AllNftInfoResponse`] - #[returns(AllNftInfoResponse)] +pub enum QueryMsg { + /// Return the owner of the given token, error if token does not exist. + #[returns(cw721::OwnerOfResponse)] + OwnerOf { + token_id: String, + /// unset or false will filter out expired approvals, you must set to true to see them + include_expired: Option, + }, + /// Return operator that can access all of the owner's tokens. + #[returns(cw721::ApprovalResponse)] + Approval { + token_id: String, + spender: String, + include_expired: Option, + }, + /// Return approvals that a token has. + #[returns(cw721::ApprovalsResponse)] + Approvals { + token_id: String, + include_expired: Option, + }, + /// Return approval of a given operator for all tokens of an owner, error if not set. + #[returns(cw721::OperatorResponse)] + Operator { + owner: String, + operator: String, + include_expired: Option, + }, + /// List all operators that can access all of the owner's tokens. + #[returns(cw721::OperatorsResponse)] + AllOperators { + owner: String, + /// unset or false will filter out expired items, you must set to true to see them. + include_expired: Option, + start_after: Option, + limit: Option, + }, + /// Total number of tokens issued. + #[returns(cw721::NumTokensResponse)] + NumTokens {}, + /// With MetaData Extension. + /// Returns top-level metadata about the contract. + #[returns(cw721::ContractInfoResponse)] + ContractInfo {}, + /// With MetaData Extension. + /// Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* + /// but directly from the contract. + #[returns(cw721::NftInfoResponse)] + NftInfo { token_id: String }, + /// With MetaData Extension. + /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization + /// for clients. + #[returns(cw721::AllNftInfoResponse)] AllNftInfo { token_id: String, + /// unset or false will filter out expired approvals, you must set to true to see them. include_expired: Option, }, - /// Returns all the tokens ids owned by the given owner from cw721 contract as a [`TokensResponse`] - #[returns(TokensResponse)] + /// With Enumerable extension. + /// Returns all tokens owned by the given address, [] if unset. + #[returns(cw721::TokensResponse)] Tokens { owner: String, start_after: Option, limit: Option, }, + /// With Enumerable extension. + /// Requires pagination. Lists all token_ids controlled by the contract. + #[returns(cw721::TokensResponse)] + AllTokens { + start_after: Option, + limit: Option, + }, + /// Return the minter. + #[returns(cw721_base::MinterResponse)] + Minter {}, + /// Returns if the POAP minting is enabled. + #[returns(IsMintableResponse)] + IsMintable {}, + /// Returns if the POAP is transferable. + #[returns(IsTransferableResponse)] + IsTransferable {}, + /// Return the mint start and end time. + #[returns(MintStartEndTimeResponse)] + MintStartEndTime {}, + /// Extension query. + #[returns(())] + Extension { msg: Q }, } -/// Response to [`QueryMsg::Config`]. +/// Response to [`QueryMsg::IsMintable`]. #[cw_serde] -pub struct QueryConfigResponse { - /// Address of the contract administrator. - pub admin: Addr, - /// Address of the entity that is allowed to use [`ExecuteMsg::MintTo`]. - pub minter: Addr, - /// Tells if the users can execute the [`ExecuteMsg::Mint`]. - pub mint_enabled: bool, - /// The maximus number of poap that an user can request. - pub per_address_limit: u32, - /// Id of the cw721 contract that this contract has initialized. - pub cw721_contract_code: Uint64, - /// Address of the cw721 contract that this contract is using to - /// mint the poaps. - pub cw721_contract: Addr, +pub struct IsMintableResponse { + pub is_mintable: bool, } -/// Response to [`QueryMsg::EventInfo`]. +/// Response to [`QueryMsg::IsTransferable`]. #[cw_serde] -pub struct QueryEventInfoResponse { - /// Address of who created the event. - pub creator: Addr, - /// Time at which the event starts. - pub start_time: Timestamp, - /// Time at which the event ends. - pub end_time: Timestamp, - /// IPFS uri where the event's metadata are stored - pub poap_uri: String, +pub struct IsTransferableResponse { + pub is_transferable: bool, } -/// Response to [`QueryMsg::MintedAmount`]. +/// Response to [`QueryMsg::MintStartEndTime`]. #[cw_serde] -pub struct QueryMintedAmountResponse { - /// Address for which the request was made. - pub user: Addr, - /// Amount of poaps minted from the user. - pub amount: u32, -} - -impl InstantiateMsg { - /// Checks that the data inside the message are coherent. - /// NOTE: This function don't checks if the address are valid. - pub fn validate(&self) -> Result<(), ContractError> { - // Check that the end time is after the start time - if self.event_info.start_time >= self.event_info.end_time { - return Err(ContractError::StartTimeAfterEndTime { - start: self.event_info.start_time.to_owned(), - end: self.event_info.end_time.to_owned(), - }); - } - - // Check per address limit - if self.event_info.per_address_limit == 0 { - return Err(ContractError::InvalidPerAddressLimit {}); - } - - // Check that the poap uri is a valid IPFS url - let poap_uri = Url::parse(&self.event_info.poap_uri) - .map_err(|_err| ContractError::InvalidPoapUri {})?; - if poap_uri.scheme() != "ipfs" { - return Err(ContractError::InvalidPoapUri {}); - } - - Ok(()) - } +pub struct MintStartEndTimeResponse { + pub start_time: Option, + pub end_time: Option, } -impl ExecuteMsg { - /// Checks that the data inside the message are coherent. - /// NOTE: This function don't checks if the address are valid. - pub fn validate(&self) -> Result<(), ContractError> { - match &self { - ExecuteMsg::UpdateEventInfo { - start_time, - end_time, - } => { - if start_time >= end_time { - Err(ContractError::StartTimeAfterEndTime { - start: start_time.to_owned(), - end: end_time.to_owned(), - }) - } else { - Ok(()) - } +impl From> for Cw721BaseExecuteMsg +where + T: Debug, + E: Debug, +{ + #[cfg(not(tarpaulin_include))] + fn from(execute_msg: ExecuteMsg) -> Self { + match execute_msg { + ExecuteMsg::TransferNft { + recipient, + token_id, + } => Cw721BaseExecuteMsg::TransferNft { + recipient, + token_id, + }, + ExecuteMsg::SendNft { + msg, + token_id, + contract, + } => Cw721BaseExecuteMsg::SendNft { + msg, + token_id, + contract, + }, + ExecuteMsg::Approve { + token_id, + spender, + expires, + } => Cw721BaseExecuteMsg::Approve { + token_id, + spender, + expires, + }, + ExecuteMsg::Revoke { spender, token_id } => { + Cw721BaseExecuteMsg::Revoke { spender, token_id } } - _ => Ok(()), + ExecuteMsg::ApproveAll { expires, operator } => { + Cw721BaseExecuteMsg::ApproveAll { operator, expires } + } + ExecuteMsg::RevokeAll { operator } => Cw721BaseExecuteMsg::RevokeAll { operator }, + ExecuteMsg::Burn { token_id } => Cw721BaseExecuteMsg::Burn { token_id }, + ExecuteMsg::Extension { msg } => Cw721BaseExecuteMsg::Extension { msg }, + _ => unreachable!("cannot convert {:?} to Cw721BaseExecuteMsg", execute_msg), } } } -#[cfg(test)] -mod tests { - use crate::msg::{EventInfo, ExecuteMsg, InstantiateMsg}; - use crate::ContractError; - use cosmwasm_std::Timestamp; - use cw721_base::InstantiateMsg as Cw721InstantiateMsg; - - #[test] - fn instantiate_with_start_time_after_end_time_error() { - let start = Timestamp::from_seconds(2); - let end = Timestamp::from_seconds(1); - let msg = InstantiateMsg { - admin: "".to_string(), - minter: "".to_string(), - cw721_code_id: 0u64.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - name: "".to_string(), - minter: "".to_string(), - symbol: "".to_string(), +impl From> for Cw721BaseQueryMsg +where + Q: JsonSchema + Debug, +{ + #[cfg(not(tarpaulin_include))] + fn from(query_msg: QueryMsg) -> Self { + match query_msg { + QueryMsg::OwnerOf { + token_id, + include_expired, + } => Cw721BaseQueryMsg::OwnerOf { + token_id, + include_expired, }, - event_info: EventInfo { - creator: "".to_string(), - start_time: start.clone(), - end_time: end.clone(), - per_address_limit: 1, - poap_uri: "ipfs://domain.com".to_string(), + QueryMsg::Approval { + include_expired, + token_id, + spender, + } => Cw721BaseQueryMsg::Approval { + include_expired, + token_id, + spender, }, - }; - - assert_eq!( - ContractError::StartTimeAfterEndTime { start, end }, - msg.validate().unwrap_err() - ); - } - - #[test] - fn instantiate_with_start_time_equal_end_time_error() { - let start = Timestamp::from_seconds(1); - let end = Timestamp::from_seconds(1); - let msg = InstantiateMsg { - admin: "".to_string(), - minter: "".to_string(), - cw721_code_id: 0u64.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - name: "".to_string(), - minter: "".to_string(), - symbol: "".to_string(), + QueryMsg::Approvals { + token_id, + include_expired, + } => Cw721BaseQueryMsg::Approvals { + token_id, + include_expired, }, - event_info: EventInfo { - creator: "".to_string(), - start_time: start.clone(), - end_time: end.clone(), - per_address_limit: 1, - poap_uri: "ipfs://domain.com".to_string(), + QueryMsg::Operator { + operator, + owner, + include_expired, + } => Cw721BaseQueryMsg::Operator { + operator, + owner, + include_expired, }, - }; - - assert_eq!( - ContractError::StartTimeAfterEndTime { start, end }, - msg.validate().unwrap_err() - ); - } - - #[test] - fn instantiate_with_invalid_per_address_limit_error() { - let msg = InstantiateMsg { - admin: "".to_string(), - minter: "".to_string(), - cw721_code_id: 0u64.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - name: "".to_string(), - minter: "".to_string(), - symbol: "".to_string(), + QueryMsg::AllOperators { + owner, + limit, + include_expired, + start_after, + } => Cw721BaseQueryMsg::AllOperators { + owner, + limit, + include_expired, + start_after, }, - event_info: EventInfo { - creator: "".to_string(), - start_time: Timestamp::from_seconds(1), - end_time: Timestamp::from_seconds(2), - per_address_limit: 0, - poap_uri: "ipfs://domain.com".to_string(), + QueryMsg::NumTokens {} => Cw721BaseQueryMsg::NumTokens {}, + QueryMsg::ContractInfo {} => Cw721BaseQueryMsg::ContractInfo {}, + QueryMsg::NftInfo { token_id } => Cw721BaseQueryMsg::NftInfo { token_id }, + QueryMsg::AllNftInfo { + token_id, + include_expired, + } => Cw721BaseQueryMsg::AllNftInfo { + token_id, + include_expired, }, - }; - - assert_eq!( - ContractError::InvalidPerAddressLimit {}, - msg.validate().unwrap_err() - ); - } - - #[test] - fn instantiate_with_invalid_poap_uri_error() { - let msg = InstantiateMsg { - admin: "".to_string(), - minter: "".to_string(), - cw721_code_id: 0u64.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - name: "".to_string(), - minter: "".to_string(), - symbol: "".to_string(), - }, - event_info: EventInfo { - creator: "".to_string(), - start_time: Timestamp::from_seconds(1), - end_time: Timestamp::from_seconds(2), - per_address_limit: 1, - poap_uri: "invalid_base_poap_uri".to_string(), - }, - }; - - assert_eq!( - ContractError::InvalidPoapUri {}, - msg.validate().unwrap_err() - ); - } - - #[test] - fn instantiate_with_non_ipfs_poap_uri_error() { - let msg = InstantiateMsg { - admin: "".to_string(), - minter: "".to_string(), - cw721_code_id: 0u64.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - name: "".to_string(), - minter: "".to_string(), - symbol: "".to_string(), + QueryMsg::Tokens { + owner, + start_after, + limit, + } => Cw721BaseQueryMsg::Tokens { + owner, + start_after, + limit, }, - event_info: EventInfo { - creator: "".to_string(), - start_time: Timestamp::from_seconds(1), - end_time: Timestamp::from_seconds(2), - per_address_limit: 1, - poap_uri: "https://domain.com".to_string(), - }, - }; - - assert_eq!( - ContractError::InvalidPoapUri {}, - msg.validate().unwrap_err() - ); - } - - #[test] - fn update_event_info_start_time_after_end_time_error() { - let start = Timestamp::from_seconds(2); - let end = Timestamp::from_seconds(1); - let msg = ExecuteMsg::UpdateEventInfo { - start_time: start.clone(), - end_time: end.clone(), - }; - - assert_eq!( - ContractError::StartTimeAfterEndTime { start, end }, - msg.validate().unwrap_err() - ); - } - - #[test] - fn update_event_info_start_time_equal_end_time_error() { - let start = Timestamp::from_seconds(1); - let end = Timestamp::from_seconds(1); - let msg = ExecuteMsg::UpdateEventInfo { - start_time: start.clone(), - end_time: end.clone(), - }; - - assert_eq!( - ContractError::StartTimeAfterEndTime { start, end }, - msg.validate().unwrap_err() - ); + QueryMsg::AllTokens { start_after, limit } => { + Cw721BaseQueryMsg::AllTokens { start_after, limit } + } + QueryMsg::Extension { msg } => Cw721BaseQueryMsg::Extension { msg }, + _ => unreachable!("cannot convert {:?} to Cw721BaseQueryMsg", query_msg), + } } } diff --git a/contracts/poap/src/query.rs b/contracts/poap/src/query.rs new file mode 100644 index 00000000..57fecfff --- /dev/null +++ b/contracts/poap/src/query.rs @@ -0,0 +1,69 @@ +use crate::msg::{IsMintableResponse, IsTransferableResponse, MintStartEndTimeResponse, QueryMsg}; +use crate::state::PoapContract; +use cosmwasm_std::{to_binary, Binary, CustomMsg, Deps, Env, StdResult}; +use cw721_base::MinterResponse; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::fmt::Debug; + +impl<'a, T, C, E, Q> PoapContract<'a, T, C, E, Q> +where + T: Serialize + DeserializeOwned + Clone + Debug, + C: CustomMsg, + E: CustomMsg, + Q: CustomMsg, +{ + pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Minter {} => to_binary(&self.minter(deps, env)?), + QueryMsg::IsMintable {} => to_binary(&self.is_mintable(deps, env)?), + QueryMsg::IsTransferable {} => to_binary(&self.is_transferable(deps, env)?), + QueryMsg::MintStartEndTime {} => to_binary(&self.mint_start_end_time(deps, env)?), + _ => self.cw721_base.query(deps, env, msg.into()), + } + } +} + +impl<'a, T, C, E, Q> PoapContract<'a, T, C, E, Q> +where + T: Serialize + DeserializeOwned + Clone + Debug, + C: CustomMsg, + E: CustomMsg, + Q: CustomMsg, +{ + /// Gets the address of the contract minter. + pub fn minter(&self, deps: Deps, _env: Env) -> StdResult { + Ok(MinterResponse { + minter: self + .minter + .load(deps.storage)? + .map(|minter| minter.to_string()), + }) + } + + /// Gets if the POAP can be minted from the users. + pub fn is_mintable(&self, deps: Deps, _env: Env) -> StdResult { + Ok(IsMintableResponse { + is_mintable: self.is_mintable.load(deps.storage)?, + }) + } + + /// Gets if the POAP can be transferred between users. + pub fn is_transferable(&self, deps: Deps, _env: Env) -> StdResult { + Ok(IsTransferableResponse { + is_transferable: self.is_transferable.load(deps.storage)?, + }) + } + + /// Gets the time period in which it is possible to mint the POAP. + pub fn mint_start_end_time( + &self, + deps: Deps, + _env: Env, + ) -> StdResult { + Ok(MintStartEndTimeResponse { + start_time: self.mint_start_time.load(deps.storage)?, + end_time: self.mint_end_time.load(deps.storage)?, + }) + } +} diff --git a/contracts/poap/src/state.rs b/contracts/poap/src/state.rs index 7f635558..c0ce2ca2 100644 --- a/contracts/poap/src/state.rs +++ b/contracts/poap/src/state.rs @@ -1,126 +1,73 @@ -use cosmwasm_schema::cw_serde; - -use cosmwasm_std::{Addr, Timestamp}; -use cw_storage_plus::{Item, Map}; - -#[cw_serde] -pub struct Config { - pub admin: Addr, - pub minter: Addr, - pub mint_enabled: bool, - pub per_address_limit: u32, - pub cw721_code_id: u64, +use cosmwasm_std::{Addr, CustomMsg, Timestamp}; +use cw721_base::Cw721Contract; +use cw_storage_plus::Item; +use serde::de::DeserializeOwned; +use serde::Serialize; + +pub struct PoapContract<'a, T, C, E, Q> +where + T: Serialize + DeserializeOwned + Clone, + Q: CustomMsg, + E: CustomMsg, +{ + pub cw721_base: Cw721Contract<'a, T, C, E, Q>, + /// The URI where users can view the associated metadata for the POAPs, + /// ideally following the ERC-721 metadata scheme in a JSON file. + pub metadata_uri: Item<'a, String>, + /// Additional address that is allowed to mint tokens on behalf of other users. + pub minter: Item<'a, Option>, + /// Specifies whether each POAP can be transferred from one user to another. + pub is_transferable: Item<'a, bool>, + /// Indicates whether users can mint the POAPs. + pub is_mintable: Item<'a, bool>, + /// Identifies the timestamp at which the minting of the POAP will be enabled. + /// If not set, the minting is always enabled. + pub mint_start_time: Item<'a, Option>, + /// Identifies the timestamp at which the minting of the POAP will be disabled. + /// If not set, the minting will never end. + pub mint_end_time: Item<'a, Option>, } -#[cw_serde] -#[schemars(rename = "StateEventInfo")] -pub struct EventInfo { - pub creator: Addr, - pub start_time: Timestamp, - pub end_time: Timestamp, - pub poap_uri: String, -} - -#[cw_serde] -pub struct Metadata { - pub claimer: Addr, -} - -pub const CONFIG: Item = Item::new("config"); -pub const EVENT_INFO: Item = Item::new("event_info"); -pub const CW721_ADDRESS: Item = Item::new("cw721_address"); -pub const NEXT_POAP_ID: Item = Item::new("nex_poap_id"); -pub const MINTER_ADDRESS: Map = Map::new("minter_address"); - -impl EventInfo { - /// Checks if the event has already started. - /// * `timestamp` - Reference time used to check if the event has started. - pub fn is_started(&self, timestamp: &Timestamp) -> bool { - &self.start_time <= timestamp - } - - /// Checks if the event is ended. - /// * `timestamp` - Reference time used to check if the event is ended. - pub fn is_ended(&self, timestamp: &Timestamp) -> bool { - timestamp >= &self.end_time - } - - /// Checks if the event is in progress. - /// * `timestamp` - Reference time used to check if the event is in progress. - pub fn in_progress(&self, timestamp: &Timestamp) -> bool { - self.is_started(timestamp) && !self.is_ended(timestamp) - } -} - -#[cfg(test)] -mod tests { - use crate::state::EventInfo; - use cosmwasm_std::{Addr, Timestamp}; - - fn mock_event_info(start: u64, end: u64) -> EventInfo { - EventInfo { - creator: Addr::unchecked(""), - start_time: Timestamp::from_seconds(start), - end_time: Timestamp::from_seconds(end), - poap_uri: "".to_string(), +impl<'a, T, C, E, Q> PoapContract<'a, T, C, E, Q> +where + T: Serialize + DeserializeOwned + Clone, + E: CustomMsg, + Q: CustomMsg, +{ + fn new( + metadata_uri_key: &'a str, + minter_key: &'a str, + is_transferable_key: &'a str, + is_mintable_key: &'a str, + mint_start_time_key: &'a str, + mint_end_time_key: &'a str, + ) -> Self { + Self { + cw721_base: Cw721Contract::default(), + metadata_uri: Item::new(metadata_uri_key), + minter: Item::new(minter_key), + is_transferable: Item::new(is_transferable_key), + is_mintable: Item::new(is_mintable_key), + mint_start_time: Item::new(mint_start_time_key), + mint_end_time: Item::new(mint_end_time_key), } } +} - #[test] - fn event_is_started() { - let current_time = Timestamp::from_seconds(300); - let event_info = mock_event_info(200, 400); - - assert!(event_info.is_started(¤t_time)); - - // Test edge case start time = current time - assert!(event_info.is_started(&event_info.start_time)); - } - - #[test] - fn event_not_started() { - let current_time = Timestamp::from_seconds(300); - let event_info = mock_event_info(350, 400); - - assert!(!event_info.is_started(¤t_time)); - } - - #[test] - fn event_is_ended() { - let current_time = Timestamp::from_seconds(300); - let event_info = mock_event_info(200, 250); - - assert!(event_info.is_ended(¤t_time)); - - // Test edge end time = current time - assert!(event_info.is_ended(&event_info.end_time)); - } - - #[test] - fn event_not_ended() { - let current_time = Timestamp::from_seconds(300); - let event_info = mock_event_info(200, 400); - - assert!(!event_info.is_ended(¤t_time)); - } - - #[test] - fn event_in_progress() { - let current_time = Timestamp::from_seconds(150); - let event_info = mock_event_info(100, 200); - - assert!(event_info.in_progress(¤t_time)); - // Test edge case current time = start time - assert!(event_info.in_progress(&event_info.start_time)); - } - - #[test] - fn event_not_in_progress() { - let current_time = Timestamp::from_seconds(500); - let event_info = mock_event_info(100, 200); - - assert!(!event_info.in_progress(¤t_time)); - // Test edge case current time = end time - assert!(!event_info.in_progress(&event_info.end_time)); +impl Default for PoapContract<'static, T, C, E, Q> +where + T: Serialize + DeserializeOwned + Clone, + E: CustomMsg, + Q: CustomMsg, +{ + fn default() -> Self { + Self::new( + "poap_metadata_uri", + "minter", + "is_transferable", + "is_mintable", + "mint_start_time", + "mint_end_time", + ) } } diff --git a/contracts/poap/src/test_utils.rs b/contracts/poap/src/test_utils.rs deleted file mode 100644 index d7454fb1..00000000 --- a/contracts/poap/src/test_utils.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::msg::{EventInfo, InstantiateMsg}; -use cosmwasm_std::Timestamp; -use cw721_base::InstantiateMsg as Cw721InstantiateMsg; - -pub const INITIAL_BLOCK_TIME_SECONDS: u64 = 3600; -pub const EVENT_START_SECONDS: u64 = INITIAL_BLOCK_TIME_SECONDS + 3600; -pub const EVENT_END_SECONDS: u64 = EVENT_START_SECONDS + 3600; -pub const CREATOR: &str = "creator"; -pub const ADMIN: &str = "admin"; -pub const MINTER: &str = "minter"; -pub const USER: &str = "user"; -pub const POAP_URI: &str = "ipfs://poap-uri"; - -pub fn get_valid_init_msg(cw721_code_id: u64) -> InstantiateMsg { - let start_time = Timestamp::from_seconds(EVENT_START_SECONDS); - let end_time = Timestamp::from_seconds(EVENT_END_SECONDS); - - InstantiateMsg { - admin: ADMIN.to_string(), - minter: MINTER.to_string(), - cw721_code_id: cw721_code_id.into(), - cw721_instantiate_msg: Cw721InstantiateMsg { - name: "test-poap".to_string(), - symbol: "poap".to_string(), - minter: "".to_string(), - }, - event_info: EventInfo { - creator: CREATOR.to_string(), - start_time, - end_time, - per_address_limit: 2, - poap_uri: POAP_URI.to_string(), - }, - } -} diff --git a/contracts/remarkables/src/contract.rs b/contracts/remarkables/src/contract.rs index 81889bf6..56ef9610 100644 --- a/contracts/remarkables/src/contract.rs +++ b/contracts/remarkables/src/contract.rs @@ -498,7 +498,7 @@ mod tests { assert_eq!( instantiate(deps.as_mut(), env, info, invalid_msg).unwrap_err(), ContractError::Std(StdError::generic_err( - "Invalid input: human address too short" + "Invalid input: human address too short for this mock implementation (must be >= 3)." )) ) } @@ -928,7 +928,7 @@ mod tests { assert_eq!( execute(deps.as_mut(), env, info, msg).unwrap_err(), ContractError::Std(StdError::generic_err( - "Invalid input: human address too short" + "Invalid input: human address too short for this mock implementation (must be >= 3)." )) ) } diff --git a/contracts/tips/src/contract.rs b/contracts/tips/src/contract.rs index 553ed92e..13243988 100644 --- a/contracts/tips/src/contract.rs +++ b/contracts/tips/src/contract.rs @@ -750,7 +750,7 @@ mod tests { assert_eq!( ContractError::Std(StdError::generic_err( - "Invalid input: human address too short" + "Invalid input: human address too short for this mock implementation (must be >= 3)." )), tip_error ); @@ -1589,7 +1589,7 @@ mod tests { assert_eq!( ContractError::Std(StdError::generic_err( - "Invalid input: human address too short" + "Invalid input: human address too short for this mock implementation (must be >= 3)." )), error ); diff --git a/docs/architecture/adr-006-poap-v2.md b/docs/architecture/adr-006-poap-v2.md index 043890b4..e6a2cac7 100644 --- a/docs/architecture/adr-006-poap-v2.md +++ b/docs/architecture/adr-006-poap-v2.md @@ -6,7 +6,7 @@ ## Status -PROPOSED Not Implemented +IMPLEMENTED ## Abstract