diff --git a/Cargo.lock b/Cargo.lock index 9304021ff..79a71d9af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,12 +271,12 @@ dependencies = [ "cw-admin-factory", "cw-utils 1.0.3", "cw20 1.1.2", - "cw20-stake 2.5.1", - "dao-dao-core 2.5.1", - "dao-interface 2.5.1", - "dao-pre-propose-single 2.5.1", - "dao-proposal-single 2.5.1", - "dao-voting 2.5.1", + "cw20-stake 2.6.0", + "dao-dao-core 2.6.0", + "dao-interface 2.6.0", + "dao-pre-propose-single 2.6.0", + "dao-proposal-single 2.6.0", + "dao-voting 2.6.0", "dao-voting-cw20-staked", "env_logger", "serde", @@ -295,7 +295,7 @@ dependencies = [ [[package]] name = "btsg-ft-factory" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -304,11 +304,11 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "dao-dao-core 2.5.1", - "dao-interface 2.5.1", - "dao-proposal-single 2.5.1", + "dao-dao-core 2.6.0", + "dao-interface 2.6.0", + "dao-proposal-single 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-token-staked", "osmosis-std-derive", "prost 0.12.3", @@ -686,7 +686,7 @@ dependencies = [ [[package]] name = "cw-admin-factory" -version = "2.5.1" +version = "2.6.0" dependencies = [ "bech32", "cosmwasm-schema", @@ -698,11 +698,11 @@ dependencies = [ "cw2 1.1.2", "cw20-base 1.1.2", "cw4 1.1.2", - "dao-interface 2.5.1", - "dao-proposal-single 2.5.1", + "dao-interface 2.6.0", + "dao-proposal-single 2.6.0", "dao-testing", - "dao-voting 2.5.1", - "dao-voting-cw4 2.5.1", + "dao-voting 2.6.0", + "dao-voting-cw4 2.6.0", "osmosis-test-tube", "thiserror", ] @@ -831,7 +831,7 @@ dependencies = [ [[package]] name = "cw-denom" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -844,21 +844,21 @@ dependencies = [ [[package]] name = "cw-fund-distributor" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-fund-distributor", "cw-multi-test", - "cw-paginate-storage 2.5.1", + "cw-paginate-storage 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", - "dao-dao-core 2.5.1", - "dao-interface 2.5.1", + "cw20-stake 2.6.0", + "dao-dao-core 2.6.0", + "dao-interface 2.6.0", "dao-testing", "dao-voting-cw20-staked", "thiserror", @@ -878,7 +878,7 @@ dependencies = [ [[package]] name = "cw-hooks" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -957,7 +957,7 @@ dependencies = [ [[package]] name = "cw-paginate-storage" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-std", "cw-multi-test", @@ -967,11 +967,11 @@ dependencies = [ [[package]] name = "cw-payroll-factory" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", "cw-ownable", "cw-payroll-factory", @@ -1013,7 +1013,7 @@ dependencies = [ [[package]] name = "cw-stake-tracker" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1066,7 +1066,7 @@ dependencies = [ [[package]] name = "cw-token-swap" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1083,7 +1083,7 @@ dependencies = [ [[package]] name = "cw-tokenfactory-issuer" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1092,7 +1092,7 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw-tokenfactory-types", "cw2 1.1.2", - "dao-interface 2.5.1", + "dao-interface 2.6.0", "osmosis-std", "osmosis-test-tube", "prost 0.12.3", @@ -1105,11 +1105,11 @@ dependencies = [ [[package]] name = "cw-tokenfactory-types" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "dao-interface 2.5.1", + "dao-interface 2.6.0", "osmosis-std", "osmosis-std-derive", "prost 0.12.3", @@ -1176,12 +1176,12 @@ dependencies = [ [[package]] name = "cw-vesting" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", "cw-ownable", "cw-stake-tracker", @@ -1200,7 +1200,7 @@ dependencies = [ [[package]] name = "cw-wormhole" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1367,16 +1367,16 @@ dependencies = [ [[package]] name = "cw20-stake" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-ownable", - "cw-paginate-storage 2.5.1", + "cw-paginate-storage 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 0.13.4", "cw-utils 1.0.3", @@ -1384,16 +1384,16 @@ dependencies = [ "cw20 1.1.2", "cw20-base 1.1.2", "cw20-stake 0.2.6", - "cw20-stake 2.5.1", - "dao-hooks 2.5.1", + "cw20-stake 2.6.0", + "dao-hooks 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "thiserror", ] [[package]] name = "cw20-stake-external-rewards" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -1407,9 +1407,9 @@ dependencies = [ "cw20 0.13.4", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw20-stake-external-rewards", - "dao-hooks 2.5.1", + "dao-hooks 2.6.0", "dao-testing", "stake-cw20-external-rewards", "thiserror", @@ -1417,7 +1417,7 @@ dependencies = [ [[package]] name = "cw20-stake-reward-distributor" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1428,7 +1428,7 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw20-stake-reward-distributor", "dao-testing", "stake-cw20-reward-distributor", @@ -1623,7 +1623,20 @@ dependencies = [ [[package]] name = "cw721-controllers" -version = "2.5.1" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e48a8280ab1863169ce8caa395c72e7cbb0a4638323ddafbd3777da002fa89" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "thiserror", +] + +[[package]] +name = "cw721-controllers" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1634,7 +1647,7 @@ dependencies = [ [[package]] name = "cw721-roles" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1656,7 +1669,7 @@ dependencies = [ [[package]] name = "dao-cw721-extensions" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1686,13 +1699,13 @@ dependencies = [ [[package]] name = "dao-dao-core" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-core", "cw-multi-test", - "cw-paginate-storage 2.5.1", + "cw-paginate-storage 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -1700,9 +1713,9 @@ dependencies = [ "cw20-base 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", - "dao-dao-core 2.5.1", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-core 2.6.0", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-proposal-sudo", "dao-testing", "dao-voting-cw20-balance", @@ -1723,13 +1736,13 @@ dependencies = [ [[package]] name = "dao-dao-macros" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-hooks 2.5.1", - "dao-interface 2.5.1", - "dao-voting 2.5.1", + "cw-hooks 2.6.0", + "dao-interface 2.6.0", + "dao-voting 2.6.0", "proc-macro2", "quote", "syn 1.0.109", @@ -1751,14 +1764,14 @@ dependencies = [ [[package]] name = "dao-hooks" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw4 1.1.2", - "dao-pre-propose-base 2.5.1", - "dao-voting 2.5.1", + "dao-pre-propose-base 2.6.0", + "dao-voting 2.6.0", ] [[package]] @@ -1778,7 +1791,7 @@ dependencies = [ [[package]] name = "dao-interface" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1791,7 +1804,7 @@ dependencies = [ [[package]] name = "dao-migrator" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -1808,31 +1821,31 @@ dependencies = [ "cw20 1.1.2", "cw20-base 1.1.2", "cw20-stake 0.2.6", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw20-staked-balance-voting", "cw4 0.13.4", "cw4-voting", - "dao-dao-core 2.5.1", - "dao-interface 2.5.1", + "dao-dao-core 2.6.0", + "dao-interface 2.6.0", "dao-migrator", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-testing", "dao-voting 0.1.0", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "thiserror", ] [[package]] name = "dao-pre-propose-approval-multiple" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", - "cw-paginate-storage 2.5.1", + "cw-paginate-storage 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -1840,15 +1853,15 @@ dependencies = [ "cw20-base 1.1.2", "cw4 1.1.2", "cw4-group 1.1.2", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", - "dao-pre-propose-base 2.5.1", - "dao-proposal-multiple 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", + "dao-pre-propose-base 2.6.0", + "dao-proposal-multiple 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "thiserror", ] @@ -1871,13 +1884,13 @@ dependencies = [ [[package]] name = "dao-pre-propose-approval-single" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", - "cw-paginate-storage 2.5.1", + "cw-paginate-storage 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -1886,30 +1899,30 @@ dependencies = [ "cw4 1.1.2", "cw4-group 1.1.2", "dao-dao-core 2.4.1", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", "dao-interface 2.4.1", - "dao-interface 2.5.1", + "dao-interface 2.6.0", "dao-pre-propose-approval-single 2.4.1", - "dao-pre-propose-base 2.5.1", + "dao-pre-propose-base 2.6.0", "dao-proposal-single 2.4.1", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-testing", "dao-voting 2.4.1", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", "dao-voting-cw4 2.4.1", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "thiserror", ] [[package]] name = "dao-pre-propose-approver" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -1917,18 +1930,18 @@ dependencies = [ "cw20 1.1.2", "cw20-base 1.1.2", "cw4-group 1.1.2", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", "dao-pre-propose-approval-multiple", - "dao-pre-propose-approval-single 2.5.1", - "dao-pre-propose-base 2.5.1", - "dao-proposal-multiple 2.5.1", - "dao-proposal-single 2.5.1", + "dao-pre-propose-approval-single 2.6.0", + "dao-pre-propose-base 2.6.0", + "dao-proposal-multiple 2.6.0", + "dao-proposal-single 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", ] [[package]] @@ -1952,18 +1965,18 @@ dependencies = [ [[package]] name = "dao-pre-propose-base" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", - "cw-hooks 2.5.1", + "cw-denom 2.6.0", + "cw-hooks 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "dao-interface 2.5.1", - "dao-voting 2.5.1", + "dao-interface 2.6.0", + "dao-voting 2.6.0", "semver", "serde", "thiserror", @@ -1984,11 +1997,11 @@ dependencies = [ [[package]] name = "dao-pre-propose-multiple" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-multi-test", "cw-utils 1.0.3", "cw2 1.1.2", @@ -1997,20 +2010,20 @@ dependencies = [ "cw4 1.1.2", "cw4-group 1.1.2", "dao-dao-core 2.4.1", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", "dao-interface 2.4.1", - "dao-interface 2.5.1", - "dao-pre-propose-base 2.5.1", + "dao-interface 2.6.0", + "dao-pre-propose-base 2.6.0", "dao-pre-propose-multiple 2.4.1", "dao-proposal-multiple 2.4.1", - "dao-proposal-multiple 2.5.1", + "dao-proposal-multiple 2.6.0", "dao-testing", "dao-voting 2.4.1", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", "dao-voting-cw4 2.4.1", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", ] [[package]] @@ -2028,12 +2041,12 @@ dependencies = [ [[package]] name = "dao-pre-propose-single" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", - "cw-hooks 2.5.1", + "cw-denom 2.6.0", + "cw-hooks 2.6.0", "cw-multi-test", "cw-utils 1.0.3", "cw2 1.1.2", @@ -2042,25 +2055,25 @@ dependencies = [ "cw4 1.1.2", "cw4-group 1.1.2", "dao-dao-core 2.4.1", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", "dao-interface 2.4.1", - "dao-interface 2.5.1", - "dao-pre-propose-base 2.5.1", + "dao-interface 2.6.0", + "dao-pre-propose-base 2.6.0", "dao-pre-propose-single 2.4.1", "dao-proposal-single 2.4.1", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-testing", "dao-voting 2.4.1", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", "dao-voting-cw4 2.4.1", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", ] [[package]] name = "dao-proposal-condorcet" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -2071,34 +2084,34 @@ dependencies = [ "cw2 1.1.2", "cw4 1.1.2", "cw4-group 1.1.2", - "dao-dao-core 2.5.1", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-core 2.6.0", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-testing", - "dao-voting 2.5.1", - "dao-voting-cw4 2.5.1", + "dao-voting 2.6.0", + "dao-voting-cw4 2.6.0", "thiserror", ] [[package]] name = "dao-proposal-hook-counter" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "dao-dao-core 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", - "dao-proposal-single 2.5.1", + "dao-dao-core 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", + "dao-proposal-single 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-balance", "thiserror", ] @@ -2128,35 +2141,35 @@ dependencies = [ [[package]] name = "dao-proposal-multiple" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", - "cw-hooks 2.5.1", + "cw-denom 2.6.0", + "cw-hooks 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw4 1.1.2", "cw4-group 1.1.2", "cw721-base 0.18.0", - "dao-dao-macros 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", - "dao-pre-propose-base 2.5.1", - "dao-pre-propose-multiple 2.5.1", - "dao-proposal-multiple 2.5.1", + "dao-dao-macros 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", + "dao-pre-propose-base 2.6.0", + "dao-pre-propose-multiple 2.6.0", + "dao-proposal-multiple 2.6.0", "dao-testing", "dao-voting 0.1.0", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-balance", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "dao-voting-cw721-staked", "dao-voting-token-staked", "rand", @@ -2189,14 +2202,14 @@ dependencies = [ [[package]] name = "dao-proposal-single" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-core", - "cw-denom 2.5.1", - "cw-hooks 2.5.1", + "cw-denom 2.6.0", + "cw-hooks 2.6.0", "cw-multi-test", "cw-proposal-single", "cw-storage-plus 1.2.0", @@ -2205,23 +2218,23 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw4 1.1.2", "cw4-group 1.1.2", "cw721-base 0.18.0", - "dao-dao-core 2.5.1", - "dao-dao-macros 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", - "dao-pre-propose-base 2.5.1", - "dao-pre-propose-single 2.5.1", - "dao-proposal-single 2.5.1", + "dao-dao-core 2.6.0", + "dao-dao-macros 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", + "dao-pre-propose-base 2.6.0", + "dao-pre-propose-single 2.6.0", + "dao-proposal-single 2.6.0", "dao-testing", "dao-voting 0.1.0", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-balance", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "dao-voting-cw721-staked", "dao-voting-token-staked", "thiserror", @@ -2229,21 +2242,21 @@ dependencies = [ [[package]] name = "dao-proposal-sudo" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", "cw-storage-plus 1.2.0", "cw2 1.1.2", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "thiserror", ] [[package]] name = "dao-rewards-distributor" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -2256,17 +2269,17 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw4 1.1.2", "cw4-group 1.1.2", "cw721-base 0.18.0", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", "dao-rewards-distributor", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "dao-voting-cw721-staked", "dao-voting-token-staked", "semver", @@ -2275,7 +2288,7 @@ dependencies = [ [[package]] name = "dao-test-custom-factory" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2287,15 +2300,15 @@ dependencies = [ "cw2 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", - "dao-voting 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", + "dao-voting 2.6.0", "thiserror", ] [[package]] name = "dao-testing" -version = "2.5.1" +version = "2.6.0" dependencies = [ "btsg-ft-factory", "cosmwasm-schema", @@ -2303,7 +2316,7 @@ dependencies = [ "cw-admin-factory", "cw-core", "cw-fund-distributor", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-payroll-factory", "cw-proposal-single", @@ -2315,7 +2328,7 @@ dependencies = [ "cw20 1.1.2", "cw20-base 1.1.2", "cw20-stake 0.2.6", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw20-stake-external-rewards", "cw20-stake-reward-distributor", "cw4 1.1.2", @@ -2324,33 +2337,33 @@ dependencies = [ "cw721-base 0.18.0", "cw721-roles", "dao-dao-core 2.4.1", - "dao-dao-core 2.5.1", + "dao-dao-core 2.6.0", "dao-interface 2.4.1", - "dao-interface 2.5.1", + "dao-interface 2.6.0", "dao-migrator", "dao-pre-propose-approval-single 2.4.1", - "dao-pre-propose-approval-single 2.5.1", + "dao-pre-propose-approval-single 2.6.0", "dao-pre-propose-approver", "dao-pre-propose-multiple 2.4.1", - "dao-pre-propose-multiple 2.5.1", + "dao-pre-propose-multiple 2.6.0", "dao-pre-propose-single 2.4.1", - "dao-pre-propose-single 2.5.1", + "dao-pre-propose-single 2.6.0", "dao-proposal-condorcet", "dao-proposal-hook-counter", "dao-proposal-multiple 2.4.1", - "dao-proposal-multiple 2.5.1", + "dao-proposal-multiple 2.6.0", "dao-proposal-single 2.4.1", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-proposal-sudo", "dao-rewards-distributor", "dao-test-custom-factory", "dao-voting 0.1.0", "dao-voting 2.4.1", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-balance", "dao-voting-cw20-staked", "dao-voting-cw4 2.4.1", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "dao-voting-cw721-roles", "dao-voting-cw721-staked", "dao-voting-onft-staked", @@ -2396,22 +2409,22 @@ dependencies = [ [[package]] name = "dao-voting" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-denom 2.5.1", + "cw-denom 2.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw20 1.1.2", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "thiserror", ] [[package]] name = "dao-voting-cw20-balance" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2421,15 +2434,15 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-testing", "thiserror", ] [[package]] name = "dao-voting-cw20-staked" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2439,11 +2452,11 @@ dependencies = [ "cw2 1.1.2", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "cw20-stake 2.6.0", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "thiserror", ] @@ -2467,7 +2480,7 @@ dependencies = [ [[package]] name = "dao-voting-cw4" -version = "2.5.1" +version = "2.6.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -2477,16 +2490,16 @@ dependencies = [ "cw2 1.1.2", "cw4 1.1.2", "cw4-group 1.1.2", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-testing", - "dao-voting-cw4 2.5.1", + "dao-voting-cw4 2.6.0", "thiserror", ] [[package]] name = "dao-voting-cw721-roles" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", @@ -2499,39 +2512,39 @@ dependencies = [ "cw4 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", - "cw721-controllers", "cw721-roles", "dao-cw721-extensions", - "dao-dao-macros 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-interface 2.6.0", "dao-testing", "thiserror", ] [[package]] name = "dao-voting-cw721-staked" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", "cw721 0.18.0", "cw721-base 0.18.0", - "cw721-controllers", - "dao-dao-macros 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", + "cw721-controllers 2.5.0", + "cw721-controllers 2.6.0", + "dao-dao-macros 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", "dao-proposal-hook-counter", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-test-custom-factory", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "osmosis-std", "osmosis-test-tube", "serde", @@ -2540,26 +2553,27 @@ dependencies = [ [[package]] name = "dao-voting-onft-staked" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "cw721-controllers", - "dao-dao-macros 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", + "cw721-controllers 2.5.0", + "cw721-controllers 2.6.0", + "dao-dao-macros 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", "dao-proposal-hook-counter", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-test-custom-factory", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "omniflix-std", "osmosis-test-tube", "prost 0.12.3", @@ -2570,27 +2584,27 @@ dependencies = [ [[package]] name = "dao-voting-token-staked" -version = "2.5.1" +version = "2.6.0" dependencies = [ "anyhow", "cosmwasm-schema", "cosmwasm-std", "cw-controllers 1.1.2", - "cw-hooks 2.5.1", + "cw-hooks 2.6.0", "cw-multi-test", "cw-ownable", "cw-storage-plus 1.2.0", "cw-tokenfactory-issuer", "cw-utils 1.0.3", "cw2 1.1.2", - "dao-dao-macros 2.5.1", - "dao-hooks 2.5.1", - "dao-interface 2.5.1", + "dao-dao-macros 2.6.0", + "dao-hooks 2.6.0", + "dao-interface 2.6.0", "dao-proposal-hook-counter", - "dao-proposal-single 2.5.1", + "dao-proposal-single 2.6.0", "dao-test-custom-factory", "dao-testing", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "osmosis-std", "osmosis-test-tube", "serde", @@ -3278,16 +3292,16 @@ dependencies = [ "cw-vesting", "cw20 1.1.2", "cw20-base 1.1.2", - "cw20-stake 2.5.1", + "cw20-stake 2.6.0", "cw721 0.18.0", "cw721-base 0.18.0", "cw721-roles", - "dao-dao-core 2.5.1", - "dao-interface 2.5.1", - "dao-pre-propose-single 2.5.1", - "dao-proposal-single 2.5.1", + "dao-dao-core 2.6.0", + "dao-interface 2.6.0", + "dao-pre-propose-single 2.6.0", + "dao-proposal-single 2.6.0", "dao-test-custom-factory", - "dao-voting 2.5.1", + "dao-voting 2.6.0", "dao-voting-cw20-staked", "dao-voting-cw721-staked", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 8ccbe6e0e..e2e18cab5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ resolver = "2" edition = "2021" license = "BSD-3-Clause" repository = "https://github.com/DA0-DA0/dao-contracts" -version = "2.5.1" +version = "2.6.0" [profile.release] codegen-units = 1 @@ -85,52 +85,52 @@ wynd-utils = "0.4" # optional owner. cw-ownable = "0.5" -btsg-ft-factory = { path = "./contracts/external/btsg-ft-factory", version = "2.5.1" } -cw-admin-factory = { path = "./contracts/external/cw-admin-factory", version = "2.5.1" } -cw-denom = { path = "./packages/cw-denom", version = "2.5.1" } -cw-fund-distributor = { path = "./contracts/distribution/cw-fund-distributor", version = "2.5.1" } -cw-hooks = { path = "./packages/cw-hooks", version = "2.5.1" } -cw-paginate-storage = { path = "./packages/cw-paginate-storage", version = "2.5.1" } -cw-payroll-factory = { path = "./contracts/external/cw-payroll-factory", version = "2.5.1" } -cw-stake-tracker = { path = "./packages/cw-stake-tracker", version = "2.5.1" } -cw-token-swap = { path = "./contracts/external/cw-token-swap", version = "2.5.1" } -cw-tokenfactory-issuer = { path = "./contracts/external/cw-tokenfactory-issuer", version = "2.5.1", default-features = false } -cw-tokenfactory-types = { path = "./packages/cw-tokenfactory-types", version = "2.5.1", default-features = false } -cw-vesting = { path = "./contracts/external/cw-vesting", version = "2.5.1" } -cw-wormhole = { path = "./packages/cw-wormhole", version = "2.5.1" } -cw20-stake = { path = "./contracts/staking/cw20-stake", version = "2.5.1" } -cw20-stake-external-rewards = { path = "./contracts/staking/cw20-stake-external-rewards", version = "2.5.1" } -cw20-stake-reward-distributor = { path = "./contracts/staking/cw20-stake-reward-distributor", version = "2.5.1" } -cw721-controllers = { path = "./packages/cw721-controllers", version = "2.5.1" } -cw721-roles = { path = "./contracts/external/cw721-roles", version = "2.5.1" } -dao-cw721-extensions = { path = "./packages/dao-cw721-extensions", version = "2.5.1" } -dao-dao-core = { path = "./contracts/dao-dao-core", version = "2.5.1" } -dao-dao-macros = { path = "./packages/dao-dao-macros", version = "2.5.1" } -dao-hooks = { path = "./packages/dao-hooks", version = "2.5.1" } -dao-interface = { path = "./packages/dao-interface", version = "2.5.1" } -dao-pre-propose-approval-multiple = { path = "./contracts/pre-propose/dao-pre-propose-approval-multiple", version = "2.5.1" } -dao-migrator = { path = "./contracts/external/dao-migrator", version = "2.5.1" } -dao-pre-propose-approval-single = { path = "./contracts/pre-propose/dao-pre-propose-approval-single", version = "2.5.1" } -dao-pre-propose-approver = { path = "./contracts/pre-propose/dao-pre-propose-approver", version = "2.5.1" } -dao-pre-propose-base = { path = "./packages/dao-pre-propose-base", version = "2.5.1" } -dao-pre-propose-multiple = { path = "./contracts/pre-propose/dao-pre-propose-multiple", version = "2.5.1" } -dao-pre-propose-single = { path = "./contracts/pre-propose/dao-pre-propose-single", version = "2.5.1" } -dao-proposal-condorcet = { path = "./contracts/proposal/dao-proposal-condorcet", version = "2.5.1" } -dao-proposal-hook-counter = { path = "./contracts/test/dao-proposal-hook-counter", version = "2.5.1" } -dao-proposal-multiple = { path = "./contracts/proposal/dao-proposal-multiple", version = "2.5.1" } -dao-proposal-single = { path = "./contracts/proposal/dao-proposal-single", version = "2.5.1" } -dao-proposal-sudo = { path = "./contracts/test/dao-proposal-sudo", version = "2.5.1" } -dao-rewards-distributor = { path = "./contracts/distribution/dao-rewards-distributor", version = "2.5.1" } -dao-test-custom-factory = { path = "./contracts/test/dao-test-custom-factory", version = "2.5.1" } -dao-testing = { path = "./packages/dao-testing", version = "2.5.1" } -dao-voting = { path = "./packages/dao-voting", version = "2.5.1" } -dao-voting-cw20-balance = { path = "./contracts/test/dao-voting-cw20-balance", version = "2.5.1" } -dao-voting-cw20-staked = { path = "./contracts/voting/dao-voting-cw20-staked", version = "2.5.1" } -dao-voting-cw4 = { path = "./contracts/voting/dao-voting-cw4", version = "2.5.1" } -dao-voting-cw721-roles = { path = "./contracts/voting/dao-voting-cw721-roles", version = "2.5.1" } -dao-voting-cw721-staked = { path = "./contracts/voting/dao-voting-cw721-staked", version = "2.5.1" } -dao-voting-onft-staked = { path = "./contracts/voting/dao-voting-onft-staked", version = "2.5.1" } -dao-voting-token-staked = { path = "./contracts/voting/dao-voting-token-staked", version = "2.5.1" } +btsg-ft-factory = { path = "./contracts/external/btsg-ft-factory", version = "2.6.0" } +cw-admin-factory = { path = "./contracts/external/cw-admin-factory", version = "2.6.0" } +cw-denom = { path = "./packages/cw-denom", version = "2.6.0" } +cw-fund-distributor = { path = "./contracts/distribution/cw-fund-distributor", version = "2.6.0" } +cw-hooks = { path = "./packages/cw-hooks", version = "2.6.0" } +cw-paginate-storage = { path = "./packages/cw-paginate-storage", version = "2.6.0" } +cw-payroll-factory = { path = "./contracts/external/cw-payroll-factory", version = "2.6.0" } +cw-stake-tracker = { path = "./packages/cw-stake-tracker", version = "2.6.0" } +cw-token-swap = { path = "./contracts/external/cw-token-swap", version = "2.6.0" } +cw-tokenfactory-issuer = { path = "./contracts/external/cw-tokenfactory-issuer", version = "2.6.0", default-features = false } +cw-tokenfactory-types = { path = "./packages/cw-tokenfactory-types", version = "2.6.0", default-features = false } +cw-vesting = { path = "./contracts/external/cw-vesting", version = "2.6.0" } +cw-wormhole = { path = "./packages/cw-wormhole", version = "2.6.0" } +cw20-stake = { path = "./contracts/staking/cw20-stake", version = "2.6.0" } +cw20-stake-external-rewards = { path = "./contracts/staking/cw20-stake-external-rewards", version = "2.6.0" } +cw20-stake-reward-distributor = { path = "./contracts/staking/cw20-stake-reward-distributor", version = "2.6.0" } +cw721-controllers = { path = "./packages/cw721-controllers", version = "2.6.0" } +cw721-roles = { path = "./contracts/external/cw721-roles", version = "2.6.0" } +dao-cw721-extensions = { path = "./packages/dao-cw721-extensions", version = "2.6.0" } +dao-dao-core = { path = "./contracts/dao-dao-core", version = "2.6.0" } +dao-dao-macros = { path = "./packages/dao-dao-macros", version = "2.6.0" } +dao-hooks = { path = "./packages/dao-hooks", version = "2.6.0" } +dao-interface = { path = "./packages/dao-interface", version = "2.6.0" } +dao-pre-propose-approval-multiple = { path = "./contracts/pre-propose/dao-pre-propose-approval-multiple", version = "2.6.0" } +dao-migrator = { path = "./contracts/external/dao-migrator", version = "2.6.0" } +dao-pre-propose-approval-single = { path = "./contracts/pre-propose/dao-pre-propose-approval-single", version = "2.6.0" } +dao-pre-propose-approver = { path = "./contracts/pre-propose/dao-pre-propose-approver", version = "2.6.0" } +dao-pre-propose-base = { path = "./packages/dao-pre-propose-base", version = "2.6.0" } +dao-pre-propose-multiple = { path = "./contracts/pre-propose/dao-pre-propose-multiple", version = "2.6.0" } +dao-pre-propose-single = { path = "./contracts/pre-propose/dao-pre-propose-single", version = "2.6.0" } +dao-proposal-condorcet = { path = "./contracts/proposal/dao-proposal-condorcet", version = "2.6.0" } +dao-proposal-hook-counter = { path = "./contracts/test/dao-proposal-hook-counter", version = "2.6.0" } +dao-proposal-multiple = { path = "./contracts/proposal/dao-proposal-multiple", version = "2.6.0" } +dao-proposal-single = { path = "./contracts/proposal/dao-proposal-single", version = "2.6.0" } +dao-proposal-sudo = { path = "./contracts/test/dao-proposal-sudo", version = "2.6.0" } +dao-rewards-distributor = { path = "./contracts/distribution/dao-rewards-distributor", version = "2.6.0" } +dao-test-custom-factory = { path = "./contracts/test/dao-test-custom-factory", version = "2.6.0" } +dao-testing = { path = "./packages/dao-testing", version = "2.6.0" } +dao-voting = { path = "./packages/dao-voting", version = "2.6.0" } +dao-voting-cw20-balance = { path = "./contracts/test/dao-voting-cw20-balance", version = "2.6.0" } +dao-voting-cw20-staked = { path = "./contracts/voting/dao-voting-cw20-staked", version = "2.6.0" } +dao-voting-cw4 = { path = "./contracts/voting/dao-voting-cw4", version = "2.6.0" } +dao-voting-cw721-roles = { path = "./contracts/voting/dao-voting-cw721-roles", version = "2.6.0" } +dao-voting-cw721-staked = { path = "./contracts/voting/dao-voting-cw721-staked", version = "2.6.0" } +dao-voting-onft-staked = { path = "./contracts/voting/dao-voting-onft-staked", version = "2.6.0" } +dao-voting-token-staked = { path = "./contracts/voting/dao-voting-token-staked", version = "2.6.0" } # v1 dependencies. used for state migrations. cw-core-v1 = { package = "cw-core", version = "0.1.0" } @@ -159,3 +159,6 @@ dao-proposal-multiple-v241 = { package = "dao-proposal-multiple", version = "=2. dao-proposal-single-v241 = { package = "dao-proposal-single", version = "=2.4.1" } dao-voting-cw4-v241 = { package = "dao-voting-cw4", version = "=2.4.1" } dao-voting-v241 = { package = "dao-voting", version = "=2.4.1" } + +# v2.5.0 dependencies. +cw721-controllers-v250 = { package = "cw721-controllers", version = "=2.5.0" } diff --git a/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs b/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs index dd13c47fb..390407979 100644 --- a/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs +++ b/ci/integration-tests/src/tests/dao_voting_cw721_staked_test.rs @@ -1,4 +1,4 @@ -use cosm_orc::orchestrator::{ExecReq, SigningKey}; +use cosm_orc::orchestrator::SigningKey; use cosmwasm_std::{Binary, Empty, Uint128}; use cw_utils::Duration; use test_context::test_context; @@ -126,7 +126,9 @@ pub fn claim_nfts(chain: &mut Chain, sender: &SigningKey) { .execute( CONTRACT_NAME, "claim_nfts", - &module::msg::ExecuteMsg::ClaimNfts {}, + &module::msg::ExecuteMsg::ClaimNfts { + r#type: module::msg::ClaimType::All, + }, sender, vec![], ) @@ -189,79 +191,3 @@ fn cw721_stake_tokens(chain: &mut Chain) { let voting_power = query_voting_power(chain, &user_addr, None); assert_eq!(voting_power, Uint128::zero()); } - -#[test_context(Chain)] -#[test] -#[ignore] -fn cw721_stake_max_claims_works(chain: &mut Chain) { - use module::state::MAX_CLAIMS; - - let user_addr = chain.users["user1"].account.address.clone(); - let user_key = chain.users["user1"].key.clone(); - - let CommonTest { module, .. } = - setup_test(chain, Some(Duration::Height(1)), &user_key, &user_addr); - - // Create `MAX_CLAIMS` claims. - - // batch_size * 3 = the number of msgs to be batched per tx. - // We cant batch all of the msgs under a single tx because we hit MAX_BLOCK_GAS limits. - let batch_size = 10; - let mut total_msgs = 0; - - let mut reqs = vec![]; - for i in 0..MAX_CLAIMS { - let token_id = i.to_string(); - - reqs.push(ExecReq { - contract_name: CW721_NAME.to_string(), - msg: Box::new(cw721_base::ExecuteMsg::Mint:: { - token_id: token_id.clone(), - owner: user_addr.to_string(), - token_uri: None, - extension: Empty::default(), - }), - funds: vec![], - }); - - reqs.push(ExecReq { - contract_name: CW721_NAME.to_string(), - msg: Box::new(cw721::Cw721ExecuteMsg::SendNft { - contract: module.to_string(), - token_id: token_id.clone(), - msg: Binary::default(), - }), - funds: vec![], - }); - - reqs.push(ExecReq { - contract_name: CONTRACT_NAME.to_string(), - msg: Box::new(module::msg::ExecuteMsg::Unstake { - token_ids: vec![token_id], - }), - funds: vec![], - }); - - if (i != 0 && i % batch_size == 0) || i == MAX_CLAIMS - 1 { - total_msgs += reqs.len(); - - chain - .orc - .execute_batch("batch_cw721_stake_max_claims", reqs, &user_key) - .unwrap(); - - reqs = vec![]; - } - } - - assert_eq!(total_msgs as u64, MAX_CLAIMS * 3); - - chain - .orc - .poll_for_n_blocks(1, core::time::Duration::from_millis(20_000), false) - .unwrap(); - - // If this works, we're golden. Other tests make sure that the - // NFTs get returned as a result of this. - claim_nfts(chain, &user_key); -} diff --git a/contracts/dao-dao-core/schema/dao-dao-core.json b/contracts/dao-dao-core/schema/dao-dao-core.json index 7ca1635ea..b9f673d8e 100644 --- a/contracts/dao-dao-core/schema/dao-dao-core.json +++ b/contracts/dao-dao-core/schema/dao-dao-core.json @@ -1,6 +1,6 @@ { "contract_name": "dao-dao-core", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/distribution/cw-fund-distributor/schema/cw-fund-distributor.json b/contracts/distribution/cw-fund-distributor/schema/cw-fund-distributor.json index 8c7e8ac22..3666612bd 100644 --- a/contracts/distribution/cw-fund-distributor/schema/cw-fund-distributor.json +++ b/contracts/distribution/cw-fund-distributor/schema/cw-fund-distributor.json @@ -1,6 +1,6 @@ { "contract_name": "cw-fund-distributor", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/distribution/dao-rewards-distributor/schema/dao-rewards-distributor.json b/contracts/distribution/dao-rewards-distributor/schema/dao-rewards-distributor.json index 93f017585..07d8eec5c 100644 --- a/contracts/distribution/dao-rewards-distributor/schema/dao-rewards-distributor.json +++ b/contracts/distribution/dao-rewards-distributor/schema/dao-rewards-distributor.json @@ -1,6 +1,6 @@ { "contract_name": "dao-rewards-distributor", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/btsg-ft-factory/schema/btsg-ft-factory.json b/contracts/external/btsg-ft-factory/schema/btsg-ft-factory.json index 784d1ca5a..e937ca5bb 100644 --- a/contracts/external/btsg-ft-factory/schema/btsg-ft-factory.json +++ b/contracts/external/btsg-ft-factory/schema/btsg-ft-factory.json @@ -1,6 +1,6 @@ { "contract_name": "btsg-ft-factory", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw-admin-factory/schema/cw-admin-factory.json b/contracts/external/cw-admin-factory/schema/cw-admin-factory.json index 743c9ceed..edff0be9f 100644 --- a/contracts/external/cw-admin-factory/schema/cw-admin-factory.json +++ b/contracts/external/cw-admin-factory/schema/cw-admin-factory.json @@ -1,6 +1,6 @@ { "contract_name": "cw-admin-factory", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw-payroll-factory/schema/cw-payroll-factory.json b/contracts/external/cw-payroll-factory/schema/cw-payroll-factory.json index 1544e81a9..6bf66ae5d 100644 --- a/contracts/external/cw-payroll-factory/schema/cw-payroll-factory.json +++ b/contracts/external/cw-payroll-factory/schema/cw-payroll-factory.json @@ -1,6 +1,6 @@ { "contract_name": "cw-payroll-factory", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw-token-swap/schema/cw-token-swap.json b/contracts/external/cw-token-swap/schema/cw-token-swap.json index 66b147cd2..1f6dade46 100644 --- a/contracts/external/cw-token-swap/schema/cw-token-swap.json +++ b/contracts/external/cw-token-swap/schema/cw-token-swap.json @@ -1,6 +1,6 @@ { "contract_name": "cw-token-swap", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json index a7bed322d..8240fba03 100644 --- a/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json +++ b/contracts/external/cw-tokenfactory-issuer/schema/cw-tokenfactory-issuer.json @@ -1,6 +1,6 @@ { "contract_name": "cw-tokenfactory-issuer", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw-vesting/schema/cw-vesting.json b/contracts/external/cw-vesting/schema/cw-vesting.json index 6a3fd952d..c85fc4d39 100644 --- a/contracts/external/cw-vesting/schema/cw-vesting.json +++ b/contracts/external/cw-vesting/schema/cw-vesting.json @@ -1,6 +1,6 @@ { "contract_name": "cw-vesting", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/cw721-roles/schema/cw721-roles.json b/contracts/external/cw721-roles/schema/cw721-roles.json index 18e12c74b..e4d7c61a2 100644 --- a/contracts/external/cw721-roles/schema/cw721-roles.json +++ b/contracts/external/cw721-roles/schema/cw721-roles.json @@ -1,6 +1,6 @@ { "contract_name": "cw721-roles", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/external/dao-migrator/schema/dao-migrator.json b/contracts/external/dao-migrator/schema/dao-migrator.json index 954647379..c83063c9b 100644 --- a/contracts/external/dao-migrator/schema/dao-migrator.json +++ b/contracts/external/dao-migrator/schema/dao-migrator.json @@ -1,6 +1,6 @@ { "contract_name": "dao-migrator", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/pre-propose/dao-pre-propose-approval-multiple/schema/dao-pre-propose-approval-multiple.json b/contracts/pre-propose/dao-pre-propose-approval-multiple/schema/dao-pre-propose-approval-multiple.json index bfcff069a..520973ce4 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-multiple/schema/dao-pre-propose-approval-multiple.json +++ b/contracts/pre-propose/dao-pre-propose-approval-multiple/schema/dao-pre-propose-approval-multiple.json @@ -1,6 +1,6 @@ { "contract_name": "dao-pre-propose-approval-multiple", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json b/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json index ef0e213b4..c6a999b80 100644 --- a/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json +++ b/contracts/pre-propose/dao-pre-propose-approval-single/schema/dao-pre-propose-approval-single.json @@ -1,6 +1,6 @@ { "contract_name": "dao-pre-propose-approval-single", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json b/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json index 1168bb676..bfb71f2fd 100644 --- a/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json +++ b/contracts/pre-propose/dao-pre-propose-approver/schema/dao-pre-propose-approver.json @@ -1,6 +1,6 @@ { "contract_name": "dao-pre-propose-approver", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json b/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json index 9d2b4a9b5..614783775 100644 --- a/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json +++ b/contracts/pre-propose/dao-pre-propose-multiple/schema/dao-pre-propose-multiple.json @@ -1,6 +1,6 @@ { "contract_name": "dao-pre-propose-multiple", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json b/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json index ca2a8348c..136a7800b 100644 --- a/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json +++ b/contracts/pre-propose/dao-pre-propose-single/schema/dao-pre-propose-single.json @@ -1,6 +1,6 @@ { "contract_name": "dao-pre-propose-single", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json b/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json index 49c48e13b..fdc97d640 100644 --- a/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json +++ b/contracts/proposal/dao-proposal-condorcet/schema/dao-proposal-condorcet.json @@ -1,6 +1,6 @@ { "contract_name": "dao-proposal-condorcet", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json index 0921abb8a..43f5f9941 100644 --- a/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json +++ b/contracts/proposal/dao-proposal-multiple/schema/dao-proposal-multiple.json @@ -1,6 +1,6 @@ { "contract_name": "dao-proposal-multiple", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json b/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json index cd90bc875..c08edfbc4 100644 --- a/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json +++ b/contracts/proposal/dao-proposal-single/schema/dao-proposal-single.json @@ -1,6 +1,6 @@ { "contract_name": "dao-proposal-single", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json b/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json index 9bb4e69d7..58653c88d 100644 --- a/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json +++ b/contracts/staking/cw20-stake-external-rewards/schema/cw20-stake-external-rewards.json @@ -1,6 +1,6 @@ { "contract_name": "cw20-stake-external-rewards", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/staking/cw20-stake-reward-distributor/schema/cw20-stake-reward-distributor.json b/contracts/staking/cw20-stake-reward-distributor/schema/cw20-stake-reward-distributor.json index d1328d9d4..da228880e 100644 --- a/contracts/staking/cw20-stake-reward-distributor/schema/cw20-stake-reward-distributor.json +++ b/contracts/staking/cw20-stake-reward-distributor/schema/cw20-stake-reward-distributor.json @@ -1,6 +1,6 @@ { "contract_name": "cw20-stake-reward-distributor", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/staking/cw20-stake/schema/cw20-stake.json b/contracts/staking/cw20-stake/schema/cw20-stake.json index 8716e1d57..b12f6659f 100644 --- a/contracts/staking/cw20-stake/schema/cw20-stake.json +++ b/contracts/staking/cw20-stake/schema/cw20-stake.json @@ -1,6 +1,6 @@ { "contract_name": "cw20-stake", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/voting/dao-voting-cw20-staked/schema/dao-voting-cw20-staked.json b/contracts/voting/dao-voting-cw20-staked/schema/dao-voting-cw20-staked.json index ce7327e21..fca963e8e 100644 --- a/contracts/voting/dao-voting-cw20-staked/schema/dao-voting-cw20-staked.json +++ b/contracts/voting/dao-voting-cw20-staked/schema/dao-voting-cw20-staked.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-cw20-staked", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/voting/dao-voting-cw4/schema/dao-voting-cw4.json b/contracts/voting/dao-voting-cw4/schema/dao-voting-cw4.json index cee956c6e..e6595e7d7 100644 --- a/contracts/voting/dao-voting-cw4/schema/dao-voting-cw4.json +++ b/contracts/voting/dao-voting-cw4/schema/dao-voting-cw4.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-cw4", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/voting/dao-voting-cw721-roles/Cargo.toml b/contracts/voting/dao-voting-cw721-roles/Cargo.toml index e816d763c..7fbf4360a 100644 --- a/contracts/voting/dao-voting-cw721-roles/Cargo.toml +++ b/contracts/voting/dao-voting-cw721-roles/Cargo.toml @@ -22,7 +22,6 @@ dao-cw721-extensions = { workspace = true } dao-dao-macros = { workspace = true } dao-interface = { workspace = true } cw721-base = { workspace = true, features = ["library"] } -cw721-controllers = { workspace = true } cw-ownable = { workspace = true } cw721 = { workspace = true } cw-utils = { workspace = true } diff --git a/contracts/voting/dao-voting-cw721-roles/schema/dao-voting-cw721-roles.json b/contracts/voting/dao-voting-cw721-roles/schema/dao-voting-cw721-roles.json index e804bb47a..3d581ddbd 100644 --- a/contracts/voting/dao-voting-cw721-roles/schema/dao-voting-cw721-roles.json +++ b/contracts/voting/dao-voting-cw721-roles/schema/dao-voting-cw721-roles.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-cw721-roles", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/voting/dao-voting-cw721-staked/Cargo.toml b/contracts/voting/dao-voting-cw721-staked/Cargo.toml index af7d5532e..f63e4dc12 100644 --- a/contracts/voting/dao-voting-cw721-staked/Cargo.toml +++ b/contracts/voting/dao-voting-cw721-staked/Cargo.toml @@ -31,6 +31,7 @@ cw-hooks = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true, features = ["library"] } cw721-controllers = { workspace = true } +cw721-controllers-v250 = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } dao-dao-macros = { workspace = true } diff --git a/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json b/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json index 346476689..05661b18a 100644 --- a/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json +++ b/contracts/voting/dao-voting-cw721-staked/schema/dao-voting-cw721-staked.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-cw721-staked", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -273,6 +273,14 @@ "properties": { "claim_nfts": { "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "$ref": "#/definitions/ClaimType" + } + }, "additionalProperties": false } }, @@ -429,6 +437,40 @@ "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", "type": "string" }, + "ClaimType": { + "oneOf": [ + { + "description": "Claims all legacy claims.", + "type": "string", + "enum": [ + "legacy" + ] + }, + { + "description": "Claims all non-legacy claims.", + "type": "string", + "enum": [ + "all" + ] + }, + { + "description": "Claims specific non-legacy NFTs.", + "type": "object", + "required": [ + "specific" + ], + "properties": { + "specific": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, "Cw721ReceiveMsg": { "description": "Cw721ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", "type": "object", @@ -525,6 +567,20 @@ "properties": { "address": { "type": "string" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false @@ -963,14 +1019,25 @@ "NftClaim": { "type": "object", "required": [ + "legacy", "release_at", "token_id" ], "properties": { + "legacy": { + "description": "Whether the claim is a legacy claim.", + "type": "boolean" + }, "release_at": { - "$ref": "#/definitions/Expiration" + "description": "The expiration time of the claim.", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] }, "token_id": { + "description": "The token ID of the NFT being claimed.", "type": "string" } }, diff --git a/contracts/voting/dao-voting-cw721-staked/src/contract.rs b/contracts/voting/dao-voting-cw721-staked/src/contract.rs index fc83cce13..53f574c24 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/contract.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/contract.rs @@ -17,10 +17,14 @@ use dao_voting::threshold::{ ActiveThresholdResponse, }; -use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, NftContract, QueryMsg}; +use crate::msg::{ + ClaimType, ExecuteMsg, InstantiateMsg, MigrateMsg, NftClaim, NftClaimsResponse, NftContract, + QueryMsg, +}; use crate::state::{ register_staked_nft, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, - INITIAL_NFTS, MAX_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, TOTAL_STAKED_NFTS, + INITIAL_NFTS, LEGACY_NFT_CLAIMS, NFT_BALANCES, NFT_CLAIMS, STAKED_NFTS_PER_OWNER, + TOTAL_STAKED_NFTS, }; use crate::ContractError; @@ -204,7 +208,7 @@ pub fn execute( match msg { ExecuteMsg::ReceiveNft(msg) => execute_stake(deps, env, info, msg), ExecuteMsg::Unstake { token_ids } => execute_unstake(deps, env, info, token_ids), - ExecuteMsg::ClaimNfts {} => execute_claim_nfts(deps, env, info), + ExecuteMsg::ClaimNfts { r#type } => execute_claim_nfts(deps, env, info, r#type), ExecuteMsg::UpdateConfig { duration } => execute_update_config(info, deps, duration), ExecuteMsg::AddHook { addr } => execute_add_hook(deps, info, addr), ExecuteMsg::RemoveHook { addr } => execute_remove_hook(deps, info, addr), @@ -314,13 +318,6 @@ pub fn execute_unstake( } Some(duration) => { - let outstanding_claims = NFT_CLAIMS - .query_claims(deps.as_ref(), &info.sender)? - .nft_claims; - if outstanding_claims.len() + token_ids.len() > MAX_CLAIMS as usize { - return Err(ContractError::TooManyClaims {}); - } - // Out of gas here is fine - just try again with fewer // tokens. NFT_CLAIMS.create_nft_claims( @@ -343,22 +340,52 @@ pub fn execute_claim_nfts( deps: DepsMut, env: Env, info: MessageInfo, + claim_type: ClaimType, ) -> Result { - let nfts = NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)?; - if nfts.is_empty() { + let token_ids = match claim_type { + // attempt to claim all legacy NFTs + ClaimType::Legacy => { + LEGACY_NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)? + } + // attempt to claim all non-legacy NFTs + ClaimType::All => { + let token_ids = NFT_CLAIMS + .query_claims(deps.as_ref(), &info.sender, None, None)? + .into_iter() + .filter(|nft| nft.release_at.is_expired(&env.block)) + .map(|nft| nft.token_id) + .collect::>(); + + if !token_ids.is_empty() { + NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; + } + + token_ids + } + // attempt to claim specific non-legacy NFTs + ClaimType::Specific(token_ids) => { + if !token_ids.is_empty() { + NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; + } + + token_ids + } + }; + + if token_ids.is_empty() { return Err(ContractError::NothingToClaim {}); } let config = CONFIG.load(deps.storage)?; - let msgs = nfts + let msgs = token_ids .into_iter() - .map(|nft| -> StdResult { + .map(|token_id| -> StdResult { Ok(WasmMsg::Execute { contract_addr: config.nft_address.to_string(), msg: to_json_binary(&cw721::Cw721ExecuteMsg::TransferNft { recipient: info.sender.to_string(), - token_id: nft, + token_id, })?, funds: vec![], } @@ -485,7 +512,11 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { QueryMsg::Dao {} => query_dao(deps), QueryMsg::Info {} => query_info(deps), QueryMsg::IsActive {} => query_is_active(deps, env), - QueryMsg::NftClaims { address } => query_nft_claims(deps, address), + QueryMsg::NftClaims { + address, + start_after, + limit, + } => query_nft_claims(deps, address, start_after, limit), QueryMsg::Hooks {} => query_hooks(deps), QueryMsg::StakedNfts { address, @@ -606,8 +637,41 @@ pub fn query_dao(deps: Deps) -> StdResult { to_json_binary(&dao) } -pub fn query_nft_claims(deps: Deps, address: String) -> StdResult { - to_json_binary(&NFT_CLAIMS.query_claims(deps, &deps.api.addr_validate(&address)?)?) +pub fn query_nft_claims( + deps: Deps, + address: String, + start_after: Option, + limit: Option, +) -> StdResult { + let addr = deps.api.addr_validate(&address)?; + + // load all legacy claims since it does not support pagination + let legacy_claims = LEGACY_NFT_CLAIMS + .query_claims(deps, &addr)? + .nft_claims + .into_iter() + .map(|c| NftClaim { + token_id: c.token_id, + release_at: c.release_at, + legacy: true, + }) + .collect::>(); + + // paginate all new claims + let claims = NFT_CLAIMS + .query_claims(deps, &addr, start_after.as_ref(), limit)? + .into_iter() + .map(|c| NftClaim { + token_id: c.token_id, + release_at: c.release_at, + legacy: false, + }) + .collect::>(); + + // combine legacy and new claims + let nft_claims = legacy_claims.into_iter().chain(claims).collect(); + + to_json_binary(&NftClaimsResponse { nft_claims }) } pub fn query_hooks(deps: Deps) -> StdResult { diff --git a/contracts/voting/dao-voting-cw721-staked/src/error.rs b/contracts/voting/dao-voting-cw721-staked/src/error.rs index df9526bd2..97f41436b 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/error.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/error.rs @@ -20,6 +20,9 @@ pub enum ContractError { #[error(transparent)] UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), + #[error(transparent)] + NftClaimError(#[from] cw721_controllers::NftClaimError), + #[error("Can not stake that which has already been staked")] AlreadyStaked {}, @@ -44,9 +47,6 @@ pub enum ContractError { #[error("Can not unstake that which you have not staked (unstaking {token_id})")] NotStaked { token_id: String }, - #[error("Too many outstanding claims. Claim some tokens before unstaking more.")] - TooManyClaims {}, - #[error("Unauthorized")] Unauthorized {}, diff --git a/contracts/voting/dao-voting-cw721-staked/src/msg.rs b/contracts/voting/dao-voting-cw721-staked/src/msg.rs index 837851ed3..62ca2efd4 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/msg.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/msg.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Binary; -use cw721::Cw721ReceiveMsg; +use cw721::{Cw721ReceiveMsg, Expiration}; use cw_utils::Duration; use dao_dao_macros::{active_query, voting_module_query}; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; @@ -55,7 +55,7 @@ pub enum ExecuteMsg { /// length. Unstake { token_ids: Vec }, /// Claim NFTs that have been unstaked for the specified duration. - ClaimNfts {}, + ClaimNfts { r#type: ClaimType }, /// Updates the contract configuration, namely unstaking duration. /// Only callable by the DAO that initialized this voting contract. UpdateConfig { duration: Option }, @@ -72,6 +72,16 @@ pub enum ExecuteMsg { }, } +#[cw_serde] +pub enum ClaimType { + /// Claims all legacy claims. + Legacy, + /// Claims all non-legacy claims. + All, + /// Claims specific non-legacy NFTs. + Specific(Vec), +} + #[active_query] #[voting_module_query] #[cw_serde] @@ -79,8 +89,12 @@ pub enum ExecuteMsg { pub enum QueryMsg { #[returns(crate::state::Config)] Config {}, - #[returns(::cw721_controllers::NftClaimsResponse)] - NftClaims { address: String }, + #[returns(NftClaimsResponse)] + NftClaims { + address: String, + start_after: Option, + limit: Option, + }, #[returns(::cw_controllers::HooksResponse)] Hooks {}, // List the staked NFTs for a given address. @@ -94,5 +108,20 @@ pub enum QueryMsg { ActiveThreshold {}, } +#[cw_serde] +pub struct NftClaimsResponse { + pub nft_claims: Vec, +} + +#[cw_serde] +pub struct NftClaim { + /// The token ID of the NFT being claimed. + pub token_id: String, + /// The expiration time of the claim. + pub release_at: Expiration, + /// Whether the claim is a legacy claim. + pub legacy: bool, +} + #[cw_serde] pub struct MigrateMsg {} diff --git a/contracts/voting/dao-voting-cw721-staked/src/state.rs b/contracts/voting/dao-voting-cw721-staked/src/state.rs index 0d8e8b62f..99341499a 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/state.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/state.rs @@ -42,9 +42,14 @@ pub const TOTAL_STAKED_NFTS: SnapshotItem = SnapshotItem::new( Strategy::EveryBlock, ); -/// The maximum number of claims that may be outstanding. -pub const MAX_CLAIMS: u64 = 70; -pub const NFT_CLAIMS: NftClaims = NftClaims::new("nft_claims"); +/// The legacy NFT claims storage uses a non-paginatable vector, which limits +/// the number of claims that may be outstanding. This is horrible UX, +/// especially for large NFT collections. To allow DAOs to upgrade, we must keep +/// the legacy NFT claims storage, but we can paginate the new storage. +pub const LEGACY_NFT_CLAIMS: cw721_controllers_v250::NftClaims = + cw721_controllers_v250::NftClaims::new("nft_claims"); + +pub const NFT_CLAIMS: NftClaims = NftClaims::new("nc"); // Hooks to contracts that will receive staking and unstaking // messages. diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs index e5c587564..bc81a2119 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/adversarial.rs @@ -1,14 +1,10 @@ use cosmwasm_std::Uint128; use cw_multi_test::next_block; -use cw_utils::Duration; - -use crate::{ - state::MAX_CLAIMS, - testing::{ - execute::{stake_nft, unstake_nfts}, - instantiate::instantiate_cw721_base, - queries::query_voting_power, - }, + +use crate::testing::{ + execute::{stake_nft, unstake_nfts}, + instantiate::instantiate_cw721_base, + queries::query_voting_power, }; use super::{ @@ -145,31 +141,3 @@ fn test_query_the_future() -> anyhow::Result<()> { Ok(()) } - -/// I can not unstake more than one NFT in a TX in order to bypass the -/// MAX_CLAIMS limit. -#[test] -fn test_bypass_max_claims() -> anyhow::Result<()> { - let CommonTest { - mut app, - module, - nft, - } = setup_test(Some(Duration::Height(1))); - let mut to_stake = vec![]; - for i in 1..(MAX_CLAIMS + 10) { - let i_str = &i.to_string(); - mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, i_str)?; - if i < MAX_CLAIMS { - // unstake MAX_CLAMS - 1 NFTs - unstake_nfts(&mut app, &module, CREATOR_ADDR, &[i_str])?; - } else { - // push rest of NFT ids to vec - to_stake.push(i_str.clone()); - } - } - let binding = to_stake.iter().map(|s| s.as_str()).collect::>(); - let to_stake_slice: &[&str] = binding.as_slice(); - let res = unstake_nfts(&mut app, &module, CREATOR_ADDR, to_stake_slice); - is_error!(res => "Too many outstanding claims. Claim some tokens before unstaking more."); - Ok(()) -} diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs index dd5b60877..acfbeea1c 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/execute.rs @@ -5,7 +5,7 @@ use cw_multi_test::{App, AppResponse, Executor}; use anyhow::Result as AnyResult; use cw_utils::Duration; -use crate::msg::ExecuteMsg; +use crate::msg::{ClaimType, ExecuteMsg}; // Shorthand for an unchecked address. macro_rules! addr { @@ -110,7 +110,36 @@ pub fn claim_nfts(app: &mut App, module: &Addr, sender: &str) -> AnyResult AnyResult { + app.execute_contract( + addr!(sender), + module.clone(), + &ExecuteMsg::ClaimNfts { + r#type: ClaimType::Specific(token_ids.to_vec()), + }, + &[], + ) +} + +pub fn claim_legacy_nfts(app: &mut App, module: &Addr, sender: &str) -> AnyResult { + app.execute_contract( + addr!(sender), + module.clone(), + &ExecuteMsg::ClaimNfts { + r#type: ClaimType::Legacy, + }, &[], ) } diff --git a/contracts/voting/dao-voting-cw721-staked/src/testing/queries.rs b/contracts/voting/dao-voting-cw721-staked/src/testing/queries.rs index 946d29af7..8807538b5 100644 --- a/contracts/voting/dao-voting-cw721-staked/src/testing/queries.rs +++ b/contracts/voting/dao-voting-cw721-staked/src/testing/queries.rs @@ -1,12 +1,14 @@ use cosmwasm_std::{Addr, StdResult, Uint128}; -use cw721_controllers::NftClaimsResponse; use cw_controllers::HooksResponse; use cw_multi_test::App; use dao_interface::voting::{ InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; -use crate::{msg::QueryMsg, state::Config}; +use crate::{ + msg::{NftClaimsResponse, QueryMsg}, + state::Config, +}; pub fn query_config(app: &App, module: &Addr) -> StdResult { let config = app.wrap().query_wasm_smart(module, &QueryMsg::Config {})?; @@ -18,6 +20,8 @@ pub fn query_claims(app: &App, module: &Addr, addr: &str) -> StdResult anyhow::Result<()> { NftClaimsResponse { nft_claims: vec![NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }] } ); @@ -214,7 +220,8 @@ fn test_update_config() -> anyhow::Result<()> { NftClaimsResponse { nft_claims: vec![NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }] } ); @@ -229,11 +236,13 @@ fn test_update_config() -> anyhow::Result<()> { nft_claims: vec![ NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }, NftClaim { token_id: "2".to_string(), - release_at: Duration::Time(1).after(&app.block_info()) + release_at: Duration::Time(1).after(&app.block_info()), + legacy: false, } ] } @@ -284,7 +293,8 @@ fn test_claims() -> anyhow::Result<()> { claims.nft_claims, vec![NftClaim { token_id: "2".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, }] ); @@ -301,24 +311,169 @@ fn test_claims() -> anyhow::Result<()> { Ok(()) } -// I can not have more than MAX_CLAIMS claims pending. +// I can query and claim my pending legacy claims and non-legacy claims. #[test] -fn test_max_claims() -> anyhow::Result<()> { +pub fn test_legacy_claims_work() -> anyhow::Result<()> { let CommonTest { mut app, module, nft, } = setup_test(Some(Duration::Height(1))); - for i in 0..MAX_CLAIMS { - let i_str = &i.to_string(); - mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, i_str)?; - unstake_nfts(&mut app, &module, CREATOR_ADDR, &[i_str])?; - } + mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "1")?; + mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "2")?; + mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "3")?; + mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "4")?; + mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "5")?; + + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!(claims.nft_claims, vec![]); + + let res = claim_legacy_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + + // insert legacy claims manually + + // taken from cw-multi-test's WasmKeeper::contract_storage in wasm.rs + let mut module_namespace = b"contract_data/".to_vec(); + module_namespace.extend_from_slice(module.as_bytes()); + let prefix = to_length_prefixed_nested(&[b"wasm", &module_namespace]); + let key = Map::<&Addr, Vec>::new("nft_claims").key(&Addr::unchecked(CREATOR_ADDR)); + let mut legacy_nft_claims_key = prefix; + legacy_nft_claims_key.extend_from_slice(&key); + + let block = app.block_info(); + app.storage_mut().set( + &legacy_nft_claims_key, + &to_json_vec(&vec![cw721_controllers_v250::NftClaim { + token_id: "4".to_string(), + release_at: Duration::Height(1).after(&block), + }]) + .unwrap(), + ); + + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "4".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: true, + }] + ); + + // claim now exists, but is not yet expired. Nothing to claim. + let res = claim_legacy_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + + app.update_block(next_block); + + // no non-legacy claims + let res = claim_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + + // legacy claim works + claim_legacy_nfts(&mut app, &module, CREATOR_ADDR).unwrap(); + let owner = query_nft_owner(&app, &nft, "4")?; + assert_eq!(owner.owner, CREATOR_ADDR.to_string()); + + // unstake non-legacy + unstake_nfts(&mut app, &module, CREATOR_ADDR, &["2"])?; - mint_and_stake_nft(&mut app, &nft, &module, CREATOR_ADDR, "a")?; - let res = unstake_nfts(&mut app, &module, CREATOR_ADDR, &["a"]); - is_error!(res => "Too many outstanding claims. Claim some tokens before unstaking more."); + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "2".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, + }] + ); + + // Claim now exists, but is not yet expired. Nothing to claim. + let res = claim_legacy_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + + app.update_block(next_block); + + // no legacy claims + let res = claim_legacy_nfts(&mut app, &module, CREATOR_ADDR); + is_error!(res => "Nothing to claim"); + + claim_nfts(&mut app, &module, CREATOR_ADDR)?; + + let owner = query_nft_owner(&app, &nft, "2")?; + assert_eq!(owner.owner, CREATOR_ADDR.to_string()); + + // unstake another non-legacy + unstake_nfts(&mut app, &module, CREATOR_ADDR, &["3"])?; + + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "3".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, + }] + ); + + app.update_block(next_block); + + claim_specific_nfts(&mut app, &module, CREATOR_ADDR, &["3".to_string()])?; + let owner = query_nft_owner(&app, &nft, "3")?; + assert_eq!(owner.owner, CREATOR_ADDR.to_string()); + + // unstake legacy + let block = app.block_info(); + app.storage_mut().set( + &legacy_nft_claims_key, + &to_json_vec(&vec![cw721_controllers_v250::NftClaim { + token_id: "5".to_string(), + release_at: Duration::Height(1).after(&block), + }]) + .unwrap(), + ); + // unstake non-legacy + unstake_nfts(&mut app, &module, CREATOR_ADDR, &["1"])?; + + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!( + claims.nft_claims, + vec![ + NftClaim { + token_id: "5".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: true, + }, + NftClaim { + token_id: "1".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, + } + ] + ); + + app.update_block(next_block); + + // both claims should be ready to claim + claim_legacy_nfts(&mut app, &module, CREATOR_ADDR)?; + claim_nfts(&mut app, &module, CREATOR_ADDR)?; + + let owner = query_nft_owner(&app, &nft, "1")?; + assert_eq!(owner.owner, CREATOR_ADDR.to_string()); + let owner = query_nft_owner(&app, &nft, "5")?; + assert_eq!(owner.owner, CREATOR_ADDR.to_string()); + + // no claims left + let claims = query_claims(&app, &module, CREATOR_ADDR)?; + assert_eq!(claims.nft_claims, vec![]); Ok(()) } diff --git a/contracts/voting/dao-voting-onft-staked/Cargo.toml b/contracts/voting/dao-voting-onft-staked/Cargo.toml index 9c96ec2d7..eaf1f53fa 100644 --- a/contracts/voting/dao-voting-onft-staked/Cargo.toml +++ b/contracts/voting/dao-voting-onft-staked/Cargo.toml @@ -33,6 +33,7 @@ cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } cw-hooks = { workspace = true } cw721-controllers = { workspace = true } +cw721-controllers-v250 = { workspace = true } cw-utils = { workspace = true } cw2 = { workspace = true } dao-dao-macros = { workspace = true } diff --git a/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json b/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json index ede91d3be..1aec3afc9 100644 --- a/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json +++ b/contracts/voting/dao-voting-onft-staked/schema/dao-voting-onft-staked.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-onft-staked", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -282,6 +282,14 @@ "properties": { "claim_nfts": { "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "$ref": "#/definitions/ClaimType" + } + }, "additionalProperties": false } }, @@ -434,6 +442,40 @@ } ] }, + "ClaimType": { + "oneOf": [ + { + "description": "Claims all legacy claims.", + "type": "string", + "enum": [ + "legacy" + ] + }, + { + "description": "Claims all non-legacy claims.", + "type": "string", + "enum": [ + "all" + ] + }, + { + "description": "Claims specific non-legacy NFTs.", + "type": "object", + "required": [ + "specific" + ], + "properties": { + "specific": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + ] + }, "Decimal": { "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" @@ -509,6 +551,20 @@ "properties": { "address": { "type": "string" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false @@ -943,14 +999,25 @@ "NftClaim": { "type": "object", "required": [ + "legacy", "release_at", "token_id" ], "properties": { + "legacy": { + "description": "Whether the claim is a legacy claim.", + "type": "boolean" + }, "release_at": { - "$ref": "#/definitions/Expiration" + "description": "The expiration time of the claim.", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] }, "token_id": { + "description": "The token ID of the NFT being claimed.", "type": "string" } }, diff --git a/contracts/voting/dao-voting-onft-staked/src/contract.rs b/contracts/voting/dao-voting-onft-staked/src/contract.rs index 2ab1ebd73..2ac9b87ba 100644 --- a/contracts/voting/dao-voting-onft-staked/src/contract.rs +++ b/contracts/voting/dao-voting-onft-staked/src/contract.rs @@ -15,11 +15,15 @@ use dao_voting::threshold::{ ActiveThresholdResponse, }; -use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, OnftCollection, QueryMsg}; +use crate::msg::{ + ClaimType, ExecuteMsg, InstantiateMsg, MigrateMsg, NftClaim, NftClaimsResponse, OnftCollection, + QueryMsg, +}; use crate::omniflix::{get_onft_transfer_msg, query_onft_owner, query_onft_supply}; use crate::state::{ register_staked_nfts, register_unstaked_nfts, Config, ACTIVE_THRESHOLD, CONFIG, DAO, HOOKS, - MAX_CLAIMS, NFT_BALANCES, NFT_CLAIMS, PREPARED_ONFTS, STAKED_NFTS_PER_OWNER, TOTAL_STAKED_NFTS, + LEGACY_NFT_CLAIMS, NFT_BALANCES, NFT_CLAIMS, PREPARED_ONFTS, STAKED_NFTS_PER_OWNER, + TOTAL_STAKED_NFTS, }; use crate::ContractError; @@ -97,7 +101,7 @@ pub fn execute( recipient, } => execute_cancel_stake(deps, env, info, token_ids, recipient), ExecuteMsg::Unstake { token_ids } => execute_unstake(deps, env, info, token_ids), - ExecuteMsg::ClaimNfts {} => execute_claim_nfts(deps, env, info), + ExecuteMsg::ClaimNfts { r#type } => execute_claim_nfts(deps, env, info, r#type), ExecuteMsg::UpdateConfig { duration } => execute_update_config(info, deps, duration), ExecuteMsg::AddHook { addr } => execute_add_hook(deps, info, addr), ExecuteMsg::RemoveHook { addr } => execute_remove_hook(deps, info, addr), @@ -371,13 +375,6 @@ pub fn execute_unstake( } Some(duration) => { - let outstanding_claims = NFT_CLAIMS - .query_claims(deps.as_ref(), &info.sender)? - .nft_claims; - if outstanding_claims.len() + token_ids.len() > MAX_CLAIMS as usize { - return Err(ContractError::TooManyClaims {}); - } - // Out of gas here is fine - just try again with fewer // tokens. NFT_CLAIMS.create_nft_claims( @@ -400,20 +397,50 @@ pub fn execute_claim_nfts( deps: DepsMut, env: Env, info: MessageInfo, + claim_type: ClaimType, ) -> Result { - let nfts = NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)?; - if nfts.is_empty() { + let token_ids = match claim_type { + // attempt to claim all legacy NFTs + ClaimType::Legacy => { + LEGACY_NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &env.block)? + } + // attempt to claim all non-legacy NFTs + ClaimType::All => { + let token_ids = NFT_CLAIMS + .query_claims(deps.as_ref(), &info.sender, None, None)? + .into_iter() + .filter(|nft| nft.release_at.is_expired(&env.block)) + .map(|nft| nft.token_id) + .collect::>(); + + if !token_ids.is_empty() { + NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; + } + + token_ids + } + // attempt to claim specific non-legacy NFTs + ClaimType::Specific(token_ids) => { + if !token_ids.is_empty() { + NFT_CLAIMS.claim_nfts(deps.storage, &info.sender, &token_ids, &env.block)?; + } + + token_ids + } + }; + + if token_ids.is_empty() { return Err(ContractError::NothingToClaim {}); } let config = CONFIG.load(deps.storage)?; - let msgs = nfts + let msgs = token_ids .into_iter() - .map(|nft| -> CosmosMsg { + .map(|token_id| -> CosmosMsg { get_onft_transfer_msg( &config.onft_collection_id, - &nft, + &token_id, env.contract.address.as_str(), info.sender.as_str(), ) @@ -534,7 +561,11 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { QueryMsg::Dao {} => query_dao(deps), QueryMsg::Info {} => query_info(deps), QueryMsg::IsActive {} => query_is_active(deps, env), - QueryMsg::NftClaims { address } => query_nft_claims(deps, address), + QueryMsg::NftClaims { + address, + start_after, + limit, + } => query_nft_claims(deps, address, start_after, limit), QueryMsg::Hooks {} => query_hooks(deps), QueryMsg::StakedNfts { address, @@ -652,8 +683,41 @@ pub fn query_dao(deps: Deps) -> StdResult { to_json_binary(&dao) } -pub fn query_nft_claims(deps: Deps, address: String) -> StdResult { - to_json_binary(&NFT_CLAIMS.query_claims(deps, &deps.api.addr_validate(&address)?)?) +pub fn query_nft_claims( + deps: Deps, + address: String, + start_after: Option, + limit: Option, +) -> StdResult { + let addr = deps.api.addr_validate(&address)?; + + // load all legacy claims since it does not support pagination + let legacy_claims = LEGACY_NFT_CLAIMS + .query_claims(deps, &addr)? + .nft_claims + .into_iter() + .map(|c| NftClaim { + token_id: c.token_id, + release_at: c.release_at, + legacy: true, + }) + .collect::>(); + + // paginate all new claims + let claims = NFT_CLAIMS + .query_claims(deps, &addr, start_after.as_ref(), limit)? + .into_iter() + .map(|c| NftClaim { + token_id: c.token_id, + release_at: c.release_at, + legacy: false, + }) + .collect::>(); + + // combine legacy and new claims + let nft_claims = legacy_claims.into_iter().chain(claims).collect(); + + to_json_binary(&NftClaimsResponse { nft_claims }) } pub fn query_hooks(deps: Deps) -> StdResult { diff --git a/contracts/voting/dao-voting-onft-staked/src/error.rs b/contracts/voting/dao-voting-onft-staked/src/error.rs index 252e8caf4..a57d785b5 100644 --- a/contracts/voting/dao-voting-onft-staked/src/error.rs +++ b/contracts/voting/dao-voting-onft-staked/src/error.rs @@ -16,6 +16,9 @@ pub enum ContractError { #[error(transparent)] UnstakingDurationError(#[from] dao_voting::duration::UnstakingDurationError), + #[error(transparent)] + NftClaimError(#[from] cw721_controllers::NftClaimError), + #[error("Nothing to claim")] NothingToClaim {}, @@ -34,9 +37,6 @@ pub enum ContractError { #[error("Can not unstake that which you have not staked (unstaking {token_id})")] NotStaked { token_id: String }, - #[error("Too many outstanding claims. Claim some tokens before unstaking more.")] - TooManyClaims {}, - #[error("Unauthorized")] Unauthorized {}, diff --git a/contracts/voting/dao-voting-onft-staked/src/msg.rs b/contracts/voting/dao-voting-onft-staked/src/msg.rs index 08e589bc5..0970ea266 100644 --- a/contracts/voting/dao-voting-onft-staked/src/msg.rs +++ b/contracts/voting/dao-voting-onft-staked/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cw_utils::Duration; +use cw_utils::{Duration, Expiration}; use dao_dao_macros::{active_query, voting_module_query}; use dao_voting::threshold::{ActiveThreshold, ActiveThresholdResponse}; @@ -76,7 +76,7 @@ pub enum ExecuteMsg { /// have unique values and have non-zero length. Unstake { token_ids: Vec }, /// Claim NFTs that have been unstaked for the specified duration. - ClaimNfts {}, + ClaimNfts { r#type: ClaimType }, /// Updates the contract configuration, namely unstaking duration. Only /// callable by the DAO that initialized this voting contract. UpdateConfig { duration: Option }, @@ -93,6 +93,16 @@ pub enum ExecuteMsg { }, } +#[cw_serde] +pub enum ClaimType { + /// Claims all legacy claims. + Legacy, + /// Claims all non-legacy claims. + All, + /// Claims specific non-legacy NFTs. + Specific(Vec), +} + #[active_query] #[voting_module_query] #[cw_serde] @@ -100,8 +110,12 @@ pub enum ExecuteMsg { pub enum QueryMsg { #[returns(crate::state::Config)] Config {}, - #[returns(::cw721_controllers::NftClaimsResponse)] - NftClaims { address: String }, + #[returns(NftClaimsResponse)] + NftClaims { + address: String, + start_after: Option, + limit: Option, + }, #[returns(::cw_controllers::HooksResponse)] Hooks {}, // List the staked NFTs for a given address. @@ -115,5 +129,20 @@ pub enum QueryMsg { ActiveThreshold {}, } +#[cw_serde] +pub struct NftClaimsResponse { + pub nft_claims: Vec, +} + +#[cw_serde] +pub struct NftClaim { + /// The token ID of the NFT being claimed. + pub token_id: String, + /// The expiration time of the claim. + pub release_at: Expiration, + /// Whether the claim is a legacy claim. + pub legacy: bool, +} + #[cw_serde] pub struct MigrateMsg {} diff --git a/contracts/voting/dao-voting-onft-staked/src/state.rs b/contracts/voting/dao-voting-onft-staked/src/state.rs index 0e85822ab..0fab86f74 100644 --- a/contracts/voting/dao-voting-onft-staked/src/state.rs +++ b/contracts/voting/dao-voting-onft-staked/src/state.rs @@ -48,9 +48,14 @@ pub const TOTAL_STAKED_NFTS: SnapshotItem = SnapshotItem::new( Strategy::EveryBlock, ); -/// The maximum number of claims that may be outstanding. -pub const MAX_CLAIMS: u64 = 70; -pub const NFT_CLAIMS: NftClaims = NftClaims::new("nft_claims"); +/// The legacy NFT claims storage uses a non-paginatable vector, which limits +/// the number of claims that may be outstanding. This is horrible UX, +/// especially for large NFT collections. To allow DAOs to upgrade, we must keep +/// the legacy NFT claims storage, but we can paginate the new storage. +pub const LEGACY_NFT_CLAIMS: cw721_controllers_v250::NftClaims = + cw721_controllers_v250::NftClaims::new("nft_claims"); + +pub const NFT_CLAIMS: NftClaims = NftClaims::new("nc"); // Hooks to contracts that will receive staking and unstaking // messages. diff --git a/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs b/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs index fd79fd25e..cff409ccf 100644 --- a/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs +++ b/contracts/voting/dao-voting-onft-staked/src/testing/execute.rs @@ -1,4 +1,4 @@ -use crate::msg::ExecuteMsg; +use crate::msg::{ClaimType, ExecuteMsg}; use anyhow::Result as AnyResult; use cosmwasm_std::Addr; use cw_multi_test::AppResponse; @@ -198,7 +198,40 @@ pub fn claim_nfts(app: &mut OmniflixApp, module: &Addr, sender: &str) -> AnyResu app.execute_contract( addr!(sender), module.clone(), - &ExecuteMsg::ClaimNfts {}, + &ExecuteMsg::ClaimNfts { + r#type: ClaimType::All, + }, + &[], + ) +} + +pub fn claim_specific_nfts( + app: &mut OmniflixApp, + module: &Addr, + sender: &str, + token_ids: &[String], +) -> AnyResult { + app.execute_contract( + addr!(sender), + module.clone(), + &ExecuteMsg::ClaimNfts { + r#type: ClaimType::Specific(token_ids.to_vec()), + }, + &[], + ) +} + +pub fn claim_legacy_nfts( + app: &mut OmniflixApp, + module: &Addr, + sender: &str, +) -> AnyResult { + app.execute_contract( + addr!(sender), + module.clone(), + &ExecuteMsg::ClaimNfts { + r#type: ClaimType::Legacy, + }, &[], ) } diff --git a/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs b/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs index 3ff1facde..50789e2b5 100644 --- a/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs +++ b/contracts/voting/dao-voting-onft-staked/src/testing/queries.rs @@ -1,12 +1,14 @@ use cosmwasm_std::{Addr, StdResult, Uint128}; -use cw721_controllers::NftClaimsResponse; use cw_controllers::HooksResponse; use dao_interface::voting::{ InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, }; use omniflix_std::types::omniflix::onft::v1beta1::{QueryOnftRequest, QueryOnftResponse}; -use crate::{msg::QueryMsg, state::Config}; +use crate::{ + msg::{NftClaimsResponse, QueryMsg}, + state::Config, +}; use super::app::OmniflixApp; @@ -20,6 +22,8 @@ pub fn query_claims(app: &OmniflixApp, module: &Addr, addr: &str) -> StdResult anyhow::Result<()> { NftClaimsResponse { nft_claims: vec![NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }] } ); @@ -206,7 +210,8 @@ fn test_update_config() -> anyhow::Result<()> { NftClaimsResponse { nft_claims: vec![NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }] } ); @@ -221,11 +226,13 @@ fn test_update_config() -> anyhow::Result<()> { nft_claims: vec![ NftClaim { token_id: "1".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 3), + legacy: false, }, NftClaim { token_id: "2".to_string(), - release_at: Duration::Time(1).after(&app.block_info()) + release_at: Duration::Time(1).after(&app.block_info()), + legacy: false, } ] } @@ -277,7 +284,8 @@ fn test_claims() -> anyhow::Result<()> { claims.nft_claims, vec![NftClaim { token_id: "2".to_string(), - release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1) + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, }] ); @@ -294,9 +302,9 @@ fn test_claims() -> anyhow::Result<()> { Ok(()) } -// I can not have more than MAX_CLAIMS claims pending. +// I can query and claim my pending legacy claims and non-legacy claims. #[test] -fn test_max_claims() -> anyhow::Result<()> { +pub fn test_legacy_claims_work() -> anyhow::Result<()> { let CommonTest { mut app, module, @@ -304,15 +312,160 @@ fn test_max_claims() -> anyhow::Result<()> { .. } = setup_test(Some(Duration::Height(1)), None); - for i in 0..MAX_CLAIMS { - let i_str = &i.to_string(); - mint_and_stake_nft(&mut app, &nft, &module, STAKER, i_str)?; - unstake_nfts(&mut app, &module, STAKER, &[i_str])?; - } + mint_and_stake_nft(&mut app, &nft, &module, STAKER, "1")?; + mint_and_stake_nft(&mut app, &nft, &module, STAKER, "2")?; + mint_and_stake_nft(&mut app, &nft, &module, STAKER, "3")?; + mint_and_stake_nft(&mut app, &nft, &module, STAKER, "4")?; + mint_and_stake_nft(&mut app, &nft, &module, STAKER, "5")?; + + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!(claims.nft_claims, vec![]); + + let res = claim_legacy_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + + // insert legacy claims manually + + // taken from cw-multi-test's WasmKeeper::contract_storage in wasm.rs + let mut module_namespace = b"contract_data/".to_vec(); + module_namespace.extend_from_slice(module.as_bytes()); + let prefix = to_length_prefixed_nested(&[b"wasm", &module_namespace]); + let key = Map::<&Addr, Vec>::new("nft_claims").key(&Addr::unchecked(STAKER)); + let mut legacy_nft_claims_key = prefix; + legacy_nft_claims_key.extend_from_slice(&key); + + let block = app.block_info(); + app.storage_mut().set( + &legacy_nft_claims_key, + &to_json_vec(&vec![cw721_controllers_v250::NftClaim { + token_id: "4".to_string(), + release_at: Duration::Height(1).after(&block), + }]) + .unwrap(), + ); + + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "4".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: true, + }] + ); + + // claim now exists, but is not yet expired. Nothing to claim. + let res = claim_legacy_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + + app.update_block(next_block); + + // no non-legacy claims + let res = claim_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + + // legacy claim works + claim_legacy_nfts(&mut app, &module, STAKER).unwrap(); + let owner = query_nft_owner(&app, &nft, "4")?; + assert_eq!(owner, STAKER.to_string()); + + // unstake non-legacy + unstake_nfts(&mut app, &module, STAKER, &["2"])?; + + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "2".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, + }] + ); + + // Claim now exists, but is not yet expired. Nothing to claim. + let res = claim_legacy_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + let res = claim_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + + app.update_block(next_block); + + // no legacy claims + let res = claim_legacy_nfts(&mut app, &module, STAKER); + is_error!(res => "Nothing to claim"); + + claim_nfts(&mut app, &module, STAKER)?; + + let owner = query_nft_owner(&app, &nft, "2")?; + assert_eq!(owner, STAKER.to_string()); + + // unstake another non-legacy + unstake_nfts(&mut app, &module, STAKER, &["3"])?; - mint_and_stake_nft(&mut app, &nft, &module, STAKER, "a")?; - let res = unstake_nfts(&mut app, &module, STAKER, &["a"]); - is_error!(res => "Too many outstanding claims. Claim some tokens before unstaking more."); + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!( + claims.nft_claims, + vec![NftClaim { + token_id: "3".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, + }] + ); + + app.update_block(next_block); + + claim_specific_nfts(&mut app, &module, STAKER, &["3".to_string()])?; + let owner = query_nft_owner(&app, &nft, "3")?; + assert_eq!(owner, STAKER.to_string()); + + // unstake legacy + let block = app.block_info(); + app.storage_mut().set( + &legacy_nft_claims_key, + &to_json_vec(&vec![cw721_controllers_v250::NftClaim { + token_id: "5".to_string(), + release_at: Duration::Height(1).after(&block), + }]) + .unwrap(), + ); + // unstake non-legacy + unstake_nfts(&mut app, &module, STAKER, &["1"])?; + + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!( + claims.nft_claims, + vec![ + NftClaim { + token_id: "5".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: true, + }, + NftClaim { + token_id: "1".to_string(), + release_at: cw_utils::Expiration::AtHeight(app.block_info().height + 1), + legacy: false, + } + ] + ); + + app.update_block(next_block); + + // both claims should be ready to claim + claim_legacy_nfts(&mut app, &module, STAKER)?; + claim_nfts(&mut app, &module, STAKER)?; + + let owner = query_nft_owner(&app, &nft, "1")?; + assert_eq!(owner, STAKER.to_string()); + let owner = query_nft_owner(&app, &nft, "5")?; + assert_eq!(owner, STAKER.to_string()); + + // no claims left + let claims = query_claims(&app, &module, STAKER)?; + assert_eq!(claims.nft_claims, vec![]); Ok(()) } @@ -987,35 +1140,6 @@ fn test_query_the_future() -> anyhow::Result<()> { Ok(()) } -/// I can not unstake more than one NFT in a TX in order to bypass the -/// MAX_CLAIMS limit. -#[test] -fn test_bypass_max_claims() -> anyhow::Result<()> { - let CommonTest { - mut app, - module, - nft, - .. - } = setup_test(Some(Duration::Height(1)), None); - let mut to_stake = vec![]; - for i in 1..(MAX_CLAIMS + 10) { - let i_str = &i.to_string(); - mint_and_stake_nft(&mut app, &nft, &module, STAKER, i_str)?; - if i < MAX_CLAIMS { - // unstake MAX_CLAMS - 1 NFTs - unstake_nfts(&mut app, &module, STAKER, &[i_str])?; - } else { - // push rest of NFT ids to vec - to_stake.push(i_str.clone()); - } - } - let binding = to_stake.iter().map(|s| s.as_str()).collect::>(); - let to_stake_slice: &[&str] = binding.as_slice(); - let res = unstake_nfts(&mut app, &module, STAKER, to_stake_slice); - is_error!(res => "Too many outstanding claims. Claim some tokens before unstaking more."); - Ok(()) -} - /// I can cancel my own prepared stake. #[test] fn test_preparer_cancel_prepared_stake() -> anyhow::Result<()> { diff --git a/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json index 68d20647d..da1db7de1 100644 --- a/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json +++ b/contracts/voting/dao-voting-token-staked/schema/dao-voting-token-staked.json @@ -1,6 +1,6 @@ { "contract_name": "dao-voting-token-staked", - "contract_version": "2.5.1", + "contract_version": "2.6.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/packages/cw721-controllers/src/lib.rs b/packages/cw721-controllers/src/lib.rs index d4f58091a..97c53ffe0 100644 --- a/packages/cw721-controllers/src/lib.rs +++ b/packages/cw721-controllers/src/lib.rs @@ -2,4 +2,4 @@ mod nft_claim; -pub use nft_claim::{NftClaim, NftClaims, NftClaimsResponse}; +pub use nft_claim::{NftClaim, NftClaimError, NftClaims}; diff --git a/packages/cw721-controllers/src/nft_claim.rs b/packages/cw721-controllers/src/nft_claim.rs index 4b8702f9a..1feb4f3a1 100644 --- a/packages/cw721-controllers/src/nft_claim.rs +++ b/packages/cw721-controllers/src/nft_claim.rs @@ -1,11 +1,19 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, BlockInfo, CustomQuery, Deps, StdResult, Storage}; -use cw_storage_plus::Map; +use cosmwasm_std::{Addr, BlockInfo, CustomQuery, Deps, Order, StdError, StdResult, Storage}; +use cw_storage_plus::{Bound, Map}; use cw_utils::Expiration; +use thiserror::Error; -#[cw_serde] -pub struct NftClaimsResponse { - pub nft_claims: Vec, +#[derive(Error, Debug, PartialEq)] +pub enum NftClaimError { + #[error(transparent)] + Std(#[from] StdError), + + #[error("NFT claim not found for {token_id}")] + NotFound { token_id: String }, + + #[error("NFT with ID {token_id} is not ready to be claimed")] + NotReady { token_id: String }, } #[cw_serde] @@ -15,22 +23,22 @@ pub struct NftClaim { } impl NftClaim { - pub fn new(token_id: String, released: Expiration) -> Self { + pub fn new(token_id: String, release_at: Expiration) -> Self { NftClaim { token_id, - release_at: released, + release_at, } } } -pub struct NftClaims<'a>(Map<'a, &'a Addr, Vec>); +pub struct NftClaims<'a>(Map<'a, (&'a Addr, &'a String), Expiration>); impl<'a> NftClaims<'a> { pub const fn new(storage_key: &'a str) -> Self { NftClaims(Map::new(storage_key)) } - /// Creates a number of NFT claims simeltaniously for a given + /// Creates a number of NFT claims simultaneously for a given /// address. /// /// # Invariants @@ -46,52 +54,70 @@ impl<'a> NftClaims<'a> { token_ids: Vec, release_at: Expiration, ) -> StdResult<()> { - self.0.update(storage, addr, |old| -> StdResult<_> { - Ok(old - .unwrap_or_default() - .into_iter() - .chain(token_ids.into_iter().map(|token_id| NftClaim { - token_id, - release_at, - })) - .collect::>()) - })?; + token_ids + .into_iter() + .map(|token_id| self.0.save(storage, (addr, &token_id), &release_at)) + .collect::>>()?; Ok(()) } - /// This iterates over all mature claims for the address, and removes them, up to an optional cap. - /// it removes the finished claims and returns the total amount of tokens to be released. + /// This iterates over all claims for the given IDs, removing them if they + /// are all mature and erroring if any is not. pub fn claim_nfts( &self, storage: &mut dyn Storage, addr: &Addr, + token_ids: &[String], block: &BlockInfo, - ) -> StdResult> { - let mut to_send = vec![]; - self.0.update(storage, addr, |nft_claims| -> StdResult<_> { - let (_send, waiting): (Vec<_>, _) = - nft_claims.unwrap_or_default().into_iter().partition(|c| { - // if mature and we can pay fully, then include in _send - if c.release_at.is_expired(block) { - to_send.push(c.token_id.clone()); - true - } else { - // not to send, leave in waiting and save again - false + ) -> Result<(), NftClaimError> { + token_ids + .iter() + .map(|token_id| -> Result<(), NftClaimError> { + match self.0.may_load(storage, (addr, token_id)) { + Ok(Some(expiration)) => { + // if claim is expired, remove it and continue + if expiration.is_expired(block) { + self.0.remove(storage, (addr, token_id)); + Ok(()) + } else { + // if claim is not expired, error + Err(NftClaimError::NotReady { + token_id: token_id.to_string(), + }) + } } - }); - Ok(waiting) - })?; - Ok(to_send) + // if claim is not found, error + Ok(None) => Err(NftClaimError::NotFound { + token_id: token_id.clone(), + }), + Err(e) => Err(e.into()), + } + }) + .collect::, NftClaimError>>() + .map(|_| ()) } pub fn query_claims( &self, deps: Deps, address: &Addr, - ) -> StdResult { - let nft_claims = self.0.may_load(deps.storage, address)?.unwrap_or_default(); - Ok(NftClaimsResponse { nft_claims }) + start_after: Option<&String>, + limit: Option, + ) -> StdResult> { + let limit = limit.map(|l| l as usize).unwrap_or(usize::MAX); + let start = start_after.map(Bound::<&String>::exclusive); + + self.0 + .prefix(address) + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| { + item.map(|(token_id, release_at)| NftClaim { + token_id, + release_at, + }) + }) + .collect() } } @@ -147,11 +173,13 @@ mod test { // Assert that claims creates a map and there is one claim for the address. let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); assert_eq!(saved_claims.len(), 1); - assert_eq!(saved_claims[0].token_id, TEST_BAYC_TOKEN_ID.to_string()); - assert_eq!(saved_claims[0].release_at, TEST_EXPIRATION); + assert_eq!(saved_claims[0].0, TEST_BAYC_TOKEN_ID.to_string()); + assert_eq!(saved_claims[0].1, TEST_EXPIRATION); // Adding another claim to same address, make sure that both claims are saved. claims @@ -166,16 +194,15 @@ mod test { // Assert that both claims exist for the address. let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); assert_eq!(saved_claims.len(), 2); - assert_eq!(saved_claims[0].token_id, TEST_BAYC_TOKEN_ID.to_string()); - assert_eq!(saved_claims[0].release_at, TEST_EXPIRATION); - assert_eq!( - saved_claims[1].token_id, - TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() - ); - assert_eq!(saved_claims[1].release_at, TEST_EXPIRATION); + assert_eq!(saved_claims[0].0, TEST_BAYC_TOKEN_ID.to_string()); + assert_eq!(saved_claims[0].1, TEST_EXPIRATION); + assert_eq!(saved_claims[1].0, TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()); + assert_eq!(saved_claims[1].1, TEST_EXPIRATION); // Adding another claim to different address, make sure that other address only has one claim. claims @@ -190,12 +217,16 @@ mod test { // Assert that both claims exist for the address. let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); let saved_claims_addr2 = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr2")) + .prefix(&Addr::unchecked("addr2")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); assert_eq!(saved_claims.len(), 2); assert_eq!(saved_claims_addr2.len(), 1); @@ -206,19 +237,37 @@ mod test { let mut deps = mock_dependencies(); let claims = NftClaims::new("claims"); - let nfts = claims + let env = mock_env(); + let error = claims .claim_nfts( deps.as_mut().storage, &Addr::unchecked("addr"), + &["404".to_string()], + &env.block, + ) + .unwrap_err(); + assert_eq!( + error, + NftClaimError::NotFound { + token_id: "404".to_string() + } + ); + + claims + .claim_nfts( + deps.as_mut().storage, + &Addr::unchecked("addr"), + &[], &mock_env().block, ) .unwrap(); let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range_raw(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); - assert_eq!(nfts.len(), 0); assert_eq!(saved_claims.len(), 0); } @@ -248,24 +297,36 @@ mod test { let mut env = mock_env(); env.block.height = 0; // the address has two claims however they are both not expired - let nfts = claims - .claim_nfts(deps.as_mut().storage, &Addr::unchecked("addr"), &env.block) - .unwrap(); + let error = claims + .claim_nfts( + deps.as_mut().storage, + &Addr::unchecked("addr"), + &[ + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), + TEST_BAYC_TOKEN_ID.to_string(), + ], + &env.block, + ) + .unwrap_err(); + assert_eq!( + error, + NftClaimError::NotReady { + token_id: TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() + } + ); let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); - assert_eq!(nfts.len(), 0); assert_eq!(saved_claims.len(), 2); - assert_eq!( - saved_claims[0].token_id, - TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() - ); - assert_eq!(saved_claims[0].release_at, Expiration::AtHeight(10)); - assert_eq!(saved_claims[1].token_id, TEST_BAYC_TOKEN_ID.to_string()); - assert_eq!(saved_claims[1].release_at, Expiration::AtHeight(100)); + assert_eq!(saved_claims[0].0, TEST_BAYC_TOKEN_ID.to_string()); + assert_eq!(saved_claims[0].1, Expiration::AtHeight(100)); + assert_eq!(saved_claims[1].0, TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()); + assert_eq!(saved_claims[1].1, Expiration::AtHeight(10)); } #[test] @@ -294,23 +355,25 @@ mod test { let mut env = mock_env(); env.block.height = 20; // the address has two claims and the first one can be released - let nfts = claims - .claim_nfts(deps.as_mut().storage, &Addr::unchecked("addr"), &env.block) + claims + .claim_nfts( + deps.as_mut().storage, + &Addr::unchecked("addr"), + &[TEST_BAYC_TOKEN_ID.to_string()], + &env.block, + ) .unwrap(); let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); - assert_eq!(nfts.len(), 1); - assert_eq!(nfts[0], TEST_BAYC_TOKEN_ID.to_string()); assert_eq!(saved_claims.len(), 1); - assert_eq!( - saved_claims[0].token_id, - TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() - ); - assert_eq!(saved_claims[0].release_at, Expiration::AtHeight(100)); + assert_eq!(saved_claims[0].0, TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()); + assert_eq!(saved_claims[0].1, Expiration::AtHeight(100)); } #[test] @@ -339,22 +402,25 @@ mod test { let mut env = mock_env(); env.block.height = 1000; // the address has two claims and both can be released - let nfts = claims - .claim_nfts(deps.as_mut().storage, &Addr::unchecked("addr"), &env.block) + claims + .claim_nfts( + deps.as_mut().storage, + &Addr::unchecked("addr"), + &[ + TEST_BAYC_TOKEN_ID.to_string(), + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), + ], + &env.block, + ) .unwrap(); let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .collect::>>() .unwrap(); - assert_eq!( - nfts, - vec![ - TEST_BAYC_TOKEN_ID.to_string(), - TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() - ] - ); assert_eq!(saved_claims.len(), 0); } @@ -373,13 +439,86 @@ mod test { .unwrap(); let queried_claims = claims - .query_claims(deps.as_ref(), &Addr::unchecked("addr")) + .query_claims(deps.as_ref(), &Addr::unchecked("addr"), None, None) .unwrap(); let saved_claims = claims .0 - .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .prefix(&Addr::unchecked("addr")) + .range(deps.as_mut().storage, None, None, Order::Ascending) + .map(|item| item.map(|(token_id, v)| NftClaim::new(token_id, v))) + .collect::>>() + .unwrap(); + + assert_eq!(queried_claims, saved_claims); + } + + #[test] + fn test_query_claims_returns_correct_claims_paginated() { + let mut deps = mock_dependencies(); + let claims = NftClaims::new("claims"); + + claims + .create_nft_claims( + deps.as_mut().storage, + &Addr::unchecked("addr"), + vec![ + TEST_BAYC_TOKEN_ID.to_string(), + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), + ], + Expiration::AtHeight(10), + ) + .unwrap(); + + let queried_claims = claims + .query_claims(deps.as_ref(), &Addr::unchecked("addr"), None, None) + .unwrap(); + assert_eq!( + queried_claims, + vec![ + NftClaim::new(TEST_BAYC_TOKEN_ID.to_string(), Expiration::AtHeight(10)), + NftClaim::new( + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), + Expiration::AtHeight(10) + ), + ] + ); + + let queried_claims = claims + .query_claims(deps.as_ref(), &Addr::unchecked("addr"), None, Some(1)) + .unwrap(); + assert_eq!( + queried_claims, + vec![NftClaim::new( + TEST_BAYC_TOKEN_ID.to_string(), + Expiration::AtHeight(10) + ),] + ); + + let queried_claims = claims + .query_claims( + deps.as_ref(), + &Addr::unchecked("addr"), + Some(&TEST_BAYC_TOKEN_ID.to_string()), + None, + ) + .unwrap(); + assert_eq!( + queried_claims, + vec![NftClaim::new( + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string(), + Expiration::AtHeight(10) + )] + ); + + let queried_claims = claims + .query_claims( + deps.as_ref(), + &Addr::unchecked("addr"), + Some(&TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()), + None, + ) .unwrap(); - assert_eq!(queried_claims.nft_claims, saved_claims); + assert_eq!(queried_claims.len(), 0); } #[test] @@ -397,9 +536,9 @@ mod test { .unwrap(); let queried_claims = claims - .query_claims(deps.as_ref(), &Addr::unchecked("addr2")) + .query_claims(deps.as_ref(), &Addr::unchecked("addr2"), None, None) .unwrap(); - assert_eq!(queried_claims.nft_claims.len(), 0); + assert_eq!(queried_claims.len(), 0); } }