diff --git a/.circleci/config.yml b/.circleci/config.yml index 87c65e9bf..a80f31d4e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,8 @@ workflows: - contract_open_edition_factory - contract_open_edition_minter - contract_whitelist - - sg-eth-airdrop + - rekt-airdrop + - dydx-airdrop - test-suite - package_sg_std - package_sg_utils @@ -424,10 +425,10 @@ jobs: - target key: cargocache-sg-utils-rust:1.74.0-{{ checksum "~/project/Cargo.lock" }} - sg-eth-airdrop: + rekt-airdrop: docker: - image: rust:1.74.0 - working_directory: ~/project/contracts/sg-eth-airdrop + working_directory: ~/project/contracts/rekt-airdrop steps: - checkout: path: ~/project @@ -436,7 +437,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-sg-eth-airdrop-rust:1.74.0-{{ checksum "~/project/Cargo.lock" }} + - cargocache-rekt-airdrop-rust:1.74.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Unit Tests environment: @@ -458,7 +459,43 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-sg-eth-airdrop-rust:1.74.0-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-rekt-airdrop-rust:1.74.0-{{ checksum "~/project/Cargo.lock" }} + + dydx-airdrop: + docker: + - image: rust:1.74.0 + working_directory: ~/project/contracts/dydx-airdrop + steps: + - checkout: + path: ~/project + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - cargocache-dydx-airdrop-rust:1.74.0-{{ checksum "~/project/Cargo.lock" }} + - run: + name: Unit Tests + environment: + RUST_BACKTRACE: 1 + command: cargo unit-test --locked + - run: + name: Build and run schema generator + command: cargo schema --locked + # - run: + # name: Ensure checked-in schemas are up-to-date + # command: | + # CHANGES_IN_REPO=$(git status --porcelain) + # if [[ -n "$CHANGES_IN_REPO" ]]; then + # echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + # git status && git --no-pager diff + # exit 1 + # fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target + key: cargocache-dydx-airdrop-rust:1.74.0-{{ checksum "~/project/Cargo.lock" }} lint: docker: @@ -528,7 +565,12 @@ jobs: echo "Compiling `basename $C`..." (cd $C && cargo build --release --lib --target wasm32-unknown-unknown --locked) done - for C in ./contracts/sg-eth-airdrop/ + for C in ./contracts/rekt-airdrop/ + do + echo "Compiling `basename $C`..." + (cd $C && cargo build --release --lib --target wasm32-unknown-unknown --locked) + done + for C in ./contracts/dydx-airdrop/ do echo "Compiling `basename $C`..." (cd $C && cargo build --release --lib --target wasm32-unknown-unknown --locked) diff --git a/Cargo.lock b/Cargo.lock index 78a30346d..bb3c5438a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -349,7 +349,7 @@ dependencies = [ "schemars", "semver", "serde", - "sg-std", + "sg-std 3.2.0", "sg1", "sg2", "thiserror", @@ -369,12 +369,12 @@ dependencies = [ "cw721-base 0.18.0", "schemars", "serde", - "sg-std", + "sg-std 3.2.0", "sg1", "sg2", "sg4", - "sg721", - "sg721-base", + "sg721 3.5.0", + "sg721-base 3.5.0", "thiserror", "url", ] @@ -1046,6 +1046,21 @@ dependencies = [ "cosmwasm-std", ] +[[package]] +name = "cw-controllers" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3ababe9f0a473c2d878b7c973eee08e05e5d768253fbb08d475daeadba503" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-controllers" version = "0.16.0" @@ -1121,6 +1136,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cw-storage-plus" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6cf70ef7686e2da9ad7b067c5942cd3e88dd9453f7af42f54557f8af300fb0" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-storage-plus" version = "0.16.0" @@ -1143,6 +1169,21 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae0b69fa7679de78825b4edeeec045066aa2b2c4b6e063d80042e565bb4da5c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 0.15.1", + "schemars", + "semver", + "serde", + "thiserror", +] + [[package]] name = "cw-utils" version = "0.16.0" @@ -1173,6 +1214,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw2" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5abb8ecea72e09afff830252963cb60faf945ce6cef2c20a43814516082653da" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "schemars", + "serde", +] + [[package]] name = "cw2" version = "0.16.0" @@ -1381,6 +1435,46 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "dydx-airdrop" +version = "3.5.0" +dependencies = [ + "anyhow", + "async-std", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw721 0.18.0", + "ethereum-verify", + "ethers-core", + "ethers-signers", + "eyre", + "hex", + "rlp", + "rust_decimal", + "schemars", + "semver", + "serde", + "sg-multi-test", + "sg-std 3.2.0", + "sg-whitelist-flex", + "sg1", + "sg2", + "sg721-base 3.5.0", + "sg721-name", + "sha2 0.10.8", + "sha3", + "thiserror", + "vending-factory", + "vending-minter", + "whitelist-immutable-flex", + "whitelist-updatable", + "whitelist-updatable-flatrate", +] + [[package]] name = "dyn-clone" version = "1.0.16" @@ -1404,8 +1498,8 @@ dependencies = [ "serde_json", "sg-metadata", "sg2", - "sg721", - "sg721-base", + "sg721 3.5.0", + "sg721-base 3.5.0", "test-context", "vending-factory", "vending-minter", @@ -2580,10 +2674,10 @@ dependencies = [ "cw2 1.1.2", "semver", "sg-metadata", - "sg-std", + "sg-std 3.2.0", "sg1", "sg2", - "sg721", + "sg721 3.5.0", "thiserror", ] @@ -2601,11 +2695,11 @@ dependencies = [ "semver", "serde", "sg-metadata", - "sg-std", + "sg-std 3.2.0", "sg1", "sg2", "sg4", - "sg721", + "sg721 3.5.0", "thiserror", "url", ] @@ -3137,6 +3231,41 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rekt-airdrop" +version = "3.5.0" +dependencies = [ + "anyhow", + "async-std", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "ethereum-verify", + "ethers-core", + "ethers-signers", + "eyre", + "hex", + "rlp", + "rust_decimal", + "schemars", + "serde", + "sg-multi-test", + "sg-std 3.2.0", + "sg-whitelist", + "sg1", + "sg2", + "sg721-base 3.5.0", + "sha2 0.10.8", + "sha3", + "thiserror", + "vending-factory", + "vending-minter", + "whitelist-immutable", +] + [[package]] name = "rend" version = "0.4.2" @@ -3615,43 +3744,8 @@ dependencies = [ "cw-utils 1.0.3", "schemars", "serde", - "sg-std", - "thiserror", -] - -[[package]] -name = "sg-eth-airdrop" -version = "3.5.0" -dependencies = [ - "anyhow", - "async-std", - "cosmwasm-schema", - "cosmwasm-std", - "cw-multi-test", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "cw2 1.1.2", - "ethereum-verify", - "ethers-core", - "ethers-signers", - "eyre", - "hex", - "rlp", - "rust_decimal", - "schemars", - "serde", - "sg-multi-test", - "sg-std", - "sg-whitelist", - "sg1", - "sg2", - "sg721-base", - "sha2 0.10.8", - "sha3", + "sg-std 3.2.0", "thiserror", - "vending-factory", - "vending-minter", - "whitelist-immutable", ] [[package]] @@ -3674,7 +3768,7 @@ dependencies = [ "serde", "sg-controllers", "sg-mint-hooks-derive", - "sg-std", + "sg-std 3.2.0", "thiserror", ] @@ -3699,7 +3793,56 @@ dependencies = [ "cw-multi-test", "schemars", "serde", - "sg-std", + "sg-std 3.2.0", +] + +[[package]] +name = "sg-name" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba179e312060ca767f809353a3181a9f7a94932f2498d9b0f337229063a6b93" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "sg-name-market" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c1d3bc0742d8ad86ce05aaeb15835954f665e21da96dc18c7c7412eaa3befa" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "sg-name-minter" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec03766b92b4999ebd9b2f6f10a379345db105c3361d132abd606d64c4a4d73" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 0.15.1", + "schemars", + "serde", +] + +[[package]] +name = "sg-name-minter" +version = "2.1.0" +source = "git+https://github.com/public-awesome/names.git?branch=dYdX-airdrop#eeb9b9002d0a8da979103f95d8937cef31ba5121" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 0.16.0", + "schemars", + "serde", ] [[package]] @@ -3716,7 +3859,37 @@ dependencies = [ "schemars", "serde", "sg-controllers", - "sg-std", + "sg-std 3.2.0", + "thiserror", +] + +[[package]] +name = "sg-std" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f332c16bb8cab94613e6b9bef76fbd27937e0be1c40c979ed1a7367b96866126" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "cw721 0.16.0", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "sg-std" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd076abea035a2da34a770eb7a239a4fbffa59d3a193ab91e07fb81583937c5c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "cw721 0.16.0", + "schemars", + "serde", "thiserror", ] @@ -3747,11 +3920,34 @@ dependencies = [ "rust_decimal", "schemars", "serde", - "sg-std", + "sg-std 3.2.0", "sg1", "thiserror", ] +[[package]] +name = "sg-whitelist-basic" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0670b0de4489224faa3c18ceb28af7251441118473f454110739a69df587b9" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "sg-whitelist-basic" +version = "2.1.0" +source = "git+https://github.com/public-awesome/names.git?branch=dYdX-airdrop#eeb9b9002d0a8da979103f95d8937cef31ba5121" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "sg-whitelist-flex" version = "3.5.0" @@ -3764,7 +3960,7 @@ dependencies = [ "rust_decimal", "schemars", "serde", - "sg-std", + "sg-std 3.2.0", "sg1", "thiserror", ] @@ -3776,7 +3972,7 @@ dependencies = [ "cosmwasm-std", "cw-utils 1.0.3", "serde", - "sg-std", + "sg-std 3.2.0", "thiserror", ] @@ -3790,7 +3986,7 @@ dependencies = [ "cw-utils 1.0.3", "schemars", "serde", - "sg721", + "sg721 3.5.0", "thiserror", ] @@ -3805,6 +4001,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sg721" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a8c0cb5748e59d506cb7cf375d1f03769f1e3586a1542be8a199730721c6102" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "cw721-base 0.16.0", + "serde", + "thiserror", +] + [[package]] name = "sg721" version = "3.5.0" @@ -3819,6 +4029,26 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sg721-base" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c3ba2af031105758e21f94e8a1488bf810b33914b7e49d7ef5efcf1936cf96" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "cw2 0.16.0", + "cw721 0.16.0", + "cw721-base 0.16.0", + "serde", + "sg-std 0.22.11", + "sg721 0.22.11", + "thiserror", + "url", +] + [[package]] name = "sg721-base" version = "3.5.0" @@ -3832,8 +4062,8 @@ dependencies = [ "cw721 0.18.0", "cw721-base 0.18.0", "serde", - "sg-std", - "sg721", + "sg-std 3.2.0", + "sg721 3.5.0", "thiserror", "url", ] @@ -3851,9 +4081,35 @@ dependencies = [ "schemars", "serde", "sg-metadata", - "sg-std", - "sg721", - "sg721-base", + "sg-std 3.2.0", + "sg721 3.5.0", + "sg721-base 3.5.0", +] + +[[package]] +name = "sg721-name" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc40d7bb875a788848daacac5d8d3e894f48f8c282a3d704f3492ffa9261ede" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 0.16.0", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "cw2 0.16.0", + "cw721 0.16.0", + "cw721-base 0.16.0", + "schemars", + "semver", + "serde", + "sg-name", + "sg-name-market", + "sg-std 0.22.11", + "sg721 0.22.11", + "sg721-base 0.22.11", + "subtle-encoding", + "thiserror", ] [[package]] @@ -3868,9 +4124,9 @@ dependencies = [ "cw721-base 0.18.0", "schemars", "serde", - "sg-std", - "sg721", - "sg721-base", + "sg-std 3.2.0", + "sg721 3.5.0", + "sg721-base 3.5.0", ] [[package]] @@ -3888,10 +4144,10 @@ dependencies = [ "schemars", "semver", "serde", - "sg-std", + "sg-std 3.2.0", "sg1", - "sg721", - "sg721-base", + "sg721 3.5.0", + "sg721-base 3.5.0", "thiserror", ] @@ -4277,6 +4533,7 @@ dependencies = [ "cw4-group", "cw721 0.18.0", "cw721-base 0.18.0", + "dydx-airdrop", "ethers-core", "ethers-signers", "eyre", @@ -4284,22 +4541,24 @@ dependencies = [ "open-edition-minter", "rand_core 0.6.4", "rand_xoshiro", + "rekt-airdrop", "rlp", "rs_merkle", "schemars", "serde", "sg-controllers", - "sg-eth-airdrop", "sg-metadata", "sg-multi-test", "sg-splits", - "sg-std", + "sg-std 3.2.0", "sg-whitelist", + "sg-whitelist-flex", "sg1", "sg2", "sg4", - "sg721", - "sg721-base", + "sg721 3.5.0", + "sg721-base 3.5.0", + "sg721-name", "sg721-nt", "sg721-updatable", "sha2 0.10.8", @@ -4310,7 +4569,9 @@ dependencies = [ "vending-minter", "vending-minter-merkle-wl", "whitelist-immutable", + "whitelist-immutable-flex", "whitelist-mtree", + "whitelist-updatable", ] [[package]] @@ -4718,10 +4979,10 @@ dependencies = [ "schemars", "semver", "serde", - "sg-std", + "sg-std 3.2.0", "sg1", "sg2", - "sg721", + "sg721 3.5.0", "thiserror", ] @@ -4741,12 +5002,12 @@ dependencies = [ "schemars", "semver", "serde", - "sg-std", + "sg-std 3.2.0", "sg-whitelist", "sg1", "sg2", "sg4", - "sg721", + "sg721 3.5.0", "sha2 0.10.8", "shuffle", "thiserror", @@ -4770,12 +5031,12 @@ dependencies = [ "schemars", "semver", "serde", - "sg-std", + "sg-std 3.2.0", "sg-whitelist", "sg1", "sg2", "sg4", - "sg721", + "sg721 3.5.0", "sha2 0.10.8", "shuffle", "thiserror", @@ -4799,12 +5060,12 @@ dependencies = [ "schemars", "semver", "serde", - "sg-std", + "sg-std 3.2.0", "sg-whitelist", "sg1", "sg2", "sg4", - "sg721", + "sg721 3.5.0", "sha2 0.10.8", "shuffle", "thiserror", @@ -4829,12 +5090,12 @@ dependencies = [ "schemars", "semver", "serde", - "sg-std", + "sg-std 3.2.0", "sg-whitelist-flex", "sg1", "sg2", "sg4", - "sg721", + "sg721 3.5.0", "sha2 0.10.8", "shuffle", "thiserror", @@ -4858,12 +5119,12 @@ dependencies = [ "schemars", "semver", "serde", - "sg-std", + "sg-std 3.2.0", "sg-whitelist-flex", "sg1", "sg2", "sg4", - "sg721", + "sg721 3.5.0", "sha2 0.10.8", "shuffle", "thiserror", @@ -5015,7 +5276,24 @@ dependencies = [ "cw2 1.1.2", "schemars", "serde", - "sg-std", + "sg-std 3.2.0", + "sg-whitelist", + "thiserror", +] + +[[package]] +name = "whitelist-immutable-flex" +version = "3.5.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 0.16.0", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "schemars", + "serde", + "sg-std 3.2.0", "sg-whitelist", "thiserror", ] @@ -5035,12 +5313,52 @@ dependencies = [ "schemars", "serde", "serde_json", - "sg-std", + "sg-std 3.2.0", "sg1", "thiserror", "url", ] +[[package]] +name = "whitelist-updatable" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266929a317b6b0b09f421bb5a478e4d1377d1637927f59bf76b21b05352fcbd1" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 0.16.0", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "cw2 0.16.0", + "schemars", + "serde", + "sg-name-minter 0.12.0", + "sg-std 0.21.12", + "sg-whitelist-basic 0.12.0", + "thiserror", +] + +[[package]] +name = "whitelist-updatable-flatrate" +version = "2.1.0" +source = "git+https://github.com/public-awesome/names.git?branch=dYdX-airdrop#eeb9b9002d0a8da979103f95d8937cef31ba5121" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers 0.16.0", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "cw2 0.16.0", + "schemars", + "semver", + "serde", + "sg-name-minter 2.1.0", + "sg-std 0.22.11", + "sg-whitelist-basic 2.1.0", + "thiserror", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index b86fb6c15..c5eb7b28f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "contracts/factories/*", "contracts/minters/*", "contracts/whitelists/*", + "contracts/dydx-airdrop", "test-suite/", "e2e", ] @@ -59,9 +60,14 @@ vending-minter = { version = "3.5.0", path = "contracts/minters/vending-mi open-edition-factory = { version = "3.5.0", path = "contracts/factories/open-edition-factory" } open-edition-minter = { version = "3.5.0", path = "contracts/minters/open-edition-minter" } whitelist-immutable = { version = "3.5.0", path = "contracts/whitelists/whitelist-immutable" } +whitelist-immutable-flex = { version = "3.5.0", path = "contracts/whitelists/whitelist-immutable-flex" } +whitelist-updatable = "0.12.0" +whitelist-updatable-flatrate = {git = "https://github.com/public-awesome/names.git", branch = "dYdX-airdrop"} sg-whitelist-flex = { version = "3.5.0", path = "contracts/whitelists/whitelist-flex" } +sg721-name = "1.2.5" ethereum-verify = { version = "3.5.0", path = "packages/ethereum-verify" } -sg-eth-airdrop = { version = "3.5.0", path = "contracts/sg-eth-airdrop" } +rekt-airdrop = { version = "3.5.0", path = "contracts/rekt-airdrop"} +dydx-airdrop = { version = "3.5.0", path = "contracts/dydx-airdrop"} test-suite = { version = "3.5.0", path = "test-suite" } semver = "1" @@ -107,7 +113,11 @@ incremental = false codegen-units = 1 incremental = false -[profile.release.package.sg-eth-airdrop] +[profile.release.package.rekt-airdrop] +codegen-units = 1 +incremental = false + +[profile.release.package.dydx-airdrop] codegen-units = 1 incremental = false diff --git a/contracts/sg-eth-airdrop/.cargo/config b/contracts/dydx-airdrop/.cargo/config similarity index 100% rename from contracts/sg-eth-airdrop/.cargo/config rename to contracts/dydx-airdrop/.cargo/config diff --git a/contracts/sg-eth-airdrop/.gitignore b/contracts/dydx-airdrop/.gitignore similarity index 100% rename from contracts/sg-eth-airdrop/.gitignore rename to contracts/dydx-airdrop/.gitignore diff --git a/contracts/dydx-airdrop/Cargo.toml b/contracts/dydx-airdrop/Cargo.toml new file mode 100644 index 000000000..892ea22a5 --- /dev/null +++ b/contracts/dydx-airdrop/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "dydx-airdrop" +authors = ["Michael Scotto "] +description = "Stargaze dydx Airdrop" +version = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +license = { workspace = true } + +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 = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } +cw2 = { workspace = true } +ethereum-verify = { workspace = true } +hex = "0.4" +rust_decimal = { version = "1.14.3" } +cw721 = { workspace = true } +sg721-base = { workspace = true, features = ["library"] } +sg721-name = { workspace = true, features = ["library"] } +sg-whitelist-flex = { workspace = true, features = ["library"] } +sg1 = { workspace = true } +sha2 = { workspace = true } +sha3 = "0.10" +schemars = { workspace = true } +serde = { workspace = true } +semver = { workspace = true } +sg-std = { workspace = true } +thiserror = { workspace = true } +vending-minter = { workspace = true, features = ["library"] } +whitelist-immutable-flex = { workspace = true, features = ["library"] } +whitelist-updatable = { workspace = true, features = ["library"] } +whitelist-updatable-flatrate = { workspace = true, features = ["library"] } + +[dev-dependencies] +async-std = "1.12.0" +ethers-signers = "1.0.0" +ethers-core = "1.0.0" +eyre = "0.6" +rlp = "0.5" +sg2 = { workspace = true } +vending-factory = { workspace = true } +cw-multi-test = { workspace = true } +sg-multi-test = { workspace = true } +anyhow = "1.0.57" diff --git a/contracts/sg-eth-airdrop/rustfmt.toml b/contracts/dydx-airdrop/rustfmt.toml similarity index 100% rename from contracts/sg-eth-airdrop/rustfmt.toml rename to contracts/dydx-airdrop/rustfmt.toml diff --git a/contracts/dydx-airdrop/schema/dydx-airdrop.json b/contracts/dydx-airdrop/schema/dydx-airdrop.json new file mode 100644 index 000000000..28c5a7fe7 --- /dev/null +++ b/contracts/dydx-airdrop/schema/dydx-airdrop.json @@ -0,0 +1,263 @@ +{ + "contract_name": "dydx-airdrop", + "contract_version": "3.5.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "admin", + "airdrop_amount", + "airdrop_count_limit", + "claim_msg_plaintext", + "members", + "minter_address", + "name_collection_address", + "name_discount_wl_address", + "whitelist_code_id" + ], + "properties": { + "admin": { + "type": "string" + }, + "airdrop_amount": { + "type": "integer", + "format": "uint128", + "minimum": 0.0 + }, + "airdrop_count_limit": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "claim_msg_plaintext": { + "type": "string" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/Member" + } + }, + "minter_address": { + "type": "string" + }, + "name_collection_address": { + "type": "string" + }, + "name_discount_wl_address": { + "type": "string" + }, + "whitelist_code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Member": { + "type": "object", + "required": [ + "address", + "mint_count" + ], + "properties": { + "address": { + "type": "string" + }, + "mint_count": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "register" + ], + "properties": { + "register": { + "type": "object", + "required": [ + "eth_address", + "eth_sig" + ], + "properties": { + "eth_address": { + "type": "string" + }, + "eth_sig": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim_airdrop" + ], + "properties": { + "claim_airdrop": { + "type": "object", + "required": [ + "eth_address", + "eth_sig" + ], + "properties": { + "eth_address": { + "type": "string" + }, + "eth_sig": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "airdrop_eligible" + ], + "properties": { + "airdrop_eligible": { + "type": "object", + "required": [ + "eth_address" + ], + "properties": { + "eth_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_minter" + ], + "properties": { + "get_minter": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "is_registered" + ], + "properties": { + "is_registered": { + "type": "object", + "required": [ + "eth_address" + ], + "properties": { + "eth_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "has_claimed" + ], + "properties": { + "has_claimed": { + "type": "object", + "required": [ + "eth_address" + ], + "properties": { + "eth_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_airdrop_count" + ], + "properties": { + "get_airdrop_count": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "airdrop_eligible": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Boolean", + "type": "boolean" + }, + "get_airdrop_count": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint32", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "get_minter": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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" + }, + "has_claimed": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Boolean", + "type": "boolean" + }, + "is_registered": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Boolean", + "type": "boolean" + } + } +} diff --git a/contracts/dydx-airdrop/schema/raw/execute.json b/contracts/dydx-airdrop/schema/raw/execute.json new file mode 100644 index 000000000..41a752849 --- /dev/null +++ b/contracts/dydx-airdrop/schema/raw/execute.json @@ -0,0 +1,56 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "register" + ], + "properties": { + "register": { + "type": "object", + "required": [ + "eth_address", + "eth_sig" + ], + "properties": { + "eth_address": { + "type": "string" + }, + "eth_sig": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim_airdrop" + ], + "properties": { + "claim_airdrop": { + "type": "object", + "required": [ + "eth_address", + "eth_sig" + ], + "properties": { + "eth_address": { + "type": "string" + }, + "eth_sig": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/dydx-airdrop/schema/raw/instantiate.json b/contracts/dydx-airdrop/schema/raw/instantiate.json new file mode 100644 index 000000000..464bfc25b --- /dev/null +++ b/contracts/dydx-airdrop/schema/raw/instantiate.json @@ -0,0 +1,75 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "admin", + "airdrop_amount", + "airdrop_count_limit", + "claim_msg_plaintext", + "members", + "minter_address", + "name_collection_address", + "name_discount_wl_address", + "whitelist_code_id" + ], + "properties": { + "admin": { + "type": "string" + }, + "airdrop_amount": { + "type": "integer", + "format": "uint128", + "minimum": 0.0 + }, + "airdrop_count_limit": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "claim_msg_plaintext": { + "type": "string" + }, + "members": { + "type": "array", + "items": { + "$ref": "#/definitions/Member" + } + }, + "minter_address": { + "type": "string" + }, + "name_collection_address": { + "type": "string" + }, + "name_discount_wl_address": { + "type": "string" + }, + "whitelist_code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "Member": { + "type": "object", + "required": [ + "address", + "mint_count" + ], + "properties": { + "address": { + "type": "string" + }, + "mint_count": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/dydx-airdrop/schema/raw/query.json b/contracts/dydx-airdrop/schema/raw/query.json new file mode 100644 index 000000000..5fd2cdaf7 --- /dev/null +++ b/contracts/dydx-airdrop/schema/raw/query.json @@ -0,0 +1,95 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "airdrop_eligible" + ], + "properties": { + "airdrop_eligible": { + "type": "object", + "required": [ + "eth_address" + ], + "properties": { + "eth_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_minter" + ], + "properties": { + "get_minter": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "is_registered" + ], + "properties": { + "is_registered": { + "type": "object", + "required": [ + "eth_address" + ], + "properties": { + "eth_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "has_claimed" + ], + "properties": { + "has_claimed": { + "type": "object", + "required": [ + "eth_address" + ], + "properties": { + "eth_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_airdrop_count" + ], + "properties": { + "get_airdrop_count": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/dydx-airdrop/schema/raw/response_to_airdrop_eligible.json b/contracts/dydx-airdrop/schema/raw/response_to_airdrop_eligible.json new file mode 100644 index 000000000..a7fe2bfe9 --- /dev/null +++ b/contracts/dydx-airdrop/schema/raw/response_to_airdrop_eligible.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Boolean", + "type": "boolean" +} diff --git a/contracts/dydx-airdrop/schema/raw/response_to_get_minter.json b/contracts/dydx-airdrop/schema/raw/response_to_get_minter.json new file mode 100644 index 000000000..4c7f1934d --- /dev/null +++ b/contracts/dydx-airdrop/schema/raw/response_to_get_minter.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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/sg-eth-airdrop/src/bin/schema.rs b/contracts/dydx-airdrop/src/bin/schema.rs similarity index 71% rename from contracts/sg-eth-airdrop/src/bin/schema.rs rename to contracts/dydx-airdrop/src/bin/schema.rs index 19a0a98f5..2a15dd0fd 100644 --- a/contracts/sg-eth-airdrop/src/bin/schema.rs +++ b/contracts/dydx-airdrop/src/bin/schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use sg_eth_airdrop::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use dydx_airdrop::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/dydx-airdrop/src/build_messages.rs b/contracts/dydx-airdrop/src/build_messages.rs new file mode 100644 index 000000000..8158c2444 --- /dev/null +++ b/contracts/dydx-airdrop/src/build_messages.rs @@ -0,0 +1,139 @@ +use crate::msg::InstantiateMsg; +use crate::query::{query_collection_whitelist, query_mint_count}; +use crate::state::{Config, CONFIG}; +use crate::ContractError; +use cosmwasm_std::{ + coins, to_json_binary, Addr, BankMsg, Deps, DepsMut, Env, MessageInfo, StdResult, WasmMsg, +}; +use sg_std::{CosmosMsg, Response, StargazeMsgWrapper, SubMsg, NATIVE_DENOM}; +use sg_whitelist_flex::helpers::interface::CollectionWhitelistContract; +use sg_whitelist_flex::msg::AddMembersMsg; +use sg_whitelist_flex::msg::{ExecuteMsg as CollectionWhitelistExecuteMsg, Member}; +use whitelist_immutable_flex::msg::InstantiateMsg as WIFInstantiateMsg; +use whitelist_updatable_flatrate::msg::ExecuteMsg; +use whitelist_updatable_flatrate::msg::ExecuteMsg::AddAddresses; +pub const IMMUTABLE_WHITELIST_LABEL: &str = "Whitelist Immutable Flex for Airdrop"; +pub const INIT_WHITELIST_REPLY_ID: u64 = 1; + +pub fn whitelist_instantiate( + env: Env, + msg: InstantiateMsg, +) -> Result, ContractError> { + let whitelist_instantiate_msg = WIFInstantiateMsg { + members: msg.members, + mint_discount_bps: Some(0), + }; + let wasm_msg = WasmMsg::Instantiate { + code_id: msg.whitelist_code_id, + admin: Some(env.contract.address.to_string()), + funds: vec![], + label: IMMUTABLE_WHITELIST_LABEL.to_string(), + msg: to_json_binary(&whitelist_instantiate_msg)?, + }; + Ok(SubMsg::reply_on_success(wasm_msg, INIT_WHITELIST_REPLY_ID)) +} + +pub fn state_config( + deps: Deps, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + Ok(Config { + admin: info.sender, + claim_msg_plaintext: msg.clone().claim_msg_plaintext, + airdrop_amount: crate::validation::validate_airdrop_amount(msg.airdrop_amount)?, + whitelist_address: None, + minter_address: deps.api.addr_validate(msg.minter_address.as_ref())?, + name_discount_wl_address: deps + .api + .addr_validate(msg.name_discount_wl_address.as_ref())?, + name_collection_address: deps + .api + .addr_validate(msg.name_collection_address.as_ref())?, + airdrop_count_limit: msg.airdrop_count_limit, + }) +} + +pub fn claim_reward(info: MessageInfo, airdrop_amount: u128) -> Result { + let mut res = Response::new(); + let bank_msg = SubMsg::new(BankMsg::Send { + to_address: info.sender.to_string(), + amount: coins(airdrop_amount, NATIVE_DENOM), + }); + res = res.add_submessage(bank_msg); + + Ok(res) +} + +pub fn dust_and_whitelist_add( + deps: &DepsMut, + info: MessageInfo, + eth_address: String, +) -> Result { + let res = add_member_to_whitelists(deps, info.clone(), eth_address)?; + let res = res.add_message(dust_member_wallet(info.clone())?); + Ok(res) +} +pub fn add_member_to_whitelists( + deps: &DepsMut, + info: MessageInfo, + eth_address: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let collection_whitelist = query_collection_whitelist(deps)?; + let mint_count = query_mint_count(deps, eth_address.clone())?; + let names_discount_whitelist = config.name_discount_wl_address; + + let res = Response::new(); + let res = res.add_message(add_member_to_collection_whitelist( + deps, + info.sender.clone(), + collection_whitelist, + mint_count, + )?); + let res = res.add_message(add_member_to_names_discount_whitelist( + info.sender.clone(), + names_discount_whitelist.clone().to_string(), + )?); + Ok(res) +} + +fn add_member_to_collection_whitelist( + deps: &DepsMut, + wallet_address: Addr, + collection_whitelist: String, + mint_count: u32, +) -> StdResult { + let inner_msg = AddMembersMsg { + to_add: vec![Member { + address: wallet_address.to_string(), + mint_count, + }], + }; + let execute_msg = CollectionWhitelistExecuteMsg::AddMembers(inner_msg); + CollectionWhitelistContract(deps.api.addr_validate(&collection_whitelist)?).call(execute_msg) +} + +fn add_member_to_names_discount_whitelist( + wallet_address: Addr, + name_discount_wl: String, +) -> StdResult { + let execute_msg: ExecuteMsg = AddAddresses { + addresses: vec![wallet_address.to_string()], + }; + let msg = to_json_binary(&execute_msg)?; + Ok(WasmMsg::Execute { + contract_addr: name_discount_wl, + msg, + funds: vec![], + } + .into()) +} + +pub fn dust_member_wallet(info: MessageInfo) -> StdResult { + let inner_msg = BankMsg::Send { + to_address: info.sender.to_string(), + amount: coins(1000000, NATIVE_DENOM), + }; + Ok(CosmosMsg::Bank(inner_msg)) +} diff --git a/contracts/dydx-airdrop/src/claim_airdrop.rs b/contracts/dydx-airdrop/src/claim_airdrop.rs new file mode 100644 index 000000000..2cfd19307 --- /dev/null +++ b/contracts/dydx-airdrop/src/claim_airdrop.rs @@ -0,0 +1,37 @@ +use crate::build_messages::claim_reward; +use crate::state::{AIRDROP_COUNT, HAS_CLAIMED}; +use crate::{state::CONFIG, ContractError}; +use cosmwasm_std::DepsMut; +use cosmwasm_std::{Env, MessageInfo}; +use sg_std::Response; + +use crate::validation::validate_claim; + +pub fn claim_airdrop( + deps: DepsMut, + info: MessageInfo, + _env: Env, + eth_address: String, + eth_sig: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let airdrop_count_limit = config.airdrop_count_limit; + if AIRDROP_COUNT.load(deps.storage)? >= airdrop_count_limit { + return Err(ContractError::AirdropCountLimitExceeded {}); + } + + validate_claim( + &deps, + info.clone(), + eth_address.clone(), + eth_sig, + config.clone(), + )?; + + let res = claim_reward(info, config.airdrop_amount)?; + AIRDROP_COUNT.update(deps.storage, |count: u32| -> Result { + Ok(count + 1) + })?; + HAS_CLAIMED.save(deps.storage, ð_address, &true)?; + Ok(res.add_attribute("claimed_amount", config.airdrop_amount.to_string())) +} diff --git a/contracts/dydx-airdrop/src/contract.rs b/contracts/dydx-airdrop/src/contract.rs new file mode 100644 index 000000000..853ac791b --- /dev/null +++ b/contracts/dydx-airdrop/src/contract.rs @@ -0,0 +1,153 @@ +use crate::claim_airdrop::claim_airdrop; +#[cfg(not(feature = "library"))] +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg}; +use crate::state::{AIRDROP_COUNT, CONFIG}; + +use cosmwasm_std::{ + attr, ensure, entry_point, BankMsg, Coin, CosmosMsg, Empty, Event, StdError, Uint128, +}; +use cosmwasm_std::{DepsMut, Env, MessageInfo}; +use cw2::set_contract_version; +use semver::Version; +use sg1::fair_burn; +use sg_std::{Response, NATIVE_DENOM}; + +use crate::build_messages::{state_config, whitelist_instantiate}; +use crate::register::register; +use crate::validation::validate_instantiation_params; + +const CONTRACT_NAME: &str = "crates.io:dydx-airdrop"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const INSTANTIATION_FEE: u128 = 100_000_000; // 100 STARS + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + validate_instantiation_params(info.clone(), msg.clone())?; + let mut res = Response::new(); + fair_burn(INSTANTIATION_FEE, None, &mut res); + AIRDROP_COUNT.save(deps.storage, &0)?; + let cfg = state_config(deps.as_ref(), info.clone(), msg.clone())?; + CONFIG.save(deps.storage, &cfg)?; + Ok(res + .add_attribute("action", "instantiate") + .add_attribute("contract_name", CONTRACT_NAME) + .add_attribute("contract_version", CONTRACT_VERSION) + .add_attribute("sender", info.sender) + .add_submessage(whitelist_instantiate(env, msg)?)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Register { + eth_address, + eth_sig, + } => register(deps, info, env, eth_address, eth_sig), + ExecuteMsg::ClaimAirdrop { + eth_address, + eth_sig, + } => claim_airdrop(deps, info, env, eth_address, eth_sig), + ExecuteMsg::WithdrawRemaining { recipient, amount } => { + withdraw_remaining(deps, info, env, recipient, amount) + } + } +} + +pub fn withdraw_remaining( + deps: DepsMut, + info: MessageInfo, + env: Env, + recipient: String, + amount: Option, +) -> Result { + let contract_info = deps + .querier + .query_wasm_contract_info(env.contract.address.clone())?; + ensure!( + contract_info.admin.is_some() && contract_info.admin.unwrap() == info.sender, + ContractError::Unauthorized { + sender: info.sender + } + ); + + let total_amount = deps + .querier + .query_balance(env.contract.address.clone(), NATIVE_DENOM)? + .amount; + + let recipient_addr = deps.api.addr_validate(&recipient)?; + + let amount_to_withdraw = match amount { + Some(amount) => { + if amount > total_amount { + return Err(ContractError::InsufficientFunds { + balance: total_amount, + amount, + }); + } + amount + } + None => total_amount, + }; + + let msg = BankMsg::Send { + to_address: recipient_addr.to_string(), + amount: vec![Coin { + denom: NATIVE_DENOM.to_string(), + amount: amount_to_withdraw, + }], + }; + + let res = Response::new() + .add_message(CosmosMsg::Bank(msg)) + .add_attributes(vec![ + attr("action", "withdraw_remaining"), + attr("amount", amount_to_withdraw), + attr("recipient", recipient_addr.to_string()), + ]); + Ok(res) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { + let current_version = cw2::get_contract_version(deps.storage)?; + if current_version.contract != CONTRACT_NAME { + return Err(StdError::generic_err("Cannot upgrade to a different contract").into()); + } + let version: Version = current_version + .version + .parse() + .map_err(|_| StdError::generic_err("Invalid contract version"))?; + let new_version: Version = CONTRACT_VERSION + .parse() + .map_err(|_| StdError::generic_err("Invalid contract version"))?; + + if version > new_version { + return Err(StdError::generic_err("Cannot upgrade to a previous contract version").into()); + } + // if same version return + if version == new_version { + return Ok(Response::new()); + } + + // set new contract version + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let event = Event::new("migrate") + .add_attribute("from_name", current_version.contract) + .add_attribute("from_version", current_version.version) + .add_attribute("to_name", CONTRACT_NAME) + .add_attribute("to_version", CONTRACT_VERSION); + Ok(Response::new().add_event(event)) +} diff --git a/contracts/dydx-airdrop/src/error.rs b/contracts/dydx-airdrop/src/error.rs new file mode 100644 index 000000000..39f42fb4a --- /dev/null +++ b/contracts/dydx-airdrop/src/error.rs @@ -0,0 +1,81 @@ +use cosmwasm_std::{Addr, StdError, Uint128}; +use cw_utils::{self, PaymentError}; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("Contract has no funds")] + NoFunds {}, + + #[error("Insufficient Funds for Instantiate")] + InsufficientFundsInstantiate {}, + + #[error("Airdrop Amount Too Small")] + AirdropTooSmall {}, + + #[error("Airdrop Amount Too Big")] + AirdropTooBig {}, + + #[error("Invalid reply ID")] + InvalidReplyID {}, + + #[error("Unauthorized admin, sender is {sender}")] + Unauthorized { sender: Addr }, + + #[error("Reply error")] + ReplyOnSuccess {}, + + #[error("Whitelist contract has not been set")] + WhitelistContractNotSet {}, + + #[error("Minter already set")] + MinterAlreadySet {}, + + #[error("Address {address} is not eligible")] + AddressNotEligible { address: String }, + + #[error("Invalid Eth signature for address: {address}")] + InvalidEthSignature { address: String }, + + #[error("Address {address} has already claimed all available mints")] + MintCountReached { address: String }, + + #[error("Address {address} has already registered for the airdrop")] + AlreadyRegistered { address: String }, + + #[error("Address {address} has already claimed the airdrop")] + AlreadyClaimed { address: String }, + + #[error("Collection Whitelist on Minter contract has not been set")] + CollectionWhitelistMinterNotSet {}, + + #[error("Minter config is missing the collection contract address")] + CollectionContractNotSet {}, + + #[error("Need to mint a Geckies token first")] + CollectionNotMinted {}, + + #[error("Need to mint a name first")] + NameNotMinted {}, + + #[error("Airdrop count limit exceeded")] + AirdropCountLimitExceeded {}, + + #[error("Plaintext message must contain `{{wallet}}` string")] + PlaintextMsgNoWallet {}, + + #[error("Plaintext message is too long")] + PlaintextTooLong {}, + + #[error("Address {address} should be converted to lowercase")] + EthAddressShouldBeLower { address: String }, + + #[error("Insufficient Funds: Contract balance: {balance} does not cover the required amount: {amount}")] + InsufficientFunds { balance: Uint128, amount: Uint128 }, +} diff --git a/contracts/dydx-airdrop/src/lib.rs b/contracts/dydx-airdrop/src/lib.rs new file mode 100644 index 000000000..1ecfe337a --- /dev/null +++ b/contracts/dydx-airdrop/src/lib.rs @@ -0,0 +1,11 @@ +pub mod contract; +pub mod error; +pub mod msg; +pub mod query; +pub mod reply; +pub mod state; +pub use crate::error::ContractError; +mod build_messages; +mod claim_airdrop; +mod register; +mod validation; diff --git a/contracts/dydx-airdrop/src/msg.rs b/contracts/dydx-airdrop/src/msg.rs new file mode 100644 index 000000000..66c2887a4 --- /dev/null +++ b/contracts/dydx-airdrop/src/msg.rs @@ -0,0 +1,54 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Uint128}; +use whitelist_immutable_flex::msg::Member; + +#[cw_serde] +pub struct InstantiateMsg { + pub admin: String, + pub claim_msg_plaintext: String, + pub airdrop_amount: u128, + pub members: Vec, + pub whitelist_code_id: u64, + pub minter_address: String, + pub name_discount_wl_address: String, + pub name_collection_address: String, + pub airdrop_count_limit: u32, +} + +#[cw_serde] +pub struct AirdropClaimResponse { + result: bool, + amount: u32, + minter_page: String, +} + +#[cw_serde] +pub enum ExecuteMsg { + Register { + eth_address: String, + eth_sig: String, + }, + ClaimAirdrop { + eth_address: String, + eth_sig: String, + }, + WithdrawRemaining { + recipient: String, + amount: Option, + }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(bool)] + AirdropEligible { eth_address: String }, + #[returns(Addr)] + GetMinter {}, + #[returns(bool)] + IsRegistered { eth_address: String }, + #[returns(bool)] + HasClaimed { eth_address: String }, + #[returns(u32)] + GetAirdropCount {}, +} diff --git a/contracts/dydx-airdrop/src/query.rs b/contracts/dydx-airdrop/src/query.rs new file mode 100644 index 000000000..d8a4a064c --- /dev/null +++ b/contracts/dydx-airdrop/src/query.rs @@ -0,0 +1,92 @@ +use crate::state::{AIRDROP_COUNT, HAS_CLAIMED, IS_ADDRESS_REGISTERED}; +use crate::{msg::QueryMsg, state::CONFIG, ContractError}; +use cosmwasm_std::{entry_point, to_json_binary, Binary}; +use cosmwasm_std::{Addr, Env}; +use cosmwasm_std::{Deps, DepsMut, StdResult}; +use vending_minter::helpers::MinterContract; +use whitelist_immutable_flex::helpers::WhitelistImmutableFlexContract; +use whitelist_immutable_flex::msg::MemberResponse; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::AirdropEligible { eth_address } => { + to_json_binary(&query_airdrop_is_eligible(deps, eth_address)?) + } + QueryMsg::GetMinter {} => to_json_binary(&query_minter(deps)?), + QueryMsg::IsRegistered { eth_address } => { + to_json_binary(&query_airdrop_is_registered(deps, eth_address)?) + } + QueryMsg::HasClaimed { eth_address } => { + to_json_binary(&query_airdrop_has_claimed(deps, eth_address)?) + } + QueryMsg::GetAirdropCount {} => to_json_binary(&query_airdrop_count(deps)?), + } +} + +fn query_minter(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + Ok(config.minter_address) +} + +pub fn query_airdrop_is_eligible(deps: Deps, eth_address: String) -> StdResult { + let config = CONFIG.load(deps.storage)?; + match config.whitelist_address { + Some(address) => WhitelistImmutableFlexContract(deps.api.addr_validate(&address)?) + .includes(&deps.querier, eth_address), + None => Err(cosmwasm_std::StdError::NotFound { + kind: "Whitelist Contract".to_string(), + }), + } +} + +pub fn query_airdrop_count(deps: Deps) -> StdResult { + let airdrop_count = AIRDROP_COUNT.load(deps.storage)?; + Ok(airdrop_count) +} + +pub fn query_airdrop_is_registered(deps: Deps, eth_address: String) -> StdResult { + let is_registered = IS_ADDRESS_REGISTERED.may_load(deps.storage, ð_address)?; + Ok(is_registered.unwrap_or_default()) +} + +pub fn query_airdrop_has_claimed(deps: Deps, eth_address: String) -> StdResult { + let has_claimed = HAS_CLAIMED.may_load(deps.storage, ð_address)?; + Ok(has_claimed.unwrap_or_default()) +} + +pub fn query_collection_whitelist(deps: &DepsMut) -> Result { + let config = CONFIG.load(deps.storage)?; + let minter_addr = config.minter_address; + let config = MinterContract(minter_addr).config(&deps.querier)?; + match config.whitelist { + Some(whitelist) => Ok(whitelist), + None => Err(ContractError::CollectionWhitelistMinterNotSet {}), + } +} + +pub fn query_collection_address(deps: &DepsMut) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let minter_addr = config.minter_address; + let config = MinterContract(minter_addr).config(&deps.querier)?; + Ok(config.sg721_address) +} + +pub fn query_mint_count(deps: &DepsMut, eth_address: String) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let whitelist_address = + config + .whitelist_address + .ok_or_else(|| cosmwasm_std::StdError::NotFound { + kind: "Whitelist Contract".to_string(), + })?; + let member_response: MemberResponse = deps.querier.query(&cosmwasm_std::QueryRequest::Wasm( + cosmwasm_std::WasmQuery::Smart { + contract_addr: whitelist_address, + msg: to_json_binary(&whitelist_immutable_flex::msg::QueryMsg::Member { + address: eth_address, + })?, + }, + ))?; + Ok(member_response.member.mint_count) +} diff --git a/contracts/dydx-airdrop/src/register.rs b/contracts/dydx-airdrop/src/register.rs new file mode 100644 index 000000000..7a8c1ad38 --- /dev/null +++ b/contracts/dydx-airdrop/src/register.rs @@ -0,0 +1,28 @@ +use crate::build_messages::dust_and_whitelist_add; +use crate::state::IS_ADDRESS_REGISTERED; +use crate::validation::validate_registration; +use crate::{state::CONFIG, ContractError}; +use cosmwasm_std::DepsMut; +use cosmwasm_std::{Env, MessageInfo}; +use sg_std::Response; + +pub fn register( + deps: DepsMut, + info: MessageInfo, + _env: Env, + eth_address: String, + eth_sig: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + validate_registration( + &deps, + info.clone(), + eth_address.clone(), + eth_sig, + config.clone(), + )?; + let res = dust_and_whitelist_add(&deps, info, eth_address.clone())?; + IS_ADDRESS_REGISTERED.save(deps.storage, ð_address, &true)?; + + Ok(res.add_attribute("address_registered", eth_address)) +} diff --git a/contracts/sg-eth-airdrop/src/reply.rs b/contracts/dydx-airdrop/src/reply.rs similarity index 100% rename from contracts/sg-eth-airdrop/src/reply.rs rename to contracts/dydx-airdrop/src/reply.rs diff --git a/contracts/dydx-airdrop/src/state.rs b/contracts/dydx-airdrop/src/state.rs new file mode 100644 index 000000000..0effe836d --- /dev/null +++ b/contracts/dydx-airdrop/src/state.rs @@ -0,0 +1,22 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct Config { + pub admin: Addr, + pub claim_msg_plaintext: String, + pub airdrop_amount: u128, + pub whitelist_address: Option, + pub minter_address: Addr, + pub name_discount_wl_address: Addr, + pub name_collection_address: Addr, + pub airdrop_count_limit: u32, +} + +pub const CONFIG: Item = Item::new("cfg"); +pub const ADDRS_TO_MINT_COUNT: Map<&str, u32> = Map::new("amc"); +pub const IS_ADDRESS_REGISTERED: Map<&str, bool> = Map::new("iar"); +pub const HAS_CLAIMED: Map<&str, bool> = Map::new("hc"); +pub const AIRDROP_COUNT: Item = Item::new("ac"); diff --git a/contracts/dydx-airdrop/src/validation.rs b/contracts/dydx-airdrop/src/validation.rs new file mode 100644 index 000000000..a93ef92f0 --- /dev/null +++ b/contracts/dydx-airdrop/src/validation.rs @@ -0,0 +1,215 @@ +use crate::state::CONFIG; +use crate::state::{HAS_CLAIMED, IS_ADDRESS_REGISTERED}; +use crate::ContractError; +use cosmwasm_std::{DepsMut, MessageInfo, StdResult}; + +use cosmwasm_std::StdError; +use ethereum_verify::verify_ethereum_text; + +use crate::{query::query_airdrop_is_eligible, state::Config}; + +use crate::contract::INSTANTIATION_FEE; +use crate::msg::InstantiateMsg; +use crate::query::query_collection_address; +use cosmwasm_std::Uint128; +use cw721::TokensResponse; +use cw_utils::must_pay; +use sg721_base::msg::QueryMsg; +use sg721_name::msg::QueryMsg as NameQueryMsg; +use sg_std::NATIVE_DENOM; + +const MIN_AIRDROP: u128 = 10_000_000; // 10 STARS +const MAX_AIRDROP: u128 = 100_000_000_000_000; // 100 million STARS + +pub fn validate_instantiation_params( + info: MessageInfo, + msg: InstantiateMsg, +) -> Result<(), ContractError> { + validate_airdrop_amount(msg.airdrop_amount)?; + validate_plaintext_msg(msg.claim_msg_plaintext)?; + validate_instantiate_funds(info)?; + Ok(()) +} + +pub fn validate_instantiate_funds(info: MessageInfo) -> Result<(), ContractError> { + let amount = must_pay(&info, NATIVE_DENOM)?; + if amount < Uint128::from(INSTANTIATION_FEE) { + return Err(ContractError::InsufficientFundsInstantiate {}); + }; + Ok(()) +} + +pub fn validate_airdrop_amount(airdrop_amount: u128) -> Result { + if airdrop_amount < MIN_AIRDROP { + return Err(ContractError::AirdropTooSmall {}); + }; + if airdrop_amount > MAX_AIRDROP { + return Err(ContractError::AirdropTooBig {}); + }; + Ok(airdrop_amount) +} + +pub fn validate_plaintext_msg(plaintext_msg: String) -> Result<(), ContractError> { + if !plaintext_msg.contains("{wallet}") { + return Err(ContractError::PlaintextMsgNoWallet {}); + } + if plaintext_msg.len() > 1000 { + return Err(ContractError::PlaintextTooLong {}); + } + Ok(()) +} + +pub fn compute_plaintext_msg(config: &Config, info: MessageInfo) -> String { + str::replace( + &config.claim_msg_plaintext, + "{wallet}", + info.sender.as_ref(), + ) +} + +pub fn assert_lower_case(eth_address: String) -> Result<(), ContractError> { + match eth_address.to_lowercase() == eth_address { + true => Ok(()), + false => Err(ContractError::EthAddressShouldBeLower { + address: eth_address, + }), + } +} + +pub fn validate_registration( + deps: &DepsMut, + info: MessageInfo, + eth_address: String, + eth_sig: String, + config: Config, +) -> Result<(), ContractError> { + assert_lower_case(eth_address.clone())?; + validate_is_eligible(deps, eth_address.clone())?; + validate_eth_sig(deps, info, eth_address.clone(), eth_sig, config)?; + check_previous_registration(deps, ð_address)?; + Ok(()) +} + +pub fn validate_claim( + deps: &DepsMut, + info: MessageInfo, + eth_address: String, + eth_sig: String, + config: Config, +) -> Result<(), ContractError> { + assert_lower_case(eth_address.clone())?; + validate_is_eligible(deps, eth_address.clone())?; + validate_eth_sig(deps, info.clone(), eth_address.clone(), eth_sig, config)?; + check_previous_claim(deps, ð_address.clone())?; + validate_collection_mint(deps, info.clone())?; + validate_name_mint_and_association(deps, info.clone())?; + Ok(()) +} + +fn validate_is_eligible(deps: &DepsMut, eth_address: String) -> Result<(), ContractError> { + let eligible = query_airdrop_is_eligible(deps.as_ref(), eth_address.clone())?; + match eligible { + true => Ok(()), + false => Err(ContractError::AddressNotEligible { + address: eth_address, + }), + } +} + +fn validate_eth_sig( + deps: &DepsMut, + info: MessageInfo, + eth_address: String, + eth_sig: String, + config: Config, +) -> Result<(), ContractError> { + let valid_eth_sig = validate_ethereum_text(deps, info, &config, eth_sig, eth_address.clone())?; + match valid_eth_sig { + true => Ok(()), + false => Err(ContractError::InvalidEthSignature { + address: eth_address, + }), + } +} + +pub fn validate_collection_mint(deps: &DepsMut, info: MessageInfo) -> Result<(), ContractError> { + let collection_address = query_collection_address(deps)?; + let tokens_response: TokensResponse = deps.querier.query_wasm_smart( + collection_address, + &QueryMsg::Tokens { + owner: String::from(info.sender.clone()), + start_after: None, + limit: None, + }, + )?; + if tokens_response.tokens.is_empty() { + return Err(ContractError::CollectionNotMinted {}); + } + Ok(()) +} + +pub fn validate_name_mint_and_association( + deps: &DepsMut, + info: MessageInfo, +) -> Result<(), ContractError> { + let config = CONFIG.load(deps.storage)?; + let name_collection_address = config.name_collection_address; + let tokens_response: TokensResponse = deps.querier.query_wasm_smart( + name_collection_address.clone(), + &QueryMsg::Tokens { + owner: String::from(info.sender.clone()), + start_after: None, + limit: None, + }, + )?; + if tokens_response.tokens.is_empty() { + return Err(ContractError::NameNotMinted {}); + }; + let _associated_name: String = deps.querier.query_wasm_smart( + name_collection_address.clone(), + &NameQueryMsg::Name { + address: String::from(info.sender.clone()), + }, + )?; + Ok(()) +} + +pub fn validate_ethereum_text( + deps: &DepsMut, + info: MessageInfo, + config: &Config, + eth_sig: String, + eth_address: String, +) -> StdResult { + let plaintext_msg = compute_plaintext_msg(config, info); + match hex::decode(eth_sig.clone()) { + Ok(eth_sig_hex) => { + verify_ethereum_text(deps.as_ref(), &plaintext_msg, ð_sig_hex, ð_address) + } + Err(_) => Err(StdError::InvalidHex { + msg: format!("Could not decode {eth_sig}"), + }), + } +} + +pub fn check_previous_registration(deps: &DepsMut, eth_address: &str) -> Result<(), ContractError> { + let registered = IS_ADDRESS_REGISTERED + .load(deps.storage, eth_address) + .unwrap_or(false); + if registered { + return Err(ContractError::AlreadyRegistered { + address: eth_address.to_string(), + }); + } + Ok(()) +} + +pub fn check_previous_claim(deps: &DepsMut, eth_address: &str) -> Result<(), ContractError> { + let already_claimed = HAS_CLAIMED.load(deps.storage, eth_address).unwrap_or(false); + if already_claimed { + return Err(ContractError::AlreadyClaimed { + address: eth_address.to_string(), + }); + } + Ok(()) +} diff --git a/contracts/rekt-airdrop/.cargo/config b/contracts/rekt-airdrop/.cargo/config new file mode 100644 index 000000000..de2d36ac7 --- /dev/null +++ b/contracts/rekt-airdrop/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +wasm-debug = "build --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" diff --git a/contracts/rekt-airdrop/.gitignore b/contracts/rekt-airdrop/.gitignore new file mode 100644 index 000000000..dfdaaa6bc --- /dev/null +++ b/contracts/rekt-airdrop/.gitignore @@ -0,0 +1,15 @@ +# 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/sg-eth-airdrop/Cargo.toml b/contracts/rekt-airdrop/Cargo.toml similarity index 96% rename from contracts/sg-eth-airdrop/Cargo.toml rename to contracts/rekt-airdrop/Cargo.toml index ffb4ec5fd..c01cc3cc7 100644 --- a/contracts/sg-eth-airdrop/Cargo.toml +++ b/contracts/rekt-airdrop/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "sg-eth-airdrop" +name = "rekt-airdrop" authors = ["Michael Scotto "] -description = "Stargaze Ethereum Airdrop" +description = "Stargaze Rekt Airdrop" version = { workspace = true } edition = { workspace = true } homepage = { workspace = true } diff --git a/contracts/rekt-airdrop/rustfmt.toml b/contracts/rekt-airdrop/rustfmt.toml new file mode 100644 index 000000000..6918e2237 --- /dev/null +++ b/contracts/rekt-airdrop/rustfmt.toml @@ -0,0 +1,14 @@ +# stable +newline_style = "unix" +hard_tabs = false +tab_spaces = 4 + +# unstable... should we require `rustup run nightly cargo fmt` ? +# or just update the style guide when they are stable? +#fn_single_line = true +#format_code_in_doc_comments = true +#overflow_delimited_expr = true +#reorder_impl_items = true +#struct_field_align_threshold = 20 +#struct_lit_single_line = true +#report_todo = "Always" diff --git a/contracts/rekt-airdrop/schema/raw/execute.json b/contracts/rekt-airdrop/schema/raw/execute.json new file mode 100644 index 000000000..78ab6b551 --- /dev/null +++ b/contracts/rekt-airdrop/schema/raw/execute.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "claim_airdrop" + ], + "properties": { + "claim_airdrop": { + "type": "object", + "required": [ + "eth_address", + "eth_sig" + ], + "properties": { + "eth_address": { + "type": "string" + }, + "eth_sig": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/rekt-airdrop/schema/raw/instantiate.json b/contracts/rekt-airdrop/schema/raw/instantiate.json new file mode 100644 index 000000000..d7e7e85ad --- /dev/null +++ b/contracts/rekt-airdrop/schema/raw/instantiate.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "addresses", + "admin", + "airdrop_amount", + "claim_msg_plaintext", + "minter_address", + "per_address_limit", + "whitelist_code_id" + ], + "properties": { + "addresses": { + "type": "array", + "items": { + "type": "string" + } + }, + "admin": { + "$ref": "#/definitions/Addr" + }, + "airdrop_amount": { + "type": "integer", + "format": "uint128", + "minimum": 0.0 + }, + "claim_msg_plaintext": { + "type": "string" + }, + "minter_address": { + "$ref": "#/definitions/Addr" + }, + "per_address_limit": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "whitelist_code_id": { + "type": "integer", + "format": "uint64", + "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" + } + } +} diff --git a/contracts/rekt-airdrop/schema/raw/query.json b/contracts/rekt-airdrop/schema/raw/query.json new file mode 100644 index 000000000..c21aaf535 --- /dev/null +++ b/contracts/rekt-airdrop/schema/raw/query.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "airdrop_eligible" + ], + "properties": { + "airdrop_eligible": { + "type": "object", + "required": [ + "eth_address" + ], + "properties": { + "eth_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_minter" + ], + "properties": { + "get_minter": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/rekt-airdrop/schema/raw/response_to_airdrop_eligible.json b/contracts/rekt-airdrop/schema/raw/response_to_airdrop_eligible.json new file mode 100644 index 000000000..a7fe2bfe9 --- /dev/null +++ b/contracts/rekt-airdrop/schema/raw/response_to_airdrop_eligible.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Boolean", + "type": "boolean" +} diff --git a/contracts/rekt-airdrop/schema/raw/response_to_get_minter.json b/contracts/rekt-airdrop/schema/raw/response_to_get_minter.json new file mode 100644 index 000000000..4c7f1934d --- /dev/null +++ b/contracts/rekt-airdrop/schema/raw/response_to_get_minter.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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/sg-eth-airdrop/schema/sg-eth-airdrop.json b/contracts/rekt-airdrop/schema/rekt-airdrop.json similarity index 99% rename from contracts/sg-eth-airdrop/schema/sg-eth-airdrop.json rename to contracts/rekt-airdrop/schema/rekt-airdrop.json index ddee384ab..2a3cbbf3c 100644 --- a/contracts/sg-eth-airdrop/schema/sg-eth-airdrop.json +++ b/contracts/rekt-airdrop/schema/rekt-airdrop.json @@ -1,5 +1,5 @@ { - "contract_name": "sg-eth-airdrop", + "contract_name": "rekt-airdrop", "contract_version": "3.5.0", "idl_version": "1.0.0", "instantiate": { diff --git a/contracts/rekt-airdrop/src/bin/schema.rs b/contracts/rekt-airdrop/src/bin/schema.rs new file mode 100644 index 000000000..faf5130f3 --- /dev/null +++ b/contracts/rekt-airdrop/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use rekt_airdrop::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/contracts/sg-eth-airdrop/src/claim_airdrop.rs b/contracts/rekt-airdrop/src/claim_airdrop.rs similarity index 90% rename from contracts/sg-eth-airdrop/src/claim_airdrop.rs rename to contracts/rekt-airdrop/src/claim_airdrop.rs index 7ac8d199a..fa486f030 100644 --- a/contracts/sg-eth-airdrop/src/claim_airdrop.rs +++ b/contracts/rekt-airdrop/src/claim_airdrop.rs @@ -19,15 +19,17 @@ pub fn claim_airdrop( eth_sig: String, ) -> Result { let config = CONFIG.load(deps.storage)?; + let eth_address_lower = eth_address.to_ascii_lowercase(); + validation::assert_lower_case(eth_address_lower.clone())?; validate_claim( &deps, info.clone(), - eth_address.clone(), + eth_address_lower.clone(), eth_sig, config.clone(), )?; let res = claim_and_whitelist_add(&deps, info, config.airdrop_amount)?; - increment_local_mint_count_for_address(deps, eth_address)?; + increment_local_mint_count_for_address(deps, eth_address_lower)?; Ok(res.add_attribute("claimed_amount", config.airdrop_amount.to_string())) } @@ -100,6 +102,15 @@ mod validation { ) } + pub fn assert_lower_case(eth_address: String) -> Result<(), ContractError> { + match eth_address.to_lowercase() == eth_address { + true => Ok(()), + false => Err(ContractError::EthAddressShouldBeLower { + address: eth_address, + }), + } + } + pub fn validate_claim( deps: &DepsMut, info: MessageInfo, @@ -107,6 +118,7 @@ mod validation { eth_sig: String, config: Config, ) -> Result<(), ContractError> { + assert_lower_case(eth_address.clone())?; validate_is_eligible(deps, eth_address.clone())?; validate_eth_sig(deps, info, eth_address.clone(), eth_sig, config)?; validate_mints_remaining(deps, ð_address)?; diff --git a/contracts/sg-eth-airdrop/src/contract.rs b/contracts/rekt-airdrop/src/contract.rs similarity index 98% rename from contracts/sg-eth-airdrop/src/contract.rs rename to contracts/rekt-airdrop/src/contract.rs index dee5d9d46..5efbc570c 100644 --- a/contracts/sg-eth-airdrop/src/contract.rs +++ b/contracts/rekt-airdrop/src/contract.rs @@ -13,7 +13,7 @@ use sg_std::Response; use build_message::{state_config, whitelist_instantiate}; use validation::validate_instantiation_params; -const CONTRACT_NAME: &str = "crates.io:sg-eth-airdrop"; +const CONTRACT_NAME: &str = "crates.io:rekt-airdrop"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const INSTANTIATION_FEE: u128 = 100_000_000; // 100 STARS diff --git a/contracts/sg-eth-airdrop/src/error.rs b/contracts/rekt-airdrop/src/error.rs similarity index 92% rename from contracts/sg-eth-airdrop/src/error.rs rename to contracts/rekt-airdrop/src/error.rs index 67e7b7a64..4d99551f5 100644 --- a/contracts/sg-eth-airdrop/src/error.rs +++ b/contracts/rekt-airdrop/src/error.rs @@ -51,4 +51,7 @@ pub enum ContractError { #[error("Plaintext message is too long")] PlaintextTooLong {}, + + #[error("Address {address} should be converted to lowercase")] + EthAddressShouldBeLower { address: String }, } diff --git a/contracts/sg-eth-airdrop/src/lib.rs b/contracts/rekt-airdrop/src/lib.rs similarity index 100% rename from contracts/sg-eth-airdrop/src/lib.rs rename to contracts/rekt-airdrop/src/lib.rs diff --git a/contracts/sg-eth-airdrop/src/msg.rs b/contracts/rekt-airdrop/src/msg.rs similarity index 100% rename from contracts/sg-eth-airdrop/src/msg.rs rename to contracts/rekt-airdrop/src/msg.rs diff --git a/contracts/sg-eth-airdrop/src/query.rs b/contracts/rekt-airdrop/src/query.rs similarity index 100% rename from contracts/sg-eth-airdrop/src/query.rs rename to contracts/rekt-airdrop/src/query.rs diff --git a/contracts/rekt-airdrop/src/reply.rs b/contracts/rekt-airdrop/src/reply.rs new file mode 100644 index 000000000..90f98d77b --- /dev/null +++ b/contracts/rekt-airdrop/src/reply.rs @@ -0,0 +1,37 @@ +#[cfg(not(feature = "library"))] +use crate::error::ContractError; +use crate::state::CONFIG; +use cosmwasm_std::entry_point; +use cosmwasm_std::{DepsMut, Env, Reply}; +use cw_utils::{parse_reply_instantiate_data, MsgInstantiateContractResponse, ParseReplyError}; +use sg_std::Response; + +const INIT_WHITELIST_REPLY_ID: u64 = 1; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { + if msg.id != INIT_WHITELIST_REPLY_ID { + return Err(ContractError::InvalidReplyID {}); + } + let reply = parse_reply_instantiate_data(msg); + match_reply(deps, reply) +} + +fn match_reply( + deps: DepsMut, + reply: Result, +) -> Result { + match reply { + Ok(res) => { + let whitelist_address = &res.contract_address; + let mut config = CONFIG.load(deps.storage)?; + config.whitelist_address = Some(whitelist_address.to_string()); + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default() + .add_attribute("action", "init_whitelist_reply") + .add_attribute("whitelist_address", whitelist_address)) + } + Err(_) => Err(ContractError::ReplyOnSuccess {}), + } +} diff --git a/contracts/sg-eth-airdrop/src/state.rs b/contracts/rekt-airdrop/src/state.rs similarity index 100% rename from contracts/sg-eth-airdrop/src/state.rs rename to contracts/rekt-airdrop/src/state.rs diff --git a/contracts/sg-eth-airdrop/src/testing.rs b/contracts/sg-eth-airdrop/src/testing.rs deleted file mode 100644 index 2103f40ee..000000000 --- a/contracts/sg-eth-airdrop/src/testing.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[cfg(test)] -mod constants; -#[cfg(test)] -mod setup; -#[cfg(test)] -mod tests; diff --git a/contracts/whitelists/whitelist-flex/src/contract.rs b/contracts/whitelists/whitelist-flex/src/contract.rs index dc6e56d84..a415d6e03 100644 --- a/contracts/whitelists/whitelist-flex/src/contract.rs +++ b/contracts/whitelists/whitelist-flex/src/contract.rs @@ -26,7 +26,7 @@ const CONTRACT_NAME: &str = "crates.io:sg-whitelist-flex"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); // contract governance params -pub const MAX_MEMBERS: u32 = 5000; +pub const MAX_MEMBERS: u32 = 25_000; pub const PRICE_PER_1000_MEMBERS: u128 = 100_000_000; pub const MIN_MINT_PRICE: u128 = 0; @@ -233,11 +233,10 @@ pub fn execute_add_members( }); } let addr = deps.api.addr_validate(&add.address)?; - if WHITELIST.has(deps.storage, addr.clone()) { - return Err(ContractError::DuplicateMember(addr.to_string())); + if !WHITELIST.has(deps.storage, addr.clone()) { + WHITELIST.save(deps.storage, addr, &add.mint_count)?; + config.num_members += 1; } - WHITELIST.save(deps.storage, addr, &add.mint_count)?; - config.num_members += 1; } CONFIG.save(deps.storage, &config)?; diff --git a/contracts/whitelists/whitelist-immutable-flex/.cargo/config b/contracts/whitelists/whitelist-immutable-flex/.cargo/config new file mode 100644 index 000000000..ab407a024 --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/whitelists/whitelist-immutable-flex/.gitignore b/contracts/whitelists/whitelist-immutable-flex/.gitignore new file mode 100644 index 000000000..dfdaaa6bc --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/.gitignore @@ -0,0 +1,15 @@ +# 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/whitelists/whitelist-immutable-flex/Cargo.toml b/contracts/whitelists/whitelist-immutable-flex/Cargo.toml new file mode 100644 index 000000000..7c4144f20 --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "whitelist-immutable-flex" +authors = ["Michael S"] +description = "Store-once whitelist for read only use cases" +version = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } +license = { workspace = true } + +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 = [] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw-controllers = "0.16.0" +cw2 = { workspace = true } +cw-utils = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +sg-whitelist = { workspace = true } +sg-std = { workspace = true } diff --git a/contracts/whitelists/whitelist-immutable-flex/examples/schema.rs b/contracts/whitelists/whitelist-immutable-flex/examples/schema.rs new file mode 100644 index 000000000..0ece61624 --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/examples/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use whitelist_immutable_flex::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/contracts/whitelists/whitelist-immutable-flex/schema/whitelist-immutable.json b/contracts/whitelists/whitelist-immutable-flex/schema/whitelist-immutable.json new file mode 100644 index 000000000..6d9ae48ea --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/schema/whitelist-immutable.json @@ -0,0 +1,206 @@ +{ + "contract_name": "whitelist-immutable", + "contract_version": "3.5.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "addresses", + "per_address_limit" + ], + "properties": { + "addresses": { + "type": "array", + "items": { + "type": "string" + } + }, + "mint_discount_bps": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "per_address_limit": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "type": "string", + "enum": [] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "includes_address" + ], + "properties": { + "includes_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "admin" + ], + "properties": { + "admin": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "address_count" + ], + "properties": { + "address_count": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "per_address_limit" + ], + "properties": { + "per_address_limit": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "address_count": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint64", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "admin": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint64", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/Config" + } + }, + "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" + }, + "Config": { + "type": "object", + "required": [ + "admin", + "per_address_limit" + ], + "properties": { + "admin": { + "$ref": "#/definitions/Addr" + }, + "mint_discount_bps": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "per_address_limit": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "includes_address": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Boolean", + "type": "boolean" + }, + "per_address_limit": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PerAddressLimitResponse", + "type": "object", + "required": [ + "limit" + ], + "properties": { + "limit": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/whitelists/whitelist-immutable-flex/src/contract.rs b/contracts/whitelists/whitelist-immutable-flex/src/contract.rs new file mode 100644 index 000000000..f2fca7238 --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/src/contract.rs @@ -0,0 +1,106 @@ +use crate::state::{Config, CONFIG, TOTAL_ADDRESS_COUNT, WHITELIST}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, StdResult}; +use cw2::set_contract_version; + +use crate::error::ContractError; +use crate::msg::{ConfigResponse, ExecuteMsg, InstantiateMsg, Member, MemberResponse, QueryMsg}; +use cw_utils::nonpayable; +use sg_std::Response; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:whitelist-immutable-flex"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + mut deps: DepsMut, + _env: Env, + info: MessageInfo, + mut msg: InstantiateMsg, +) -> Result { + nonpayable(&info)?; + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let config = Config { + admin: info.sender, + mint_discount_bps: msg.mint_discount_bps, + }; + + msg.members.dedup(); + let count = update_whitelist(&mut deps, msg)?; + validate_nonempty_whitelist(count)?; + TOTAL_ADDRESS_COUNT.save(deps.storage, &count)?; + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default() + .add_attribute("action", "instantiate") + .add_attribute("contract_name", CONTRACT_NAME) + .add_attribute("contract_version", CONTRACT_VERSION)) +} + +fn update_whitelist(deps: &mut DepsMut, msg: InstantiateMsg) -> Result { + let mut count = 0u64; + for member in msg.members.into_iter() { + let address_lower = member.address.clone().to_ascii_lowercase(); + WHITELIST.save(deps.storage, &address_lower, &member.mint_count)?; + count += 1; + } + Ok(count) +} + +fn validate_nonempty_whitelist(count: u64) -> Result { + if count < 1 { + return Err(ContractError::EmptyWhitelist {}); + } + Ok(true) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: ExecuteMsg, +) -> Result { + Ok(Response::new()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_json_binary(&query_config(deps)?), + QueryMsg::HasMember { address } => to_json_binary(&query_has_member(deps, address)?), + QueryMsg::Admin {} => to_json_binary(&query_admin(deps)?), + QueryMsg::AddressCount {} => to_json_binary(&query_address_count(deps)?), + QueryMsg::Member { address } => to_json_binary(&query_member(deps, address)?), + } +} + +pub fn query_config(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + Ok(ConfigResponse { config }) +} + +pub fn query_has_member(deps: Deps, address: String) -> StdResult { + Ok(WHITELIST.has(deps.storage, &address)) +} + +pub fn query_admin(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + Ok(config.admin.to_string()) +} + +pub fn query_address_count(deps: Deps) -> StdResult { + TOTAL_ADDRESS_COUNT.load(deps.storage) +} + +pub fn query_member(deps: Deps, address: String) -> StdResult { + let mint_count = WHITELIST.load(deps.storage, &address)?; + Ok(MemberResponse { + member: Member { + address, + mint_count, + }, + }) +} diff --git a/contracts/whitelists/whitelist-immutable-flex/src/error.rs b/contracts/whitelists/whitelist-immutable-flex/src/error.rs new file mode 100644 index 000000000..9e7442933 --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/src/error.rs @@ -0,0 +1,28 @@ +use cosmwasm_std::StdError; +use cw_utils::PaymentError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("AddressNotFound {addr}")] + AddressNotFound { addr: String }, + + #[error("OverPerAddressLimit")] + OverPerAddressLimit {}, + + #[error("AddressAlreadyExists {addr}")] + AddressAlreadyExists { addr: String }, + // Add any other custom errors you like here. + // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. + #[error("Empty whitelist, must provide at least one address")] + EmptyWhitelist {}, +} diff --git a/contracts/whitelists/whitelist-immutable-flex/src/helpers.rs b/contracts/whitelists/whitelist-immutable-flex/src/helpers.rs new file mode 100644 index 000000000..2fef1db7a --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/src/helpers.rs @@ -0,0 +1,55 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{to_json_binary, Addr, QuerierWrapper, QueryRequest, StdResult, WasmQuery}; + +use crate::msg::MemberResponse; +use crate::{ + msg::{ConfigResponse, QueryMsg}, + state::Config, +}; + +/// CwTemplateContract is a wrapper around Addr that provides a lot of helpers +/// for working with this. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct WhitelistImmutableFlexContract(pub Addr); + +impl WhitelistImmutableFlexContract { + pub fn addr(&self) -> Addr { + self.0.clone() + } + + pub fn includes(&self, querier: &QuerierWrapper, address: String) -> StdResult { + let includes: bool = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.addr().into(), + msg: to_json_binary(&QueryMsg::HasMember { address })?, + }))?; + Ok(includes) + } + + pub fn address_count(&self, querier: &QuerierWrapper) -> StdResult { + let address_count: u64 = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.addr().into(), + msg: to_json_binary(&QueryMsg::AddressCount {})?, + }))?; + Ok(address_count) + } + + pub fn config(&self, querier: &QuerierWrapper) -> StdResult { + let res: ConfigResponse = querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.addr().into(), + msg: to_json_binary(&QueryMsg::Config {})?, + }))?; + + Ok(res.config) + } + + pub fn mint_count(&self, querier: &QuerierWrapper, address: String) -> StdResult { + let member_response: MemberResponse = + querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.addr().into(), + msg: to_json_binary(&QueryMsg::Member { address })?, + }))?; + Ok(member_response.member.mint_count) + } +} diff --git a/contracts/whitelists/whitelist-immutable-flex/src/lib.rs b/contracts/whitelists/whitelist-immutable-flex/src/lib.rs new file mode 100644 index 000000000..0f47974b1 --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +mod error; +pub mod helpers; +pub mod msg; +pub mod state; +pub use crate::error::ContractError; diff --git a/contracts/whitelists/whitelist-immutable-flex/src/msg.rs b/contracts/whitelists/whitelist-immutable-flex/src/msg.rs new file mode 100644 index 000000000..9342eede6 --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/src/msg.rs @@ -0,0 +1,42 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; + +use crate::state::Config; + +#[cw_serde] +pub struct Member { + pub address: String, + pub mint_count: u32, +} +#[cw_serde] +pub struct InstantiateMsg { + pub members: Vec, + pub mint_discount_bps: Option, +} + +#[cw_serde] +pub enum ExecuteMsg {} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(ConfigResponse)] + Config {}, + #[returns(bool)] + HasMember { address: String }, + #[returns(MemberResponse)] + Member { address: String }, + #[returns(u64)] + Admin {}, + #[returns(u64)] + AddressCount {}, +} + +#[cw_serde] +pub struct ConfigResponse { + pub config: Config, +} + +#[cw_serde] +pub struct MemberResponse { + pub member: Member, +} diff --git a/contracts/whitelists/whitelist-immutable-flex/src/state.rs b/contracts/whitelists/whitelist-immutable-flex/src/state.rs new file mode 100644 index 000000000..ff078d849 --- /dev/null +++ b/contracts/whitelists/whitelist-immutable-flex/src/state.rs @@ -0,0 +1,21 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Decimal, Uint128}; +use cw_storage_plus::{Item, Map}; + +#[cw_serde] +pub struct Config { + pub admin: Addr, + pub mint_discount_bps: Option, +} + +impl Config { + pub fn mint_discount(&self) -> Option { + self.mint_discount_bps + .map(|v| Decimal::percent(v) / Uint128::from(100u128)) + } +} + +pub const CONFIG: Item = Item::new("config"); +pub const TOTAL_ADDRESS_COUNT: Item = Item::new("total_address_count"); +// Holds all addresses and mint count +pub const WHITELIST: Map<&str, u32> = Map::new("wl"); diff --git a/contracts/whitelists/whitelist-immutable/src/contract.rs b/contracts/whitelists/whitelist-immutable/src/contract.rs index dead0477a..c7940d696 100644 --- a/contracts/whitelists/whitelist-immutable/src/contract.rs +++ b/contracts/whitelists/whitelist-immutable/src/contract.rs @@ -44,7 +44,8 @@ pub fn instantiate( fn update_whitelist(deps: &mut DepsMut, msg: InstantiateMsg) -> Result { let mut count = 0u64; for address in msg.addresses.into_iter() { - WHITELIST.save(deps.storage, &address, &true)?; + let address_lower = address.clone().to_ascii_lowercase(); + WHITELIST.save(deps.storage, &address_lower, &true)?; count += 1; } Ok(count) diff --git a/packages/ethereum-verify/src/decode.rs b/packages/ethereum-verify/src/decode.rs index 0628aac82..8b823da72 100644 --- a/packages/ethereum-verify/src/decode.rs +++ b/packages/ethereum-verify/src/decode.rs @@ -10,6 +10,7 @@ use sha3::{Digest, Keccak256}; /// [EIP-155]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md pub fn get_recovery_param(v: u8) -> StdResult { match v { + 0 | 1 => Ok(v), 27 => Ok(0), 28 => Ok(1), _ => Err(StdError::generic_err("Values of v other than 27 and 28 not supported. Replay protection (EIP-155) cannot be used here.")) diff --git a/scripts/publish-contracts.sh b/scripts/publish-contracts.sh index 14d8a3fa4..5192f7734 100755 --- a/scripts/publish-contracts.sh +++ b/scripts/publish-contracts.sh @@ -34,6 +34,8 @@ cd contracts/minters/vending-minter-merkle-wl && cargo publish && cd ../../.. sleep 15 cd contracts/minters/vending-minter-featured && cargo publish && cd ../../.. sleep 10 -cd contracts/sg-eth-airdrop && cargo publish && cd ../.. +cd contracts/rekt-airdrop && cargo publish && cd ../.. +sleep 15 +cd contracts/dydx-airdrop && cargo publish && cd ../.. sleep 15 cd test-suite && cargo publish && cd .. diff --git a/scripts/schema.sh b/scripts/schema.sh index 3b9931704..e028e4f60 100755 --- a/scripts/schema.sh +++ b/scripts/schema.sh @@ -30,7 +30,8 @@ for d in contracts/whitelists/*; do cd ../../.. fi done -cd contracts/sg-eth-airdrop && cargo schema && rm -rf schema/raw && cd ../.. +cd contracts/rekt-airdrop && cargo schema && rm -rf schema/raw && cd ../.. +cd contracts/dydx-airdrop && cargo schema && rm -rf schema/raw && cd ../.. cd contracts/splits && cargo schema && rm -rf schema/raw && cd ../.. cd ts && yarn install && yarn codegen diff --git a/test-suite/Cargo.toml b/test-suite/Cargo.toml index 867d077f1..629f2bec5 100644 --- a/test-suite/Cargo.toml +++ b/test-suite/Cargo.toml @@ -44,8 +44,12 @@ sg721-updatable = { workspace = true } cw4-group = { workspace = true } sg-splits = { workspace = true } anyhow = "1.0.57" -sg-eth-airdrop = { workspace = true } +rekt-airdrop = { workspace = true } +dydx-airdrop = { workspace = true } whitelist-immutable = { workspace = true, features = ["library"] } +whitelist-immutable-flex = { workspace = true, features = ["library"] } +whitelist-updatable = { workspace = true, features = ["library"] } +sg721-name = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true, features = ["library"] } sg2 = { workspace = true } @@ -53,7 +57,8 @@ sg721 = { workspace = true } sg-metadata = { workspace = true } open-edition-factory = { workspace = true, features = ["library"] } open-edition-minter = { workspace = true, features = ["library"] } -sg-whitelist = { workspace = true, features = ["library"] } +sg-whitelist = { workspace = true, features = ["library"] } +sg-whitelist-flex = { workspace = true, features = ["library"] } vending-factory = { workspace = true, features = ["library"] } vending-minter = { workspace = true, features = ["library"] } base-factory = { workspace = true } diff --git a/test-suite/src/common_setup.rs b/test-suite/src/common_setup.rs index cc10d8105..278072a80 100644 --- a/test-suite/src/common_setup.rs +++ b/test-suite/src/common_setup.rs @@ -4,6 +4,7 @@ pub mod helpers; pub mod msg; pub mod setup_accounts_and_block; pub mod setup_collection_whitelist; +pub mod setup_collection_whitelist_flex; pub mod setup_minter; pub mod setup_whitelist_merkletree; pub mod templates; diff --git a/test-suite/src/common_setup/contract_boxes.rs b/test-suite/src/common_setup/contract_boxes.rs index 65a9fd3d0..f21b7a0e0 100644 --- a/test-suite/src/common_setup/contract_boxes.rs +++ b/test-suite/src/common_setup/contract_boxes.rs @@ -64,6 +64,15 @@ pub fn contract_collection_whitelist() -> Box> Box::new(contract) } +pub fn contract_collection_whitelist_flex() -> Box> { + let contract = ContractWrapper::new( + sg_whitelist_flex::contract::execute, + sg_whitelist_flex::contract::instantiate, + sg_whitelist_flex::contract::query, + ); + Box::new(contract) +} + pub fn contract_open_edition_minter() -> Box> { let contract = ContractWrapper::new( open_edition_minter::contract::execute, @@ -123,11 +132,21 @@ pub fn contract_group() -> Box> { pub fn contract_eth_airdrop() -> Box> { let contract = ContractWrapper::new( - sg_eth_airdrop::contract::execute, - sg_eth_airdrop::contract::instantiate, - sg_eth_airdrop::query::query, + rekt_airdrop::contract::execute, + rekt_airdrop::contract::instantiate, + rekt_airdrop::query::query, ) - .with_reply(sg_eth_airdrop::reply::reply); + .with_reply(rekt_airdrop::reply::reply); + Box::new(contract) +} + +pub fn contract_dydx_airdrop() -> Box> { + let contract = ContractWrapper::new( + dydx_airdrop::contract::execute, + dydx_airdrop::contract::instantiate, + dydx_airdrop::query::query, + ) + .with_reply(dydx_airdrop::reply::reply); Box::new(contract) } @@ -140,6 +159,15 @@ pub fn contract_whitelist_immutable() -> Box> { Box::new(contract) } +pub fn contract_whitelist_immutable_flex() -> Box> { + let contract = ContractWrapper::new( + whitelist_immutable_flex::contract::execute, + whitelist_immutable_flex::contract::instantiate, + whitelist_immutable_flex::contract::query, + ); + Box::new(contract) +} + pub fn contract_whitelist_merkletree() -> Box> { let contract = ContractWrapper::new( whitelist_mtree::contract::execute, diff --git a/test-suite/src/common_setup/setup_collection_whitelist_flex.rs b/test-suite/src/common_setup/setup_collection_whitelist_flex.rs new file mode 100644 index 000000000..ae383f56b --- /dev/null +++ b/test-suite/src/common_setup/setup_collection_whitelist_flex.rs @@ -0,0 +1,116 @@ +use cosmwasm_std::{coin, Addr, Timestamp}; +use cw_multi_test::Executor; +use sg_multi_test::StargazeApp; +use sg_std::{GENESIS_MINT_START_TIME, NATIVE_DENOM}; +use sg_whitelist_flex::msg::InstantiateMsg as WhitelistFlexInstantiateMsg; + +use crate::common_setup::{ + contract_boxes::contract_collection_whitelist_flex, setup_accounts_and_block::setup_block_time, +}; + +pub const WHITELIST_AMOUNT: u128 = 66_000_000; +const ZERO_FEE_WHITELIST: u128 = 0; +pub fn setup_whitelist_flex_contract( + router: &mut StargazeApp, + creator: &Addr, + whitelist_code_id: Option, + denom: Option<&str>, +) -> Addr { + let whitelist_code_id = match whitelist_code_id { + Some(value) => value, + None => router.store_code(contract_collection_whitelist_flex()), + }; + let denom = match denom { + Some(value) => value, + None => NATIVE_DENOM, + }; + + let msg = WhitelistFlexInstantiateMsg { + members: vec![], + start_time: Timestamp::from_nanos(GENESIS_MINT_START_TIME + 100), + end_time: Timestamp::from_nanos(GENESIS_MINT_START_TIME + 10000000), + mint_price: coin(WHITELIST_AMOUNT, denom), + member_limit: 1000, + admins: vec![creator.to_string()], + admins_mutable: true, + whale_cap: None, + }; + router + .instantiate_contract( + whitelist_code_id, + creator.clone(), + &msg, + &[coin(100_000_000, NATIVE_DENOM)], + "whitelist", + None, + ) + .unwrap() +} + +pub fn setup_zero_fee_whitelist_contract( + router: &mut StargazeApp, + creator: &Addr, + whitelist_code_id: Option, +) -> Addr { + let whitelist_code_id = match whitelist_code_id { + Some(value) => value, + None => router.store_code(contract_collection_whitelist_flex()), + }; + + let msg = WhitelistFlexInstantiateMsg { + members: vec![], + start_time: Timestamp::from_nanos(GENESIS_MINT_START_TIME + 100), + end_time: Timestamp::from_nanos(GENESIS_MINT_START_TIME + 10000000), + mint_price: coin(ZERO_FEE_WHITELIST, NATIVE_DENOM), + member_limit: 1000, + admins: vec![creator.to_string()], + admins_mutable: true, + whale_cap: None, + }; + router + .instantiate_contract( + whitelist_code_id, + creator.clone(), + &msg, + &[coin(100_000_000, NATIVE_DENOM)], + "whitelist", + None, + ) + .unwrap() +} + +pub fn configure_collection_whitelist_flex( + router: &mut StargazeApp, + creator: Addr, + buyer: Addr, + minter_addr: Addr, +) -> Addr { + let whitelist_flex_addr = setup_whitelist_flex_contract(router, &creator, None, None); + const AFTER_GENESIS_TIME: Timestamp = Timestamp::from_nanos(GENESIS_MINT_START_TIME + 100); + + // Set to just before genesis mint start time + setup_block_time(router, GENESIS_MINT_START_TIME - 10, None); + + // Update whitelist_expiration fails if not admin + let wl_msg = sg_whitelist_flex::msg::ExecuteMsg::UpdateEndTime(AFTER_GENESIS_TIME); + router + .execute_contract(buyer, whitelist_flex_addr.clone(), &wl_msg, &[]) + .unwrap_err(); + + // Update whitelist_expiration succeeds when from admin + let wl_msg = sg_whitelist_flex::msg::ExecuteMsg::UpdateEndTime(AFTER_GENESIS_TIME); + let res = router.execute_contract(creator.clone(), whitelist_flex_addr.clone(), &wl_msg, &[]); + assert!(res.is_ok()); + + let wl_msg = sg_whitelist_flex::msg::ExecuteMsg::UpdateStartTime(Timestamp::from_nanos(0)); + let res = router.execute_contract(creator.clone(), whitelist_flex_addr.clone(), &wl_msg, &[]); + assert!(res.is_ok()); + + // Set whitelist in minter contract + let set_whitelist_msg = vending_minter::msg::ExecuteMsg::SetWhitelist { + whitelist: whitelist_flex_addr.to_string(), + }; + let res = router.execute_contract(creator, minter_addr, &set_whitelist_msg, &[]); + assert!(res.is_ok()); + whitelist_flex_addr +} diff --git a/test-suite/src/sg_eth_airdrop.rs b/test-suite/src/dydx_airdrop.rs similarity index 100% rename from test-suite/src/sg_eth_airdrop.rs rename to test-suite/src/dydx_airdrop.rs diff --git a/test-suite/src/sg_eth_airdrop/constants.rs b/test-suite/src/dydx_airdrop/constants.rs similarity index 100% rename from test-suite/src/sg_eth_airdrop/constants.rs rename to test-suite/src/dydx_airdrop/constants.rs diff --git a/test-suite/src/dydx_airdrop/constants/claim_constants.rs b/test-suite/src/dydx_airdrop/constants/claim_constants.rs new file mode 100644 index 000000000..d72b6dcbb --- /dev/null +++ b/test-suite/src/dydx_airdrop/constants/claim_constants.rs @@ -0,0 +1,9 @@ +pub const OWNER: &str = "admin0001"; +pub const MOCK_AIRDROP_ADDR_STR: &str = "contract3"; +pub const MOCK_MINTER_ADDR_STR: &str = "contract1"; +pub const MOCK_NAME_DISCOUNT_WL_ADDR_STR: &str = "contract2"; +pub const MOCK_NAME_COLLECTION_ADDR: &str = "contract3"; +pub const STARGAZE_WALLET_01: &str = "0xstargaze_wallet_01"; +pub const STARGAZE_WALLET_02: &str = "0xstargaze_wallet_02"; +pub const CONFIG_PLAINTEXT: &str = "My Stargaze address is {wallet} and I want a Winter Pal."; +pub const NATIVE_DENOM: &str = "ustars"; diff --git a/test-suite/src/sg_eth_airdrop/constants/collection_constants.rs b/test-suite/src/dydx_airdrop/constants/collection_constants.rs similarity index 100% rename from test-suite/src/sg_eth_airdrop/constants/collection_constants.rs rename to test-suite/src/dydx_airdrop/constants/collection_constants.rs diff --git a/test-suite/src/sg_eth_airdrop/setup.rs b/test-suite/src/dydx_airdrop/setup.rs similarity index 100% rename from test-suite/src/sg_eth_airdrop/setup.rs rename to test-suite/src/dydx_airdrop/setup.rs diff --git a/test-suite/src/dydx_airdrop/setup/collection_whitelist_helpers.rs b/test-suite/src/dydx_airdrop/setup/collection_whitelist_helpers.rs new file mode 100644 index 000000000..461f47eac --- /dev/null +++ b/test-suite/src/dydx_airdrop/setup/collection_whitelist_helpers.rs @@ -0,0 +1,74 @@ +use crate::dydx_airdrop::constants::claim_constants::{NATIVE_DENOM, STARGAZE_WALLET_01}; +use crate::dydx_airdrop::constants::collection_constants::{MINT_PRICE, WHITELIST_AMOUNT}; + +use crate::dydx_airdrop::setup::execute_msg::execute_contract_with_msg; +use cosmwasm_std::{coins, Addr}; +use cw_multi_test::{BankSudo, Executor, SudoMsg}; +use sg_multi_test::StargazeApp; + +extern crate whitelist_immutable_flex; + +pub fn update_admin_for_whitelist( + app: &mut StargazeApp, + sender: Addr, + target_admin: Addr, + target_contract: Addr, +) { + // add airdrop contract as admin on whitelist + let update_admin_message = sg_whitelist_flex::msg::ExecuteMsg::UpdateAdmins { + admins: vec![target_admin.to_string()], + }; + let _ = app.execute_contract(sender, target_contract, &update_admin_message, &[]); +} + +pub fn send_funds_to_address(app: &mut StargazeApp, target_address_str: &str, amount: u128) { + app.sudo(SudoMsg::Bank({ + BankSudo::Mint { + to_address: target_address_str.to_string(), + amount: coins(amount, NATIVE_DENOM), + } + })) + .map_err(|err| println!("{err:?}")) + .ok(); +} + +pub fn execute_mint_fail_not_on_whitelist(app: &mut StargazeApp, minter_addr: Addr) { + //before mintlist add, fail + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + let mint_msg = vending_minter::msg::ExecuteMsg::Mint {}; + let res = app.execute_contract( + stargaze_wallet_01, + minter_addr, + &mint_msg, + &coins(MINT_PRICE, NATIVE_DENOM), + ); + + let expected_error = format!("address not on whitelist: {STARGAZE_WALLET_01}"); + assert_eq!(res.unwrap_err().root_cause().to_string(), expected_error); +} + +pub fn execute_airdrop_claim( + app: &mut StargazeApp, + eth_addr_str: String, + eth_sig_str: String, + target_wallet: Addr, + airdrop_contract: Addr, +) { + let claim_message = dydx_airdrop::msg::ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str, + eth_sig: eth_sig_str, + }; + let _ = execute_contract_with_msg(claim_message, app, target_wallet, airdrop_contract).unwrap(); +} + +pub fn execute_mint_success(app: &mut StargazeApp, sender: Addr, minter_addr: Addr) { + //execute the mint + let mint_msg = vending_minter::msg::ExecuteMsg::Mint {}; + let res = app.execute_contract( + sender, + minter_addr, + &mint_msg, + &coins(WHITELIST_AMOUNT, NATIVE_DENOM), + ); + assert!(res.is_ok()) +} diff --git a/test-suite/src/sg_eth_airdrop/setup/configure_mock_minter.rs b/test-suite/src/dydx_airdrop/setup/configure_mock_minter.rs similarity index 89% rename from test-suite/src/sg_eth_airdrop/setup/configure_mock_minter.rs rename to test-suite/src/dydx_airdrop/setup/configure_mock_minter.rs index 516c9913f..f20e8acf1 100644 --- a/test-suite/src/sg_eth_airdrop/setup/configure_mock_minter.rs +++ b/test-suite/src/dydx_airdrop/setup/configure_mock_minter.rs @@ -5,8 +5,8 @@ use crate::common_setup::contract_boxes::contract_vending_factory; use crate::common_setup::setup_minter::vending_minter::mock_params::{ mock_create_minter, mock_params, }; -use crate::sg_eth_airdrop::constants::collection_constants::CREATION_FEE; -use crate::sg_eth_airdrop::setup::mock_minter_contract::mock_minter; +use crate::dydx_airdrop::constants::collection_constants::CREATION_FEE; +use crate::dydx_airdrop::setup::mock_minter_contract::mock_minter; use cosmwasm_std::{coins, Addr, Timestamp}; use cw_multi_test::Executor; use sg2::msg::Sg2ExecuteMsg; @@ -14,7 +14,7 @@ use sg2::tests::mock_collection_params_1; use sg_multi_test::StargazeApp; use sg_std::{GENESIS_MINT_START_TIME, NATIVE_DENOM}; -use crate::sg_eth_airdrop::setup::mock_whitelist_contract::mock_whitelist; +use crate::dydx_airdrop::setup::mock_whitelist_contract::mock_whitelist; fn configure_mock_minter(app: &mut StargazeApp, creator: Addr) { let minter_code_id = app.store_code(mock_minter()); diff --git a/test-suite/src/dydx_airdrop/setup/execute_msg.rs b/test-suite/src/dydx_airdrop/setup/execute_msg.rs new file mode 100644 index 000000000..ec491754d --- /dev/null +++ b/test-suite/src/dydx_airdrop/setup/execute_msg.rs @@ -0,0 +1,80 @@ +use crate::dydx_airdrop::constants::claim_constants::OWNER; +use crate::{ + common_setup::contract_boxes::{contract_dydx_airdrop, contract_whitelist_immutable}, + dydx_airdrop::setup::test_msgs::InstantiateParams, +}; +use anyhow::Error as anyhow_error; +use cosmwasm_std::{coins, Addr}; +use cw_multi_test::error::Error; +use cw_multi_test::{AppResponse, BankSudo, Executor, SudoMsg}; +use dydx_airdrop::msg::{ExecuteMsg, InstantiateMsg}; +use eyre::Result; +use sg_multi_test::StargazeApp; +use sg_std::NATIVE_DENOM; + +pub fn instantiate_contract(params: InstantiateParams) -> Result { + let members = params.members; + let minter_address = params.minter_address; + let admin_account = params.admin_account; + let funds_amount = params.funds_amount; + let claim_msg_plaintext = params.claim_msg_plaintext; + let airdrop_amount = params.airdrop_amount; + let airdrop_count_limit = params.airdrop_count_limit; + params + .app + .sudo(SudoMsg::Bank({ + BankSudo::Mint { + to_address: admin_account.to_string(), + amount: coins(params.funds_amount, NATIVE_DENOM), + } + })) + .map_err(|err| println!("{err:?}")) + .ok(); + + let sg_eth_id = params.app.store_code(contract_dydx_airdrop()); + let whitelist_code_id = params.app.store_code(contract_whitelist_immutable()); + assert_eq!(sg_eth_id, params.expected_airdrop_contract_id); + let name_discount_wl_address = params.name_discount_wl_address; + + let msg: InstantiateMsg = InstantiateMsg { + admin: OWNER.to_string(), + claim_msg_plaintext, + airdrop_amount, + whitelist_code_id, + minter_address, + name_discount_wl_address: name_discount_wl_address.to_string(), + name_collection_address: "name_collection".to_string(), + members: members.clone(), + airdrop_count_limit, + }; + params.app.instantiate_contract( + sg_eth_id, + Addr::unchecked(admin_account.clone()), + &msg, + &coins(funds_amount, NATIVE_DENOM), + "sg-eg-airdrop", + Some(Addr::unchecked(admin_account).to_string()), + ) +} + +pub fn execute_contract_with_msg( + msg: ExecuteMsg, + app: &mut StargazeApp, + user: Addr, + target_address: Addr, +) -> Result { + let result = app.execute_contract(user, target_address, &msg, &[]); + Ok(result.unwrap()) +} + +pub fn execute_contract_error_with_msg( + msg: ExecuteMsg, + app: &mut StargazeApp, + user: Addr, + target_address: Addr, +) -> String { + let result = app + .execute_contract(user, target_address, &msg, &[]) + .unwrap_err(); + result.root_cause().to_string() +} diff --git a/test-suite/src/sg_eth_airdrop/setup/mock_minter_contract.rs b/test-suite/src/dydx_airdrop/setup/mock_minter_contract.rs similarity index 93% rename from test-suite/src/sg_eth_airdrop/setup/mock_minter_contract.rs rename to test-suite/src/dydx_airdrop/setup/mock_minter_contract.rs index ee38b537b..c6e0185c7 100644 --- a/test-suite/src/sg_eth_airdrop/setup/mock_minter_contract.rs +++ b/test-suite/src/dydx_airdrop/setup/mock_minter_contract.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{ to_json_binary, Binary, Coin, Deps, DepsMut, Env, MessageInfo, StdResult, Timestamp, }; use cw_multi_test::{Contract, ContractWrapper}; -use sg_eth_airdrop::error::ContractError; +use dydx_airdrop::error::ContractError; use sg_std::{Response, StargazeMsgWrapper}; use vending_factory::msg::VendingMinterCreateMsg; use vending_minter::msg::{ExecuteMsg, QueryMsg}; @@ -67,6 +67,6 @@ fn query_config() -> ConfigResponse { } pub fn mock_minter() -> Box> { - let contract = ContractWrapper::new(execute, instantiate, query); - Box::new(contract) + let ract = ContractWrapper::new(execute, instantiate, query); + Box::new(ract) } diff --git a/test-suite/src/dydx_airdrop/setup/mock_whitelist_contract.rs b/test-suite/src/dydx_airdrop/setup/mock_whitelist_contract.rs new file mode 100644 index 000000000..4801e1536 --- /dev/null +++ b/test-suite/src/dydx_airdrop/setup/mock_whitelist_contract.rs @@ -0,0 +1,45 @@ +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, StdResult}; +use cw_multi_test::{Contract, ContractWrapper}; +use dydx_airdrop::error::ContractError; +use sg_std::{Response, StargazeMsgWrapper}; +use sg_whitelist_flex::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + mut _msg: InstantiateMsg, +) -> Result { + let res = Response::new(); + Ok(res) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::AddMembers(_) => execute_add_members(), + _ => Err(ContractError::InvalidReplyID {}), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult { + to_json_binary("mock") +} + +fn execute_add_members() -> Result { + let res = Response::new(); + Ok(res) +} + +pub fn mock_whitelist() -> Box> { + let contract = ContractWrapper::new(execute, instantiate, query); + Box::new(contract) +} diff --git a/test-suite/src/sg_eth_airdrop/setup/setup_signatures.rs b/test-suite/src/dydx_airdrop/setup/setup_signatures.rs similarity index 93% rename from test-suite/src/sg_eth_airdrop/setup/setup_signatures.rs rename to test-suite/src/dydx_airdrop/setup/setup_signatures.rs index ea639bf0a..488b4ccc5 100644 --- a/test-suite/src/sg_eth_airdrop/setup/setup_signatures.rs +++ b/test-suite/src/dydx_airdrop/setup/setup_signatures.rs @@ -1,4 +1,4 @@ -use crate::sg_eth_airdrop::constants::claim_constants::CONFIG_PLAINTEXT; +use crate::dydx_airdrop::constants::claim_constants::CONFIG_PLAINTEXT; use async_std::task; use ethers_core::{k256::ecdsa::SigningKey, rand::thread_rng, types::H160}; use ethers_signers::{LocalWallet, Signer, Wallet, WalletError}; diff --git a/test-suite/src/dydx_airdrop/setup/test_msgs.rs b/test-suite/src/dydx_airdrop/setup/test_msgs.rs new file mode 100644 index 000000000..effa1b4f4 --- /dev/null +++ b/test-suite/src/dydx_airdrop/setup/test_msgs.rs @@ -0,0 +1,16 @@ +use sg_multi_test::StargazeApp; +use whitelist_immutable_flex::msg::Member; + +pub struct InstantiateParams<'a> { + pub members: Vec, + pub funds_amount: u128, + pub expected_airdrop_contract_id: u64, + pub minter_address: String, + pub admin_account: String, + pub app: &'a mut StargazeApp, + pub name_discount_wl_address: String, + pub name_collection_address: String, + pub airdrop_count_limit: u32, + pub claim_msg_plaintext: String, + pub airdrop_amount: u128, +} diff --git a/test-suite/src/sg_eth_airdrop/tests.rs b/test-suite/src/dydx_airdrop/tests.rs similarity index 100% rename from test-suite/src/sg_eth_airdrop/tests.rs rename to test-suite/src/dydx_airdrop/tests.rs diff --git a/test-suite/src/dydx_airdrop/tests/test_claim.rs b/test-suite/src/dydx_airdrop/tests/test_claim.rs new file mode 100644 index 000000000..f51fefb48 --- /dev/null +++ b/test-suite/src/dydx_airdrop/tests/test_claim.rs @@ -0,0 +1,795 @@ +use crate::common_setup::contract_boxes::custom_mock_app; +use crate::dydx_airdrop::constants::collection_constants::WHITELIST_AMOUNT; +use crate::dydx_airdrop::setup::configure_mock_minter::configure_mock_minter_with_mock_whitelist; +use crate::dydx_airdrop::setup::setup_signatures::{ + get_msg_plaintext, get_signature, get_wallet_and_sig, +}; +use crate::dydx_airdrop::setup::test_msgs::InstantiateParams; +use async_std::task; +use cosmwasm_std::{Addr, Attribute, Coin, Uint128}; +use dydx_airdrop::msg::{ExecuteMsg, QueryMsg}; + +use ethers_core::rand::thread_rng; +use ethers_signers::{LocalWallet, Signer}; +use sg_multi_test::StargazeApp; + +use crate::dydx_airdrop::constants::claim_constants::{ + CONFIG_PLAINTEXT, MOCK_AIRDROP_ADDR_STR, MOCK_MINTER_ADDR_STR, MOCK_NAME_COLLECTION_ADDR, + MOCK_NAME_DISCOUNT_WL_ADDR_STR, NATIVE_DENOM, OWNER, STARGAZE_WALLET_01, STARGAZE_WALLET_02, +}; + +use crate::dydx_airdrop::setup::execute_msg::{ + execute_contract_error_with_msg, execute_contract_with_msg, instantiate_contract, +}; + +use dydx_airdrop::contract::INSTANTIATION_FEE; +use whitelist_immutable_flex::msg::Member; + +fn query_minter_as_expected(app: &mut StargazeApp, airdrop_contract: Addr, minter_addr: Addr) { + let query_msg = QueryMsg::GetMinter {}; + let result: Addr = app + .wrap() + .query_wasm_smart(airdrop_contract, &query_msg) + .unwrap(); + assert_eq!(minter_addr, result); +} + +#[test] +fn test_instantiate() { + let mut app = custom_mock_app(); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, _, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str, + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 1, + minter_address, + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); +} + +#[test] +fn test_instantiate_plaintext_too_long() { + let long_config_plaintext: String = String::from_utf8(vec![b'X'; 1001]).unwrap() + " {wallet}"; + let mut app = custom_mock_app(); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, _, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str, + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 1, + minter_address, + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: long_config_plaintext, + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + let res = instantiate_contract(params).unwrap_err(); + assert_eq!( + res.root_cause().to_string(), + "Plaintext message is too long" + ); +} + +#[test] +fn test_instantiate_plaintext_missing_wallet() { + let plaintext_config_no_wallet = "This message doesn't have wallet string".to_string(); + let mut app = custom_mock_app(); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, _, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str, + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 1, + minter_address, + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: plaintext_config_no_wallet, + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + let res = instantiate_contract(params).unwrap_err(); + assert_eq!( + res.root_cause().to_string(), + "Plaintext message must contain `{wallet}` string" + ); +} + +#[test] +fn test_airdrop_eligible_query() { + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, _, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str.clone(), + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address: minter_address.clone(), + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + + instantiate_contract(params).unwrap(); + query_minter_as_expected( + &mut app, + airdrop_contract.clone(), + Addr::unchecked(&minter_address), + ); + let query_msg = QueryMsg::AirdropEligible { + eth_address: eth_addr_str.clone(), + }; + let result: bool = app + .wrap() + .query_wasm_smart(airdrop_contract.clone(), &query_msg) + .unwrap(); + assert!(result); + + let query_msg = QueryMsg::AirdropEligible { + eth_address: "0x-some-fake-address".to_string(), + }; + let result: bool = app + .wrap() + .query_wasm_smart(airdrop_contract, &query_msg) + .unwrap(); + assert!(!result); +} + +#[test] +fn test_valid_eth_sig_claim() { + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str.clone(), + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address: minter_address.clone(), + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + query_minter_as_expected( + &mut app, + airdrop_contract.clone(), + Addr::unchecked(&minter_address), + ); + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str.clone(), + eth_sig: eth_sig_str, + }; + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + + let res = execute_contract_with_msg( + claim_message, + &mut app, + stargaze_wallet_01, + airdrop_contract.clone(), + ) + .unwrap(); + let expected_attributes = [ + Attribute { + key: "_contract_addr".to_string(), + value: airdrop_contract.to_string(), + }, + Attribute { + key: "claimed_amount".to_string(), + value: WHITELIST_AMOUNT.to_string(), + }, + ]; + assert_eq!(res.events[1].attributes, expected_attributes); +} + +#[test] +fn test_invalid_eth_sig_claim() { + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, _, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + let (_, eth_sig_str_2, _, _) = get_wallet_and_sig(claim_plaintext.clone()); + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str.clone(), + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address: minter_address.clone(), + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + query_minter_as_expected( + &mut app, + airdrop_contract.clone(), + Addr::unchecked(&minter_address), + ); + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str.clone(), + eth_sig: eth_sig_str_2, + }; + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + let res = execute_contract_error_with_msg( + claim_message, + &mut app, + stargaze_wallet_01, + airdrop_contract, + ); + assert_eq!(res, format!("Address {eth_addr_str} is not eligible")); +} + +#[test] +fn test_can_not_claim_twice() { + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str.clone(), + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address: minter_address.clone(), + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + query_minter_as_expected( + &mut app, + airdrop_contract.clone(), + Addr::unchecked(&minter_address), + ); + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str.clone(), + eth_sig: eth_sig_str, + }; + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + let res = execute_contract_with_msg( + claim_message.clone(), + &mut app, + stargaze_wallet_01.clone(), + airdrop_contract.clone(), + ) + .unwrap(); + + let expected_attributes = [ + Attribute { + key: "_contract_addr".to_string(), + value: airdrop_contract.to_string(), + }, + Attribute { + key: "claimed_amount".to_string(), + value: WHITELIST_AMOUNT.to_string(), + }, + ]; + assert_eq!(res.events[1].attributes, expected_attributes); + let res = execute_contract_error_with_msg( + claim_message, + &mut app, + stargaze_wallet_01, + airdrop_contract, + ); + let expected_error = format!("Address {eth_addr_str} has already claimed all available mints"); + assert_eq!(res, expected_error); +} + +#[test] +fn test_claim_one_valid_airdrop() { + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str.clone(), + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address: minter_address.clone(), + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + query_minter_as_expected( + &mut app, + airdrop_contract.clone(), + Addr::unchecked(&minter_address), + ); + let balances = app + .wrap() + .query_all_balances(stargaze_wallet_01.clone()) + .unwrap(); + assert_eq!(balances, []); + + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str.clone(), + eth_sig: eth_sig_str, + }; + let _ = execute_contract_with_msg( + claim_message, + &mut app, + stargaze_wallet_01.clone(), + airdrop_contract, + ) + .unwrap(); + + let balances = app.wrap().query_all_balances(stargaze_wallet_01).unwrap(); + let expected_balance = [Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(WHITELIST_AMOUNT), + }]; + assert_eq!(balances, expected_balance) +} + +#[test] +fn test_claim_twice_receive_funds_once() { + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + let mut app = custom_mock_app(); + + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str.clone(), + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address: minter_address.clone(), + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + query_minter_as_expected( + &mut app, + airdrop_contract.clone(), + Addr::unchecked(&minter_address), + ); + let balances = app + .wrap() + .query_all_balances(stargaze_wallet_01.clone()) + .unwrap(); + assert_eq!(balances, []); + + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str.clone(), + eth_sig: eth_sig_str, + }; + let _ = execute_contract_with_msg( + claim_message.clone(), + &mut app, + stargaze_wallet_01.clone(), + airdrop_contract.clone(), + ) + .unwrap(); + + let balances = app + .wrap() + .query_all_balances(stargaze_wallet_01.clone()) + .unwrap(); + let expected_balance = [Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(WHITELIST_AMOUNT), + }]; + assert_eq!(balances, expected_balance); + let res = execute_contract_error_with_msg( + claim_message, + &mut app, + stargaze_wallet_01.clone(), + airdrop_contract, + ); + let expected_error = format!("Address {eth_addr_str} has already claimed all available mints"); + assert_eq!(res, expected_error); + let balances = app.wrap().query_all_balances(stargaze_wallet_01).unwrap(); + let expected_balance = [Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(WHITELIST_AMOUNT), + }]; + assert_eq!(balances, expected_balance); +} + +#[test] +fn test_ineligible_does_not_receive_funds() { + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, _, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str, + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address: minter_address.clone(), + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + query_minter_as_expected( + &mut app, + airdrop_contract.clone(), + Addr::unchecked(&minter_address), + ); + let stargaze_wallet_02 = Addr::unchecked(STARGAZE_WALLET_02); + let balances = app + .wrap() + .query_all_balances(stargaze_wallet_02.clone()) + .unwrap(); + assert_eq!(balances, []); + + let claim_plaintext_2 = &get_msg_plaintext(STARGAZE_WALLET_02.to_string()); + let (_, eth_sig_str_2, _, eth_addr_str_2) = get_wallet_and_sig(claim_plaintext_2.clone()); + + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str_2.clone(), + eth_sig: eth_sig_str_2, + }; + let res = execute_contract_error_with_msg( + claim_message, + &mut app, + stargaze_wallet_02.clone(), + airdrop_contract, + ); + let expected_error = format!("Address {eth_addr_str_2} is not eligible"); + assert_eq!(res, expected_error); + let balances = app.wrap().query_all_balances(stargaze_wallet_02).unwrap(); + let expected_balance = []; + assert_eq!(balances, expected_balance) +} + +#[test] +fn test_one_eth_claim_two_stargaze_addresses_invalid() { + let wallet_1 = LocalWallet::new(&mut thread_rng()); + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + let stargaze_wallet_02 = Addr::unchecked(STARGAZE_WALLET_02); + + let claim_plaintext_1 = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let eth_sig_str_1 = task::block_on(get_signature(wallet_1.clone(), claim_plaintext_1)) + .unwrap() + .to_string(); + let eth_address = wallet_1.address(); + let eth_addr_str_1 = format!("{eth_address:?}"); + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str_1.clone(), + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address, + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + + // claim with eth address 1, stargaze wallet 1 + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str_1.clone(), + eth_sig: eth_sig_str_1, + }; + let res = execute_contract_with_msg( + claim_message, + &mut app, + stargaze_wallet_01, + airdrop_contract.clone(), + ) + .unwrap(); + + let expected_attributes = [ + Attribute { + key: "_contract_addr".to_string(), + value: airdrop_contract.to_string(), + }, + Attribute { + key: "claimed_amount".to_string(), + value: WHITELIST_AMOUNT.to_string(), + }, + ]; + assert_eq!(res.events[1].attributes, expected_attributes); + + let claim_plaintext_2 = &get_msg_plaintext(STARGAZE_WALLET_02.to_string()); + let eth_sig_str_2 = task::block_on(get_signature(wallet_1, claim_plaintext_2)) + .unwrap() + .to_string(); + + // claim with eth address 1, stargaze wallet 2 + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str_1.clone(), + eth_sig: eth_sig_str_2, + }; + let expected_error = + format!("Address {eth_addr_str_1} has already claimed all available mints"); + let res_2 = execute_contract_error_with_msg( + claim_message, + &mut app, + stargaze_wallet_02, + airdrop_contract, + ); + assert_eq!(res_2, expected_error); +} + +#[test] +fn test_two_claims_allowed_success() { + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str.clone(), + mint_count: 2, + }], + funds_amount: WHITELIST_AMOUNT * 2 + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address, + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + + let balances = app + .wrap() + .query_all_balances(stargaze_wallet_01.clone()) + .unwrap(); + assert_eq!(balances, []); + + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str.clone(), + eth_sig: eth_sig_str.clone(), + }; + let _ = execute_contract_with_msg( + claim_message, + &mut app, + stargaze_wallet_01.clone(), + airdrop_contract.clone(), + ) + .unwrap(); + + let balances = app + .wrap() + .query_all_balances(stargaze_wallet_01.clone()) + .unwrap(); + let expected_balance = [Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(WHITELIST_AMOUNT), + }]; + assert_eq!(balances, expected_balance); + + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: eth_addr_str.clone(), + eth_sig: eth_sig_str, + }; + let _ = execute_contract_with_msg( + claim_message, + &mut app, + stargaze_wallet_01.clone(), + airdrop_contract, + ) + .unwrap(); + + let balances = app.wrap().query_all_balances(stargaze_wallet_01).unwrap(); + let expected_balance = [Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(2 * WHITELIST_AMOUNT), + }]; + assert_eq!(balances, expected_balance) +} + +#[test] +fn test_claim_converts_to_lowercase() { + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let mut mixed_case = vec![]; + let mut to_upper = true; + for char in eth_addr_str.chars() { + if !to_upper { + mixed_case.extend([char.to_string().to_lowercase()]); + } else { + mixed_case.extend([char.to_string().to_uppercase()]); + } + to_upper = !to_upper; + } + let mixed_case_eth_addr: String = mixed_case.into_iter().collect(); + + let params = InstantiateParams { + members: vec![Member { + address: mixed_case_eth_addr.clone(), + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address: minter_address.clone(), + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + query_minter_as_expected( + &mut app, + airdrop_contract.clone(), + Addr::unchecked(&minter_address), + ); + let balances = app + .wrap() + .query_all_balances(stargaze_wallet_01.clone()) + .unwrap(); + assert_eq!(balances, []); + + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: mixed_case_eth_addr, + eth_sig: eth_sig_str, + }; + let _ = execute_contract_with_msg( + claim_message, + &mut app, + stargaze_wallet_01.clone(), + airdrop_contract, + ) + .unwrap(); + + let balances = app.wrap().query_all_balances(stargaze_wallet_01).unwrap(); + let expected_balance = [Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(WHITELIST_AMOUNT), + }]; + assert_eq!(balances, expected_balance) +} diff --git a/test-suite/src/dydx_airdrop/tests/test_collection_whitelist.rs b/test-suite/src/dydx_airdrop/tests/test_collection_whitelist.rs new file mode 100644 index 000000000..fe0ca73ff --- /dev/null +++ b/test-suite/src/dydx_airdrop/tests/test_collection_whitelist.rs @@ -0,0 +1,114 @@ +use crate::common_setup::setup_accounts_and_block::setup_block_time; +use crate::common_setup::setup_collection_whitelist::configure_collection_whitelist; +use crate::dydx_airdrop::constants::claim_constants::{ + CONFIG_PLAINTEXT, MOCK_MINTER_ADDR_STR, MOCK_NAME_COLLECTION_ADDR, + MOCK_NAME_DISCOUNT_WL_ADDR_STR, STARGAZE_WALLET_01, +}; +use crate::dydx_airdrop::constants::collection_constants::{ + AIRDROP_ADDR_STR, MINT_PRICE, WHITELIST_AMOUNT, +}; +use crate::dydx_airdrop::setup::collection_whitelist_helpers::{ + execute_airdrop_claim, execute_mint_fail_not_on_whitelist, execute_mint_success, + send_funds_to_address, update_admin_for_whitelist, +}; +use crate::dydx_airdrop::setup::execute_msg::instantiate_contract; +use crate::dydx_airdrop::setup::setup_signatures::{get_msg_plaintext, get_wallet_and_sig}; +use crate::dydx_airdrop::setup::test_msgs::InstantiateParams; +use cosmwasm_std::Addr; +use dydx_airdrop::msg::QueryMsg; +use sg_std::GENESIS_MINT_START_TIME; +extern crate whitelist_immutable; +use crate::common_setup::templates::vending_minter_template; +use dydx_airdrop::contract::INSTANTIATION_FEE; +use whitelist_immutable_flex::msg::Member; + +#[test] +fn test_set_minter_contract_success() { + let vt = vending_minter_template(1); + let (mut app, creator) = (vt.router, vt.accts.creator); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, _, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + + let contract_admin = Addr::unchecked(creator); + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str, + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address: minter_address.clone(), + admin_account: contract_admin.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + let airdrop_contract = Addr::unchecked("contract3"); + let query_msg = QueryMsg::GetMinter {}; + let result: Addr = app + .wrap() + .query_wasm_smart(airdrop_contract, &query_msg) + .unwrap(); + assert_eq!(result, minter_address); +} + +#[test] +fn test_claim_added_to_minter_whitelist() { + let vt = vending_minter_template(1); + let (mut app, creator, buyer) = (vt.router, vt.accts.creator, vt.accts.buyer); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let whitelist_addr = configure_collection_whitelist( + &mut app, + creator.clone(), + buyer, + Addr::unchecked(minter_address.clone()), + ); + setup_block_time(&mut app, GENESIS_MINT_START_TIME, None); + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + + let airdrop_contract = Addr::unchecked(AIRDROP_ADDR_STR); + let params = InstantiateParams { + members: vec![Member { + address: eth_addr_str.clone(), + mint_count: 1, + }], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 5, + minter_address: minter_address.clone(), + admin_account: creator.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + update_admin_for_whitelist(&mut app, creator, airdrop_contract.clone(), whitelist_addr); + send_funds_to_address(&mut app, STARGAZE_WALLET_01, MINT_PRICE); + execute_mint_fail_not_on_whitelist(&mut app, Addr::unchecked(minter_address.clone())); + execute_airdrop_claim( + &mut app, + eth_addr_str.clone(), + eth_sig_str, + stargaze_wallet_01.clone(), + airdrop_contract, + ); + execute_mint_success( + &mut app, + stargaze_wallet_01, + Addr::unchecked(minter_address.clone()), + ); +} diff --git a/test-suite/src/dydx_airdrop/tests/test_immutable_whitelist.rs b/test-suite/src/dydx_airdrop/tests/test_immutable_whitelist.rs new file mode 100644 index 000000000..aabbf1404 --- /dev/null +++ b/test-suite/src/dydx_airdrop/tests/test_immutable_whitelist.rs @@ -0,0 +1,279 @@ +use crate::common_setup::contract_boxes::custom_mock_app; +use crate::dydx_airdrop::constants::claim_constants::{ + CONFIG_PLAINTEXT, MOCK_AIRDROP_ADDR_STR, MOCK_MINTER_ADDR_STR, MOCK_NAME_COLLECTION_ADDR, + MOCK_NAME_DISCOUNT_WL_ADDR_STR, OWNER, +}; +use crate::dydx_airdrop::constants::collection_constants::WHITELIST_AMOUNT; +use crate::dydx_airdrop::setup::configure_mock_minter::configure_mock_minter_with_mock_whitelist; +use crate::dydx_airdrop::setup::execute_msg::instantiate_contract; +use crate::dydx_airdrop::setup::test_msgs::InstantiateParams; + +use cosmwasm_std::Addr; +use dydx_airdrop::contract::INSTANTIATION_FEE; +use dydx_airdrop::msg::QueryMsg; +use whitelist_immutable_flex::helpers::WhitelistImmutableFlexContract; +use whitelist_immutable_flex::msg::Member; +use whitelist_immutable_flex::state::Config; + +#[test] +fn test_instantiate_with_addresses() { + let addresses: Vec = vec![ + "addr1".to_string(), + "addr2".to_string(), + "addr3".to_string(), + ]; + + let mut app = { custom_mock_app }(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let params = InstantiateParams { + members: vec![ + Member { + address: addresses[0].clone(), + mint_count: 1, + }, + Member { + address: addresses[1].clone(), + mint_count: 1, + }, + Member { + address: addresses[2].clone(), + mint_count: 1, + }, + ], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address, + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + + let query_msg = QueryMsg::AirdropEligible { + eth_address: "addr1".to_string(), + }; + let result: bool = app + .wrap() + .query_wasm_smart(airdrop_contract.clone(), &query_msg) + .unwrap(); + assert!(result); + + let query_msg = QueryMsg::AirdropEligible { + eth_address: "lies".to_string(), + }; + let result: bool = app + .wrap() + .query_wasm_smart(airdrop_contract, &query_msg) + .unwrap(); + assert!(!result); +} + +#[test] +fn test_whitelist_immutable_address_limit() { + let addresses: Vec = vec![ + "addr1".to_string(), + "addr2".to_string(), + "addr3".to_string(), + ]; + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + + let params = InstantiateParams { + members: vec![ + Member { + address: addresses[0].clone(), + mint_count: 20, + }, + Member { + address: addresses[1].clone(), + mint_count: 1, + }, + Member { + address: addresses[2].clone(), + mint_count: 1, + }, + ], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address, + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + let whitelist_immutable_flex = Addr::unchecked("contract4"); + let res: u32 = WhitelistImmutableFlexContract(whitelist_immutable_flex) + .mint_count(&app.wrap(), "addr1".to_string()) + .unwrap(); + assert_eq!(res, 20); +} + +#[test] +fn test_whitelist_immutable_address_count() { + let addresses: Vec = vec![ + "addr1".to_string(), + "addr2".to_string(), + "addr3".to_string(), + ]; + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + + let params = InstantiateParams { + members: vec![ + Member { + address: addresses[0].clone(), + mint_count: 1, + }, + Member { + address: addresses[1].clone(), + mint_count: 1, + }, + Member { + address: addresses[2].clone(), + mint_count: 1, + }, + ], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address, + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + let whitelist_immutable = Addr::unchecked("contract4"); + let res: u64 = WhitelistImmutableFlexContract(whitelist_immutable) + .address_count(&app.wrap()) + .unwrap(); + assert_eq!(res, 3); +} + +#[test] +fn test_whitelist_immutable_address_includes() { + let addresses: Vec = vec![ + "addr1".to_string(), + "addr2".to_string(), + "addr3".to_string(), + ]; + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + + let params = InstantiateParams { + members: vec![ + Member { + address: addresses[0].clone(), + mint_count: 1, + }, + Member { + address: addresses[1].clone(), + mint_count: 1, + }, + Member { + address: addresses[2].clone(), + mint_count: 1, + }, + ], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address, + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + let whitelist_immutable = Addr::unchecked("contract4"); + let res: bool = WhitelistImmutableFlexContract(whitelist_immutable.clone()) + .includes(&app.wrap(), "addr3".to_string()) + .unwrap(); + assert!(res); + + let res: bool = WhitelistImmutableFlexContract(whitelist_immutable) + .includes(&app.wrap(), "nonsense".to_string()) + .unwrap(); + assert!(!res); +} + +#[test] +fn test_whitelist_immutable_address_config() { + let addresses: Vec = vec![ + "addr1".to_string(), + "addr2".to_string(), + "addr3".to_string(), + ]; + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_address = MOCK_MINTER_ADDR_STR.to_string(); + let name_discount_wl_address = MOCK_NAME_DISCOUNT_WL_ADDR_STR.to_string(); + let name_collection_address = MOCK_NAME_COLLECTION_ADDR.to_string(); + + let params = InstantiateParams { + members: vec![ + Member { + address: addresses[0].clone(), + mint_count: 1, + }, + Member { + address: addresses[1].clone(), + mint_count: 1, + }, + Member { + address: addresses[2].clone(), + mint_count: 1, + }, + ], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address, + admin_account: OWNER.to_string(), + app: &mut app, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + name_discount_wl_address, + name_collection_address, + airdrop_count_limit: 500, + airdrop_amount: WHITELIST_AMOUNT, + }; + instantiate_contract(params).unwrap(); + let whitelist_immutable = Addr::unchecked("contract4"); + let res: Config = WhitelistImmutableFlexContract(whitelist_immutable) + .config(&app.wrap()) + .unwrap(); + let expected_config = whitelist_immutable_flex::state::Config { + admin: Addr::unchecked("contract3"), + mint_discount_bps: Some(0), + }; + assert_eq!(res, expected_config); +} diff --git a/test-suite/src/lib.rs b/test-suite/src/lib.rs index 8747e00b4..a50805fab 100644 --- a/test-suite/src/lib.rs +++ b/test-suite/src/lib.rs @@ -1,17 +1,18 @@ -pub mod common_setup; - #[cfg(test)] mod base_factory; #[cfg(test)] mod base_minter; +pub mod common_setup; +#[cfg(test)] +mod dydx_airdrop; #[cfg(test)] mod open_edition_factory; #[cfg(test)] mod open_edition_minter; #[cfg(test)] -mod sg721_base; +mod rekt_airdrop; #[cfg(test)] -mod sg_eth_airdrop; +mod sg721_base; #[cfg(test)] mod splits; #[cfg(test)] @@ -23,4 +24,6 @@ mod whitelist; #[cfg(test)] mod whitelist_immutable; #[cfg(test)] +mod whitelist_immutable_flex; +#[cfg(test)] mod whitelist_merkletree; diff --git a/test-suite/src/rekt_airdrop.rs b/test-suite/src/rekt_airdrop.rs new file mode 100644 index 000000000..50d470eda --- /dev/null +++ b/test-suite/src/rekt_airdrop.rs @@ -0,0 +1,3 @@ +mod constants; +mod setup; +mod tests; diff --git a/test-suite/src/rekt_airdrop/constants.rs b/test-suite/src/rekt_airdrop/constants.rs new file mode 100644 index 000000000..9d2841356 --- /dev/null +++ b/test-suite/src/rekt_airdrop/constants.rs @@ -0,0 +1,2 @@ +pub mod claim_constants; +pub mod collection_constants; diff --git a/test-suite/src/sg_eth_airdrop/constants/claim_constants.rs b/test-suite/src/rekt_airdrop/constants/claim_constants.rs similarity index 100% rename from test-suite/src/sg_eth_airdrop/constants/claim_constants.rs rename to test-suite/src/rekt_airdrop/constants/claim_constants.rs diff --git a/test-suite/src/rekt_airdrop/constants/collection_constants.rs b/test-suite/src/rekt_airdrop/constants/collection_constants.rs new file mode 100644 index 000000000..1c29cc2d4 --- /dev/null +++ b/test-suite/src/rekt_airdrop/constants/collection_constants.rs @@ -0,0 +1,5 @@ +pub const CREATION_FEE: u128 = 5_000_000_000; +pub const MINT_PRICE: u128 = 100_000_000; +pub const WHITELIST_AMOUNT: u128 = 66_000_000; + +pub const AIRDROP_ADDR_STR: &str = "contract4"; diff --git a/test-suite/src/rekt_airdrop/setup.rs b/test-suite/src/rekt_airdrop/setup.rs new file mode 100644 index 000000000..fc6734421 --- /dev/null +++ b/test-suite/src/rekt_airdrop/setup.rs @@ -0,0 +1,7 @@ +pub mod collection_whitelist_helpers; +pub mod configure_mock_minter; +pub mod execute_msg; +pub mod mock_minter_contract; +pub mod mock_whitelist_contract; +pub mod setup_signatures; +pub mod test_msgs; diff --git a/test-suite/src/sg_eth_airdrop/setup/collection_whitelist_helpers.rs b/test-suite/src/rekt_airdrop/setup/collection_whitelist_helpers.rs similarity index 86% rename from test-suite/src/sg_eth_airdrop/setup/collection_whitelist_helpers.rs rename to test-suite/src/rekt_airdrop/setup/collection_whitelist_helpers.rs index 7f6674cf8..6431396b2 100644 --- a/test-suite/src/sg_eth_airdrop/setup/collection_whitelist_helpers.rs +++ b/test-suite/src/rekt_airdrop/setup/collection_whitelist_helpers.rs @@ -1,7 +1,7 @@ -use crate::sg_eth_airdrop::constants::claim_constants::{NATIVE_DENOM, STARGAZE_WALLET_01}; -use crate::sg_eth_airdrop::constants::collection_constants::{MINT_PRICE, WHITELIST_AMOUNT}; +use crate::rekt_airdrop::constants::claim_constants::{NATIVE_DENOM, STARGAZE_WALLET_01}; +use crate::rekt_airdrop::constants::collection_constants::{MINT_PRICE, WHITELIST_AMOUNT}; -use crate::sg_eth_airdrop::setup::execute_msg::execute_contract_with_msg; +use crate::rekt_airdrop::setup::execute_msg::execute_contract_with_msg; use cosmwasm_std::{coins, Addr}; use cw_multi_test::{BankSudo, Executor, SudoMsg}; use sg_multi_test::StargazeApp; @@ -54,7 +54,7 @@ pub fn execute_airdrop_claim( target_wallet: Addr, airdrop_contract: Addr, ) { - let claim_message = sg_eth_airdrop::msg::ExecuteMsg::ClaimAirdrop { + let claim_message = rekt_airdrop::msg::ExecuteMsg::ClaimAirdrop { eth_address: eth_addr_str, eth_sig: eth_sig_str, }; diff --git a/test-suite/src/rekt_airdrop/setup/configure_mock_minter.rs b/test-suite/src/rekt_airdrop/setup/configure_mock_minter.rs new file mode 100644 index 000000000..ee456e191 --- /dev/null +++ b/test-suite/src/rekt_airdrop/setup/configure_mock_minter.rs @@ -0,0 +1,53 @@ +use crate::common_setup::setup_accounts_and_block::setup_accounts; +use crate::common_setup::setup_collection_whitelist::setup_whitelist_contract; + +use crate::common_setup::contract_boxes::contract_vending_factory; +use crate::common_setup::setup_minter::vending_minter::mock_params::{ + mock_create_minter, mock_params, +}; +use crate::rekt_airdrop::constants::collection_constants::CREATION_FEE; +use crate::rekt_airdrop::setup::mock_minter_contract::mock_minter; +use cosmwasm_std::{coins, Addr, Timestamp}; +use cw_multi_test::Executor; +use sg2::msg::Sg2ExecuteMsg; +use sg2::tests::mock_collection_params_1; +use sg_multi_test::StargazeApp; +use sg_std::{GENESIS_MINT_START_TIME, NATIVE_DENOM}; + +use crate::rekt_airdrop::setup::mock_whitelist_contract::mock_whitelist; + +fn configure_mock_minter(app: &mut StargazeApp, creator: Addr) { + let minter_code_id = app.store_code(mock_minter()); + + println!("minter_code_id: {minter_code_id}"); + let creation_fee = coins(CREATION_FEE, NATIVE_DENOM); + + let factory_code_id = app.store_code(contract_vending_factory()); + println!("factory_code_id: {factory_code_id}"); + + let mut params = mock_params(None); + params.code_id = minter_code_id; + + let factory_addr = app + .instantiate_contract( + factory_code_id, + creator.clone(), + &vending_factory::msg::InstantiateMsg { params }, + &[], + "factory", + None, + ) + .unwrap(); + let start_time = Timestamp::from_nanos(GENESIS_MINT_START_TIME); + let collection_params = mock_collection_params_1(Some(start_time)); + let msg = mock_create_minter(None, collection_params, None); + let msg = Sg2ExecuteMsg::CreateMinter(msg); + let res = app.execute_contract(creator, factory_addr, &msg, &creation_fee); + assert!(res.is_ok()); +} +pub fn configure_mock_minter_with_mock_whitelist(app: &mut StargazeApp) { + let (creator, _) = setup_accounts(app); + configure_mock_minter(app, creator.clone()); + let whitelist_code_id = app.store_code(mock_whitelist()); + setup_whitelist_contract(app, &creator, Some(whitelist_code_id), None); +} diff --git a/test-suite/src/sg_eth_airdrop/setup/execute_msg.rs b/test-suite/src/rekt_airdrop/setup/execute_msg.rs similarity index 90% rename from test-suite/src/sg_eth_airdrop/setup/execute_msg.rs rename to test-suite/src/rekt_airdrop/setup/execute_msg.rs index 5a1a105eb..c0b4cd087 100644 --- a/test-suite/src/sg_eth_airdrop/setup/execute_msg.rs +++ b/test-suite/src/rekt_airdrop/setup/execute_msg.rs @@ -1,15 +1,15 @@ -use crate::sg_eth_airdrop::constants::claim_constants::OWNER; -use crate::sg_eth_airdrop::constants::collection_constants::WHITELIST_AMOUNT; +use crate::rekt_airdrop::constants::claim_constants::OWNER; +use crate::rekt_airdrop::constants::collection_constants::WHITELIST_AMOUNT; use crate::{ common_setup::contract_boxes::{contract_eth_airdrop, contract_whitelist_immutable}, - sg_eth_airdrop::setup::test_msgs::InstantiateParams, + rekt_airdrop::setup::test_msgs::InstantiateParams, }; use anyhow::Error as anyhow_error; use cosmwasm_std::{coins, Addr}; use cw_multi_test::error::Error; use cw_multi_test::{AppResponse, BankSudo, Executor, SudoMsg}; use eyre::Result; -use sg_eth_airdrop::msg::{ExecuteMsg, InstantiateMsg}; +use rekt_airdrop::msg::{ExecuteMsg, InstantiateMsg}; use sg_multi_test::StargazeApp; use sg_std::NATIVE_DENOM; diff --git a/test-suite/src/rekt_airdrop/setup/mock_minter_contract.rs b/test-suite/src/rekt_airdrop/setup/mock_minter_contract.rs new file mode 100644 index 000000000..8e6f9f857 --- /dev/null +++ b/test-suite/src/rekt_airdrop/setup/mock_minter_contract.rs @@ -0,0 +1,72 @@ +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, Binary, Coin, Deps, DepsMut, Env, MessageInfo, StdResult, Timestamp, +}; +use cw_multi_test::{Contract, ContractWrapper}; +use rekt_airdrop::error::ContractError; +use sg_std::{Response, StargazeMsgWrapper}; +use vending_factory::msg::VendingMinterCreateMsg; +use vending_minter::msg::{ExecuteMsg, QueryMsg}; + +use cosmwasm_schema::cw_serde; +#[cw_serde] +pub struct ConfigResponse { + pub admin: String, + pub base_token_uri: String, + pub num_tokens: u32, + pub per_address_limit: u32, + pub sg721_address: String, + pub sg721_code_id: u64, + pub start_time: Timestamp, + pub mint_price: Coin, + pub whitelist: Option, + pub factory: String, +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: VendingMinterCreateMsg, +) -> Result { + let res = Response::new(); + Ok(res) +} + +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: ExecuteMsg, +) -> Result { + Err(ContractError::CollectionWhitelistMinterNotSet {}) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_json_binary(&query_config()), + _ => to_json_binary("invalid"), + } +} + +fn query_config() -> ConfigResponse { + ConfigResponse { + admin: "some_admin".to_string(), + whitelist: Some("contract2".to_string()), + base_token_uri: "some_uri".to_string(), + num_tokens: 5, + per_address_limit: 5, + sg721_address: "some_sg721_address".to_string(), + sg721_code_id: 4, + start_time: Timestamp::from_seconds(30), + mint_price: Coin::new(1000, "ustars"), + factory: "some_factory".to_string(), + } +} + +pub fn mock_minter() -> Box> { + let ract = ContractWrapper::new(execute, instantiate, query); + Box::new(ract) +} diff --git a/test-suite/src/sg_eth_airdrop/setup/mock_whitelist_contract.rs b/test-suite/src/rekt_airdrop/setup/mock_whitelist_contract.rs similarity index 96% rename from test-suite/src/sg_eth_airdrop/setup/mock_whitelist_contract.rs rename to test-suite/src/rekt_airdrop/setup/mock_whitelist_contract.rs index 45353a9b3..44205f373 100644 --- a/test-suite/src/sg_eth_airdrop/setup/mock_whitelist_contract.rs +++ b/test-suite/src/rekt_airdrop/setup/mock_whitelist_contract.rs @@ -1,7 +1,7 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, StdResult}; use cw_multi_test::{Contract, ContractWrapper}; -use sg_eth_airdrop::error::ContractError; +use rekt_airdrop::error::ContractError; use sg_std::{Response, StargazeMsgWrapper}; use sg_whitelist::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; diff --git a/test-suite/src/rekt_airdrop/setup/setup_signatures.rs b/test-suite/src/rekt_airdrop/setup/setup_signatures.rs new file mode 100644 index 000000000..2df7771bb --- /dev/null +++ b/test-suite/src/rekt_airdrop/setup/setup_signatures.rs @@ -0,0 +1,32 @@ +use crate::rekt_airdrop::constants::claim_constants::CONFIG_PLAINTEXT; +use async_std::task; +use ethers_core::{k256::ecdsa::SigningKey, rand::thread_rng, types::H160}; +use ethers_signers::{LocalWallet, Signer, Wallet, WalletError}; + +pub async fn get_signature( + wallet: Wallet, + plaintext_msg: &str, +) -> Result { + wallet.sign_message(plaintext_msg).await +} + +pub fn get_wallet_and_sig( + claim_plaintext: String, +) -> ( + Wallet, + std::string::String, + H160, + std::string::String, +) { + let wallet = LocalWallet::new(&mut thread_rng()); + let eth_sig_str = task::block_on(get_signature(wallet.clone(), &claim_plaintext)) + .unwrap() + .to_string(); + let eth_address = wallet.address(); + let eth_addr_str = format!("{eth_address:?}"); + (wallet, eth_sig_str, eth_address, eth_addr_str) +} + +pub fn get_msg_plaintext(wallet_address: String) -> String { + str::replace(CONFIG_PLAINTEXT, "{wallet}", &wallet_address) +} diff --git a/test-suite/src/sg_eth_airdrop/setup/test_msgs.rs b/test-suite/src/rekt_airdrop/setup/test_msgs.rs similarity index 100% rename from test-suite/src/sg_eth_airdrop/setup/test_msgs.rs rename to test-suite/src/rekt_airdrop/setup/test_msgs.rs diff --git a/test-suite/src/rekt_airdrop/tests.rs b/test-suite/src/rekt_airdrop/tests.rs new file mode 100644 index 000000000..da3fde61c --- /dev/null +++ b/test-suite/src/rekt_airdrop/tests.rs @@ -0,0 +1,3 @@ +mod test_claim; +mod test_collection_whitelist; +mod test_immutable_whitelist; diff --git a/test-suite/src/sg_eth_airdrop/tests/test_claim.rs b/test-suite/src/rekt_airdrop/tests/test_claim.rs similarity index 88% rename from test-suite/src/sg_eth_airdrop/tests/test_claim.rs rename to test-suite/src/rekt_airdrop/tests/test_claim.rs index 4c93c5432..8e5352823 100644 --- a/test-suite/src/sg_eth_airdrop/tests/test_claim.rs +++ b/test-suite/src/rekt_airdrop/tests/test_claim.rs @@ -1,28 +1,28 @@ use crate::common_setup::contract_boxes::custom_mock_app; -use crate::sg_eth_airdrop::constants::collection_constants::WHITELIST_AMOUNT; -use crate::sg_eth_airdrop::setup::configure_mock_minter::configure_mock_minter_with_mock_whitelist; -use crate::sg_eth_airdrop::setup::setup_signatures::{ +use crate::rekt_airdrop::constants::collection_constants::WHITELIST_AMOUNT; +use crate::rekt_airdrop::setup::configure_mock_minter::configure_mock_minter_with_mock_whitelist; +use crate::rekt_airdrop::setup::setup_signatures::{ get_msg_plaintext, get_signature, get_wallet_and_sig, }; -use crate::sg_eth_airdrop::setup::test_msgs::InstantiateParams; +use crate::rekt_airdrop::setup::test_msgs::InstantiateParams; use async_std::task; use cosmwasm_std::{Addr, Attribute, Coin, Uint128}; -use sg_eth_airdrop::msg::{ExecuteMsg, QueryMsg}; +use rekt_airdrop::msg::{ExecuteMsg, QueryMsg}; use ethers_core::rand::thread_rng; use ethers_signers::{LocalWallet, Signer}; use sg_multi_test::StargazeApp; -use crate::sg_eth_airdrop::constants::claim_constants::{ +use crate::rekt_airdrop::constants::claim_constants::{ CONFIG_PLAINTEXT, MOCK_AIRDROP_ADDR_STR, MOCK_MINTER_ADDR_STR, NATIVE_DENOM, OWNER, STARGAZE_WALLET_01, STARGAZE_WALLET_02, }; -use crate::sg_eth_airdrop::setup::execute_msg::{ +use crate::rekt_airdrop::setup::execute_msg::{ execute_contract_error_with_msg, execute_contract_with_msg, instantiate_contract, }; -use sg_eth_airdrop::contract::INSTANTIATION_FEE; +use rekt_airdrop::contract::INSTANTIATION_FEE; fn query_minter_as_expected(app: &mut StargazeApp, airdrop_contract: Addr, minter_addr: Addr) { let query_msg = QueryMsg::GetMinter {}; @@ -39,6 +39,7 @@ fn test_instantiate() { let minter_address = Addr::unchecked("contract1"); let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); let (_, _, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + let params = InstantiateParams { addresses: vec![eth_addr_str], funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, @@ -593,3 +594,65 @@ fn test_two_claims_allowed_success() { }]; assert_eq!(balances, expected_balance) } + +#[test] +fn test_claim_converts_to_lowercase() { + let claim_plaintext = &get_msg_plaintext(STARGAZE_WALLET_01.to_string()); + let (_, eth_sig_str, _, eth_addr_str) = get_wallet_and_sig(claim_plaintext.clone()); + + let stargaze_wallet_01 = Addr::unchecked(STARGAZE_WALLET_01); + + let mut app = custom_mock_app(); + configure_mock_minter_with_mock_whitelist(&mut app); + let minter_addr = Addr::unchecked(MOCK_MINTER_ADDR_STR); + let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); + + let mut mixed_case = vec![]; + let mut to_upper = true; + for char in eth_addr_str.chars() { + if !to_upper { + mixed_case.extend([char.to_string().to_lowercase()]); + } else { + mixed_case.extend([char.to_string().to_uppercase()]); + } + to_upper = !to_upper; + } + let mixed_case_eth_addr: String = mixed_case.into_iter().collect(); + + let params = InstantiateParams { + addresses: vec![mixed_case_eth_addr.clone()], + funds_amount: WHITELIST_AMOUNT + INSTANTIATION_FEE, + expected_airdrop_contract_id: 4, + minter_address: minter_addr.clone(), + admin_account: Addr::unchecked(OWNER), + app: &mut app, + per_address_limit: 1, + claim_msg_plaintext: CONFIG_PLAINTEXT.to_string(), + }; + instantiate_contract(params).unwrap(); + query_minter_as_expected(&mut app, airdrop_contract.clone(), minter_addr); + let balances = app + .wrap() + .query_all_balances(stargaze_wallet_01.clone()) + .unwrap(); + assert_eq!(balances, []); + + let claim_message = ExecuteMsg::ClaimAirdrop { + eth_address: mixed_case_eth_addr, + eth_sig: eth_sig_str, + }; + let _ = execute_contract_with_msg( + claim_message, + &mut app, + stargaze_wallet_01.clone(), + airdrop_contract, + ) + .unwrap(); + + let balances = app.wrap().query_all_balances(stargaze_wallet_01).unwrap(); + let expected_balance = [Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(WHITELIST_AMOUNT), + }]; + assert_eq!(balances, expected_balance) +} diff --git a/test-suite/src/sg_eth_airdrop/tests/test_collection_whitelist.rs b/test-suite/src/rekt_airdrop/tests/test_collection_whitelist.rs similarity index 85% rename from test-suite/src/sg_eth_airdrop/tests/test_collection_whitelist.rs rename to test-suite/src/rekt_airdrop/tests/test_collection_whitelist.rs index 808287f80..ef071cb08 100644 --- a/test-suite/src/sg_eth_airdrop/tests/test_collection_whitelist.rs +++ b/test-suite/src/rekt_airdrop/tests/test_collection_whitelist.rs @@ -1,22 +1,22 @@ use crate::common_setup::setup_accounts_and_block::setup_block_time; use crate::common_setup::setup_collection_whitelist::configure_collection_whitelist; -use crate::sg_eth_airdrop::constants::claim_constants::{CONFIG_PLAINTEXT, STARGAZE_WALLET_01}; -use crate::sg_eth_airdrop::constants::collection_constants::{ +use crate::rekt_airdrop::constants::claim_constants::{CONFIG_PLAINTEXT, STARGAZE_WALLET_01}; +use crate::rekt_airdrop::constants::collection_constants::{ AIRDROP_ADDR_STR, MINT_PRICE, WHITELIST_AMOUNT, }; -use crate::sg_eth_airdrop::setup::collection_whitelist_helpers::{ +use crate::rekt_airdrop::setup::collection_whitelist_helpers::{ execute_airdrop_claim, execute_mint_fail_not_on_whitelist, execute_mint_success, send_funds_to_address, update_admin_for_whitelist, }; -use crate::sg_eth_airdrop::setup::execute_msg::instantiate_contract; -use crate::sg_eth_airdrop::setup::setup_signatures::{get_msg_plaintext, get_wallet_and_sig}; -use crate::sg_eth_airdrop::setup::test_msgs::InstantiateParams; +use crate::rekt_airdrop::setup::execute_msg::instantiate_contract; +use crate::rekt_airdrop::setup::setup_signatures::{get_msg_plaintext, get_wallet_and_sig}; +use crate::rekt_airdrop::setup::test_msgs::InstantiateParams; use cosmwasm_std::Addr; -use sg_eth_airdrop::msg::QueryMsg; +use rekt_airdrop::msg::QueryMsg; use sg_std::GENESIS_MINT_START_TIME; extern crate whitelist_immutable; use crate::common_setup::templates::vending_minter_template; -use sg_eth_airdrop::contract::INSTANTIATION_FEE; +use rekt_airdrop::contract::INSTANTIATION_FEE; #[test] fn test_set_minter_contract_success() { diff --git a/test-suite/src/sg_eth_airdrop/tests/test_immutable_whitelist.rs b/test-suite/src/rekt_airdrop/tests/test_immutable_whitelist.rs similarity index 92% rename from test-suite/src/sg_eth_airdrop/tests/test_immutable_whitelist.rs rename to test-suite/src/rekt_airdrop/tests/test_immutable_whitelist.rs index 4d64e992e..4b2ba72ed 100644 --- a/test-suite/src/sg_eth_airdrop/tests/test_immutable_whitelist.rs +++ b/test-suite/src/rekt_airdrop/tests/test_immutable_whitelist.rs @@ -1,15 +1,15 @@ use crate::common_setup::contract_boxes::custom_mock_app; -use crate::sg_eth_airdrop::constants::claim_constants::{ +use crate::rekt_airdrop::constants::claim_constants::{ CONFIG_PLAINTEXT, MOCK_AIRDROP_ADDR_STR, MOCK_MINTER_ADDR_STR, OWNER, }; -use crate::sg_eth_airdrop::constants::collection_constants::WHITELIST_AMOUNT; -use crate::sg_eth_airdrop::setup::configure_mock_minter::configure_mock_minter_with_mock_whitelist; -use crate::sg_eth_airdrop::setup::execute_msg::instantiate_contract; -use crate::sg_eth_airdrop::setup::test_msgs::InstantiateParams; +use crate::rekt_airdrop::constants::collection_constants::WHITELIST_AMOUNT; +use crate::rekt_airdrop::setup::configure_mock_minter::configure_mock_minter_with_mock_whitelist; +use crate::rekt_airdrop::setup::execute_msg::instantiate_contract; +use crate::rekt_airdrop::setup::test_msgs::InstantiateParams; use cosmwasm_std::Addr; -use sg_eth_airdrop::contract::INSTANTIATION_FEE; -use sg_eth_airdrop::msg::QueryMsg; +use rekt_airdrop::contract::INSTANTIATION_FEE; +use rekt_airdrop::msg::QueryMsg; use whitelist_immutable::helpers::WhitelistImmutableContract; #[test] @@ -20,7 +20,7 @@ fn test_instantiate_with_addresses() { "addr3".to_string(), ]; - let mut app = custom_mock_app(); + let mut app = { custom_mock_app }(); configure_mock_minter_with_mock_whitelist(&mut app); let minter_addr = Addr::unchecked(MOCK_MINTER_ADDR_STR); let airdrop_contract = Addr::unchecked(MOCK_AIRDROP_ADDR_STR); diff --git a/test-suite/src/whitelist_immutable_flex.rs b/test-suite/src/whitelist_immutable_flex.rs new file mode 100644 index 000000000..14f00389d --- /dev/null +++ b/test-suite/src/whitelist_immutable_flex.rs @@ -0,0 +1 @@ +mod tests; diff --git a/test-suite/src/whitelist_immutable_flex/tests.rs b/test-suite/src/whitelist_immutable_flex/tests.rs new file mode 100644 index 000000000..07214813c --- /dev/null +++ b/test-suite/src/whitelist_immutable_flex/tests.rs @@ -0,0 +1 @@ +mod integration_tests; diff --git a/test-suite/src/whitelist_immutable_flex/tests/integration_tests.rs b/test-suite/src/whitelist_immutable_flex/tests/integration_tests.rs new file mode 100644 index 000000000..88f7f189e --- /dev/null +++ b/test-suite/src/whitelist_immutable_flex/tests/integration_tests.rs @@ -0,0 +1,226 @@ +#[cfg(test)] +mod tests { + use cosmwasm_std::Addr; + use cw_multi_test::Executor; + use sg_multi_test::StargazeApp; + use whitelist_immutable_flex::msg::*; + use whitelist_immutable_flex::{helpers::WhitelistImmutableFlexContract, state::Config}; + + use crate::common_setup::contract_boxes::{contract_whitelist_immutable_flex, custom_mock_app}; + + const CREATOR: &str = "creator"; + + fn get_init_address_list_1() -> Vec { + vec![ + Member { + address: "addr0001".to_string(), + mint_count: 1, + }, + Member { + address: "addr0002".to_string(), + mint_count: 2, + }, + Member { + address: "addr0003".to_string(), + mint_count: 3, + }, + Member { + address: "addr0004".to_string(), + mint_count: 4, + }, + Member { + address: "addr0005".to_string(), + mint_count: 5, + }, + ] + } + + fn get_init_address_list_2() -> Vec { + vec![ + Member { + address: "tester".to_string(), + mint_count: 1, + }, + Member { + address: "user".to_string(), + mint_count: 2, + }, + Member { + address: "random".to_string(), + mint_count: 3, + }, + Member { + address: "human".to_string(), + mint_count: 4, + }, + Member { + address: "bot".to_string(), + mint_count: 5, + }, + ] + } + + fn get_init_address_single_list() -> Vec { + vec![Member { + address: "onlyone".to_string(), + mint_count: 1, + }] + } + + pub fn instantiate_with_addresses(app: &mut StargazeApp, members: Vec) -> Addr { + let msg = InstantiateMsg { + members, + mint_discount_bps: None, + }; + let wl_id = app.store_code(contract_whitelist_immutable_flex()); + app.instantiate_contract( + wl_id, + Addr::unchecked(CREATOR), + &msg, + &[], + "wl-contract".to_string(), + None, + ) + .unwrap() + } + + pub fn query_address_count(app: &mut StargazeApp, addrs: Vec, wl_addr: Addr) { + let count: u64 = app + .wrap() + .query_wasm_smart(wl_addr, &QueryMsg::AddressCount {}) + .unwrap(); + assert_eq!(count, addrs.len() as u64); + } + + pub fn query_admin(app: &mut StargazeApp, wl_addr: Addr) { + let admin: String = app + .wrap() + .query_wasm_smart(wl_addr, &QueryMsg::Admin {}) + .unwrap(); + assert_eq!(admin, CREATOR.to_string()); + } + + pub fn query_includes_address(app: &mut StargazeApp, wl_addr: Addr, addr_to_check: String) { + let includes: bool = app + .wrap() + .query_wasm_smart( + wl_addr, + &QueryMsg::HasMember { + address: addr_to_check, + }, + ) + .unwrap(); + assert!(includes); + } + + pub fn query_member( + app: &mut StargazeApp, + wl_addr: Addr, + member_address: String, + expected_mint_count: u32, + ) { + let member: Member = app + .wrap() + .query_wasm_smart( + wl_addr, + &QueryMsg::Member { + address: member_address.clone(), + }, + ) + .unwrap(); + assert_eq!(member.address, member_address); + assert_eq!(member.mint_count, expected_mint_count); + } + #[test] + pub fn test_instantiate() { + let mut app = custom_mock_app(); + let addrs = get_init_address_list_2(); + let wl_addr = instantiate_with_addresses(&mut app, addrs.clone()); + let addr_to_check = addrs[1].clone().address; + query_admin(&mut app, wl_addr.clone()); + query_address_count(&mut app, addrs, wl_addr.clone()); + query_includes_address(&mut app, wl_addr.clone(), addr_to_check.clone()); + query_member(&mut app, wl_addr.clone(), addr_to_check, 2); + } + + #[test] + pub fn test_instantiate_single_list() { + let mut app = custom_mock_app(); + let addrs = get_init_address_single_list(); + let wl_addr = instantiate_with_addresses(&mut app, addrs.clone()); + let addr_to_check = addrs[0].clone().address; + query_admin(&mut app, wl_addr.clone()); + query_address_count(&mut app, addrs, wl_addr.clone()); + query_includes_address(&mut app, wl_addr.clone(), addr_to_check.clone()); + query_member(&mut app, wl_addr.clone(), addr_to_check, 1); + } + + #[test] + pub fn test_instantiate_empty_list_error() { + let mut app = custom_mock_app(); + let members = vec![]; + + let msg = InstantiateMsg { + members, + mint_discount_bps: None, + }; + let wl_id = app.store_code(contract_whitelist_immutable_flex()); + let res = app + .instantiate_contract( + wl_id, + Addr::unchecked(CREATOR), + &msg, + &[], + "wl-contract".to_string(), + None, + ) + .unwrap_err(); + let expected_error = "Empty whitelist, must provide at least one address"; + assert_eq!(res.root_cause().to_string(), expected_error) + } + + #[test] + pub fn test_helper_query_address_count() { + let mut app = custom_mock_app(); + let addrs = get_init_address_single_list(); + let wl_addr = instantiate_with_addresses(&mut app, addrs); + + let res: u64 = WhitelistImmutableFlexContract(wl_addr) + .address_count(&app.wrap()) + .unwrap(); + assert_eq!(res, 1); + } + + #[test] + pub fn test_helper_query_config() { + let mut app = custom_mock_app(); + let addrs = get_init_address_single_list(); + let wl_addr = instantiate_with_addresses(&mut app, addrs); + + let res: Config = WhitelistImmutableFlexContract(wl_addr) + .config(&app.wrap()) + .unwrap(); + let expected_config = Config { + admin: Addr::unchecked("creator"), + mint_discount_bps: None, + }; + assert_eq!(res, expected_config); + } + + #[test] + pub fn test_helper_query_includes() { + let mut app = custom_mock_app(); + let addrs = get_init_address_list_1(); + let wl_addr = instantiate_with_addresses(&mut app, addrs); + + let res: bool = WhitelistImmutableFlexContract(wl_addr.clone()) + .includes(&app.wrap(), "addr0003".to_string()) + .unwrap(); + assert!(res); + + let res: bool = WhitelistImmutableFlexContract(wl_addr) + .includes(&app.wrap(), "nonsense".to_string()) + .unwrap(); + assert!(!res); + } +} diff --git a/test-suite/src/whitelist_merkletree/tests/integration_tests.rs b/test-suite/src/whitelist_merkletree/tests/integration_tests.rs index 6f846f256..0d503c99f 100644 --- a/test-suite/src/whitelist_merkletree/tests/integration_tests.rs +++ b/test-suite/src/whitelist_merkletree/tests/integration_tests.rs @@ -90,7 +90,7 @@ mod tests { .query_wasm_smart( wl_addr, &QueryMsg::HasMember { - member: addr_to_check.to_string(), + member: addr_to_check, proof_hashes, }, ) diff --git a/ts/src/DydxAirdrop.client.ts b/ts/src/DydxAirdrop.client.ts new file mode 100644 index 000000000..d16bd7867 --- /dev/null +++ b/ts/src/DydxAirdrop.client.ts @@ -0,0 +1,148 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult } from "@cosmjs/cosmwasm-stargate"; +import { Coin, StdFee } from "@cosmjs/amino"; +import { InstantiateMsg, Member, ExecuteMsg, QueryMsg, Boolean, Uint32, Addr } from "./DydxAirdrop.types"; +export interface DydxAirdropReadOnlyInterface { + contractAddress: string; + airdropEligible: ({ + ethAddress + }: { + ethAddress: string; + }) => Promise; + getMinter: () => Promise; + isRegistered: ({ + ethAddress + }: { + ethAddress: string; + }) => Promise; + hasClaimed: ({ + ethAddress + }: { + ethAddress: string; + }) => Promise; + getAirdropCount: () => Promise; +} +export class DydxAirdropQueryClient implements DydxAirdropReadOnlyInterface { + client: CosmWasmClient; + contractAddress: string; + + constructor(client: CosmWasmClient, contractAddress: string) { + this.client = client; + this.contractAddress = contractAddress; + this.airdropEligible = this.airdropEligible.bind(this); + this.getMinter = this.getMinter.bind(this); + this.isRegistered = this.isRegistered.bind(this); + this.hasClaimed = this.hasClaimed.bind(this); + this.getAirdropCount = this.getAirdropCount.bind(this); + } + + airdropEligible = async ({ + ethAddress + }: { + ethAddress: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + airdrop_eligible: { + eth_address: ethAddress + } + }); + }; + getMinter = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_minter: {} + }); + }; + isRegistered = async ({ + ethAddress + }: { + ethAddress: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + is_registered: { + eth_address: ethAddress + } + }); + }; + hasClaimed = async ({ + ethAddress + }: { + ethAddress: string; + }): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + has_claimed: { + eth_address: ethAddress + } + }); + }; + getAirdropCount = async (): Promise => { + return this.client.queryContractSmart(this.contractAddress, { + get_airdrop_count: {} + }); + }; +} +export interface DydxAirdropInterface extends DydxAirdropReadOnlyInterface { + contractAddress: string; + sender: string; + register: ({ + ethAddress, + ethSig + }: { + ethAddress: string; + ethSig: string; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; + claimAirdrop: ({ + ethAddress, + ethSig + }: { + ethAddress: string; + ethSig: string; + }, fee?: number | StdFee | "auto", memo?: string, _funds?: Coin[]) => Promise; +} +export class DydxAirdropClient extends DydxAirdropQueryClient implements DydxAirdropInterface { + client: SigningCosmWasmClient; + sender: string; + contractAddress: string; + + constructor(client: SigningCosmWasmClient, sender: string, contractAddress: string) { + super(client, contractAddress); + this.client = client; + this.sender = sender; + this.contractAddress = contractAddress; + this.register = this.register.bind(this); + this.claimAirdrop = this.claimAirdrop.bind(this); + } + + register = async ({ + ethAddress, + ethSig + }: { + ethAddress: string; + ethSig: string; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + register: { + eth_address: ethAddress, + eth_sig: ethSig + } + }, fee, memo, _funds); + }; + claimAirdrop = async ({ + ethAddress, + ethSig + }: { + ethAddress: string; + ethSig: string; + }, fee: number | StdFee | "auto" = "auto", memo?: string, _funds?: Coin[]): Promise => { + return await this.client.execute(this.sender, this.contractAddress, { + claim_airdrop: { + eth_address: ethAddress, + eth_sig: ethSig + } + }, fee, memo, _funds); + }; +} \ No newline at end of file diff --git a/ts/src/DydxAirdrop.types.ts b/ts/src/DydxAirdrop.types.ts new file mode 100644 index 000000000..4b12a8057 --- /dev/null +++ b/ts/src/DydxAirdrop.types.ts @@ -0,0 +1,52 @@ +/** +* This file was automatically generated by @cosmwasm/ts-codegen@0.35.3. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ + +export interface InstantiateMsg { + admin: string; + airdrop_amount: number; + airdrop_count_limit: number; + claim_msg_plaintext: string; + members: Member[]; + minter_address: string; + name_collection_address: string; + name_discount_wl_address: string; + whitelist_code_id: number; +} +export interface Member { + address: string; + mint_count: number; +} +export type ExecuteMsg = { + register: { + eth_address: string; + eth_sig: string; + }; +} | { + claim_airdrop: { + eth_address: string; + eth_sig: string; + }; +}; +export type QueryMsg = { + airdrop_eligible: { + eth_address: string; + }; +} | { + get_minter: {}; +} | { + is_registered: { + eth_address: string; + }; +} | { + has_claimed: { + eth_address: string; + }; +} | { + get_airdrop_count: {}; +}; +export type Boolean = boolean; +export type Uint32 = number; +export type Addr = string; \ No newline at end of file