diff --git a/.gitignore b/.gitignore index 3dad0bb6..29f22fa3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,3 @@ Cargo.lock .idea -artifacts diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..3616c1cb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1467 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64ct" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" + +[[package]] +name = "cosmos-sdk-proto" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b42021d8488665b1a0d9748f1f81df7235362d194f44481e2e61bf376b77b4" +dependencies = [ + "prost 0.11.3", + "prost-types", + "tendermint-proto", +] + +[[package]] +name = "cosmwasm-crypto" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227315dc11f0bb22a273d0c43d3ba8ef52041c42cf959f09045388a89c57e661" +dependencies = [ + "digest 0.10.6", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fca30d51f7e5fbfa6440d8b10d7df0231bdf77e97fd3fe5d0cb79cc4822e50c" +dependencies = [ + "syn", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04135971e2c3b867eb793ca4e832543c077dbf72edaef7672699190f8fcdb619" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06c8f516a13ae481016aa35f0b5c4652459e8aee65b15b6fb51547a07cea5a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cosmwasm-std" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b13d5a84d15cf7be17dc249a21588cdb0f7ef308907c50ce2723316a7d79c3dc" +dependencies = [ + "base64", + "cosmwasm-crypto", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde", + "serde-json-wasm", + "thiserror", + "uint", +] + +[[package]] +name = "cosmwasm-storage" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9162c3f85412914d5be2ee650ebdbf11b08e5e9acdebcf4dc03608fb01cf9676" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "cw-controllers" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw-utils", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-core" +version = "0.1.0" +source = "git+https://github.com/DA0-DA0/dao-contracts.git?tag=v1.0.0#e531c760a5d057329afd98d62567aaa4dca2c96f" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-core-interface", + "cw-core-macros", + "cw-paginate 0.1.0", + "cw-storage-plus 0.13.4", + "cw-utils", + "cw2", + "cw20", + "cw721", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-core-interface" +version = "0.1.0" +source = "git+https://github.com/DA0-DA0/dao-contracts.git?tag=v1.0.0#e531c760a5d057329afd98d62567aaa4dca2c96f" +dependencies = [ + "cosmwasm-std", + "cw-core-macros", + "cw2", + "schemars", + "serde", +] + +[[package]] +name = "cw-core-macros" +version = "0.1.0" +source = "git+https://github.com/DA0-DA0/dao-contracts.git?tag=v1.0.0#e531c760a5d057329afd98d62567aaa4dca2c96f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cw-denom" +version = "0.2.0" +dependencies = [ + "cosmwasm-std", + "cw-multi-test", + "cw20", + "cw20-base", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-multi-test" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 0.13.4", + "cw-utils", + "derivative", + "itertools", + "prost 0.9.0", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-paginate" +version = "0.1.0" +source = "git+https://github.com/DA0-DA0/dao-contracts.git?tag=v1.0.0#e531c760a5d057329afd98d62567aaa4dca2c96f" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 0.13.4", + "serde", +] + +[[package]] +name = "cw-paginate" +version = "0.2.0" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test", + "cw-storage-plus 0.13.4", + "serde", +] + +[[package]] +name = "cw-proposal-single" +version = "0.1.0" +source = "git+https://github.com/DA0-DA0/dao-contracts.git?tag=v1.0.0#e531c760a5d057329afd98d62567aaa4dca2c96f" +dependencies = [ + "cosmwasm-std", + "cosmwasm-storage", + "cw-core", + "cw-core-interface", + "cw-core-macros", + "cw-storage-plus 0.13.4", + "cw-utils", + "cw2", + "cw20", + "cw3", + "indexable-hooks", + "proposal-hooks", + "schemars", + "serde", + "thiserror", + "vote-hooks", + "voting", +] + +[[package]] +name = "cw-storage-plus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-storage-plus" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b6f91c0b94481a3e9ef1ceb183c37d00764f8751e39b45fc09f4d9b970d469" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw20" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb782b8f110819a4eb5dbbcfed25ffba49ec16bbe32b4ad8da50a5ce68fec05" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw20-base" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0306e606581f4fb45e82bcbb7f0333179ed53dd949c6523f01a99b4bfc1475a0" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw-utils", + "cw2", + "cw20", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw3" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw4" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "schemars", + "serde", +] + +[[package]] +name = "cw4-group" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6c95c89153e7831c8306c8eba40a3daa76f9c7b8f5179dd0b8628aca168ec7a" +dependencies = [ + "cosmwasm-std", + "cw-controllers", + "cw-storage-plus 0.13.4", + "cw-utils", + "cw2", + "cw4", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw721" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "035818368a74c07dd9ed5c5a93340199ba251530162010b9f34c3809e3b97df1" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw721-base" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "423d4efe8b649d228d1533e141c238415f49aa8a9ee4e40fce192d7a93ffd057" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw-utils", + "cw2", + "cw721", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw721-controllers" +version = "0.2.0" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "cw-utils", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cwd-core" +version = "0.2.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-core", + "cw-paginate 0.2.0", + "cw-storage-plus 0.13.4", + "cw-utils", + "cw2", + "cw20", + "cw721", + "cwd-interface", + "cwd-macros", + "neutron-sdk", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cwd-hooks" +version = "0.2.0" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cwd-interface" +version = "0.2.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2", + "cwd-macros", + "schemars", + "serde", +] + +[[package]] +name = "cwd-macros" +version = "0.2.0" +dependencies = [ + "cosmwasm-schema", + "proc-macro2", + "quote", + "schemars", + "serde", + "syn", +] + +[[package]] +name = "cwd-pre-propose-base" +version = "0.2.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-denom", + "cw-multi-test", + "cw-storage-plus 0.13.4", + "cw2", + "cwd-interface", + "cwd-proposal-hooks", + "cwd-voting", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cwd-pre-propose-single" +version = "0.2.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-denom", + "cw-multi-test", + "cw-utils", + "cw2", + "cw20", + "cw20-base", + "cw4-group", + "cwd-core", + "cwd-interface", + "cwd-pre-propose-base", + "cwd-proposal-hooks", + "cwd-proposal-single", + "cwd-voting", + "neutron-sdk", + "schemars", + "serde", +] + +[[package]] +name = "cwd-proposal-hooks" +version = "0.2.0" +dependencies = [ + "cosmwasm-std", + "cwd-hooks", + "cwd-voting", + "schemars", + "serde", +] + +[[package]] +name = "cwd-proposal-single" +version = "0.2.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-denom", + "cw-multi-test", + "cw-proposal-single", + "cw-storage-plus 0.13.4", + "cw-utils", + "cw2", + "cw20", + "cw20-base", + "cw3", + "cw4", + "cw4-group", + "cw721-base", + "cwd-core", + "cwd-hooks", + "cwd-interface", + "cwd-macros", + "cwd-pre-propose-base", + "cwd-pre-propose-single", + "cwd-proposal-hooks", + "cwd-vote-hooks", + "cwd-voting", + "neutron-sdk", + "schemars", + "serde", + "thiserror", + "voting", +] + +[[package]] +name = "cwd-vote-hooks" +version = "0.2.0" +dependencies = [ + "cosmwasm-std", + "cwd-hooks", + "cwd-voting", + "schemars", + "serde", +] + +[[package]] +name = "cwd-voting" +version = "0.2.0" +dependencies = [ + "cosmwasm-std", + "cw-denom", + "cw-storage-plus 0.13.4", + "cw-utils", + "cw20", + "cwd-core", + "cwd-interface", + "cwd-macros", + "neutron-sdk", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer 0.10.3", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" + +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek", + "hashbrown", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "digest 0.10.6", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "paste", +] + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "indexable-hooks" +version = "0.1.0" +source = "git+https://github.com/DA0-DA0/dao-contracts.git?tag=v1.0.0#e531c760a5d057329afd98d62567aaa4dca2c96f" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "k256" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2 0.10.6", +] + +[[package]] +name = "libc" +version = "0.2.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" + +[[package]] +name = "neutron-sdk" +version = "0.1.0" +source = "git+https://github.com/neutron-org/neutron-contracts.git#3ad81939da78954ff032a26e54d792cc16a4d9f3" +dependencies = [ + "base64", + "bech32", + "cosmos-sdk-proto", + "cosmwasm-std", + "cw-storage-plus 0.16.0", + "prost 0.11.3", + "protobuf", + "schemars", + "serde", + "serde-json-wasm", + "serde_json", + "thiserror", +] + +[[package]] +name = "neutron-vault" +version = "0.2.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers", + "cw-multi-test", + "cw-paginate 0.2.0", + "cw-storage-plus 0.13.4", + "cw-utils", + "cw2", + "cwd-interface", + "cwd-macros", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "neutron-voting-registry" +version = "0.2.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-controllers", + "cw-multi-test", + "cw-paginate 0.2.0", + "cw-storage-plus 0.13.4", + "cw-utils", + "cw2", + "cwd-interface", + "cwd-macros", + "neutron-vault", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "paste" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proposal-hooks" +version = "0.1.0" +source = "git+https://github.com/DA0-DA0/dao-contracts.git?tag=v1.0.0#e531c760a5d057329afd98d62567aaa4dca2c96f" +dependencies = [ + "cosmwasm-std", + "indexable-hooks", + "schemars", + "serde", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b18e655c21ff5ac2084a5ad0611e827b3f92badf79f4910b5a5c58f4d87ff0" +dependencies = [ + "bytes", + "prost-derive 0.11.2", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" +dependencies = [ + "bytes", + "prost 0.11.3", +] + +[[package]] +name = "protobuf" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +dependencies = [ + "bytes", + "once_cell", + "protobuf-support", + "thiserror", +] + +[[package]] +name = "protobuf-support" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +dependencies = [ + "thiserror", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schemars" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "serde" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-json-wasm" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest 0.10.6", + "rand_core 0.6.4", +] + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "syn" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tendermint-proto" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ce80bf536476db81ecc9ebab834dc329c9c1509a694f211a73858814bfe023" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.11.3", + "prost-types", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "vote-hooks" +version = "0.1.0" +source = "git+https://github.com/DA0-DA0/dao-contracts.git?tag=v1.0.0#e531c760a5d057329afd98d62567aaa4dca2c96f" +dependencies = [ + "cosmwasm-std", + "indexable-hooks", + "schemars", + "serde", +] + +[[package]] +name = "voting" +version = "0.1.0" +source = "git+https://github.com/DA0-DA0/dao-contracts.git?tag=v1.0.0#e531c760a5d057329afd98d62567aaa4dca2c96f" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/Cargo.toml b/Cargo.toml index e5d5f8f4..a1d9438d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,21 @@ [workspace] -members = ["contracts/*"] +members = [ + "contracts/cwd-core", + "contracts/proposal/*", + "contracts/pre-propose/*", + "contracts/voting/*", + "packages/*", + ] [profile.release] +codegen-units = 1 +opt-level = 3 +debug = false rpath = false lto = true +debug-assertions = false +panic = 'abort' +incremental = false +# Please do not disable these. Doing so will cause overflow checks in +# all workspace members to stop working. Overflows should be errors. overflow-checks = true diff --git a/LICENSE b/LICENSE index 261eeb9e..db39fb0c 100644 --- a/LICENSE +++ b/LICENSE @@ -45,157 +45,3 @@ separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/Makefile b/Makefile index 30ec6598..35bd6062 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: schema test clippy proto-gen build fmt schema: - @find contracts/* -maxdepth 0 -type d \( ! -name . \) -exec bash -c "cd '{}' && cargo schema" \; + @find contracts/* -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && cargo schema" \; test: @cargo test @@ -14,12 +14,12 @@ fmt: check_contracts: @cargo install cosmwasm-check - @cosmwasm-check artifacts/*.wasm + @cosmwasm-check --available-capabilities iterator,staking,stargate,neutron artifacts/*.wasm compile: @./build_release.sh -build: schema clippy test fmt compile check_contracts +build: schema clippy fmt compile check_contracts diff --git a/artifacts/checksums.txt b/artifacts/checksums.txt new file mode 100644 index 00000000..baa70cf8 --- /dev/null +++ b/artifacts/checksums.txt @@ -0,0 +1,5 @@ +211577e38444ddee1c274195c68927c3564dba7b5734794ac74aac4925be8aac cwd_core.wasm +a7d0389f7ccddcd1833df91fd6005e249b1fbadb8048fc21a38b9bd259e54e9e cwd_pre_propose_single.wasm +9061ea45715d32747351251ec934a814654fd315ca76c24f57845ac9652a9557 cwd_proposal_single.wasm +db056695c4dfb700a3671b5dc74e783d83ba69e166c73712f69656f32eff1533 neutron_vault.wasm +acde815a9a7805dd4d559d9b57dfab8c37bad916ba2b2c5f7f118c977904c5cf neutron_voting_registry.wasm diff --git a/artifacts/checksums_intermediate.txt b/artifacts/checksums_intermediate.txt new file mode 100644 index 00000000..9f7025df --- /dev/null +++ b/artifacts/checksums_intermediate.txt @@ -0,0 +1,5 @@ +d1209871e9e55d01f556aac30d70610e9147f801e53183d554ddbb4503e5283f target/wasm32-unknown-unknown/release/cwd_core.wasm +ec098e437e1300a654bf5fa71ab82fd660670ef0c453f0d17429eafd45f001ef target/wasm32-unknown-unknown/release/cwd_pre_propose_single.wasm +e35349b7dcdc315c4e91cbafef9570c96adc06a11ce39ddfbde0a2cdc0355309 target/wasm32-unknown-unknown/release/cwd_proposal_single.wasm +b74ac8ee1b649d1fa671af9be078b7275c238d948b0d6078345bc254d906a2f3 target/wasm32-unknown-unknown/release/neutron_vault.wasm +0fcb3ab9c846845680e92e386b7b10468f047e1d52365c4b4f28cf71d62a7539 target/wasm32-unknown-unknown/release/neutron_voting_registry.wasm diff --git a/artifacts/cwd_core.wasm b/artifacts/cwd_core.wasm new file mode 100644 index 00000000..9fd2ca5f Binary files /dev/null and b/artifacts/cwd_core.wasm differ diff --git a/artifacts/cwd_pre_propose_single.wasm b/artifacts/cwd_pre_propose_single.wasm new file mode 100644 index 00000000..e184bf40 Binary files /dev/null and b/artifacts/cwd_pre_propose_single.wasm differ diff --git a/artifacts/cwd_proposal_single.wasm b/artifacts/cwd_proposal_single.wasm new file mode 100644 index 00000000..61b1c6d6 Binary files /dev/null and b/artifacts/cwd_proposal_single.wasm differ diff --git a/artifacts/neutron_vault.wasm b/artifacts/neutron_vault.wasm new file mode 100644 index 00000000..7e6e1177 Binary files /dev/null and b/artifacts/neutron_vault.wasm differ diff --git a/artifacts/neutron_voting_registry.wasm b/artifacts/neutron_voting_registry.wasm new file mode 100644 index 00000000..255ce2eb Binary files /dev/null and b/artifacts/neutron_voting_registry.wasm differ diff --git a/contracts/README b/contracts/README new file mode 100644 index 00000000..ae299296 --- /dev/null +++ b/contracts/README @@ -0,0 +1,13 @@ +- `cwd-core` - the core module for DAOs. +- `external` - contracts used by DAOs that are not part of a DAO + module. +- `pre-propose` - pre-propose modules. +- `proposal` - proposal modules. +- `voting` - voting modules. +- `staking` - cw20 staking functionality and a staking rewards + system. These contracts are used by [Wasmswap] as well as DAO DAO. + +For a description of each module type, see [our wiki]. + +[Wasmswap]: https://github.com/Wasmswap +[our wiki]: https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-Contracts-Design diff --git a/contracts/cwd-core/.cargo/config b/contracts/cwd-core/.cargo/config new file mode 100644 index 00000000..336b618a --- /dev/null +++ b/contracts/cwd-core/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/cwd-core/.cargo/schema/active_proposal_modules_response.json b/contracts/cwd-core/.cargo/schema/active_proposal_modules_response.json new file mode 100644 index 00000000..064e8b4e --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/active_proposal_modules_response.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ActiveProposalModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/admin_nomination_response.json b/contracts/cwd-core/.cargo/schema/admin_nomination_response.json new file mode 100644 index 00000000..7e2b4af9 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/admin_nomination_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminNominationResponse", + "description": "Returned by the `AdminNomination` query.", + "type": "object", + "properties": { + "nomination": { + "description": "The currently nominated admin or None if no nomination is pending.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/admin_response.json b/contracts/cwd-core/.cargo/schema/admin_response.json new file mode 100644 index 00000000..794c4351 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/admin_response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/config_response.json b/contracts/cwd-core/.cargo/schema/config_response.json new file mode 100644 index 00000000..f0f3d2fc --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/config_response.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/cw20_balances_response.json b/contracts/cwd-core/.cargo/schema/cw20_balances_response.json new file mode 100644 index 00000000..34300995 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/cw20_balances_response.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20BalancesResponse", + "description": "Returned by the `Cw20Balances` query.", + "type": "object", + "required": [ + "addr", + "balance" + ], + "properties": { + "addr": { + "description": "The address of the token.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "balance": { + "description": "The contract's balance.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/cw20_token_list_response.json b/contracts/cwd-core/.cargo/schema/cw20_token_list_response.json new file mode 100644 index 00000000..20fb4848 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/cw20_token_list_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20TokenListResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/cw721_token_list_response.json b/contracts/cwd-core/.cargo/schema/cw721_token_list_response.json new file mode 100644 index 00000000..9ca019ec --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/cw721_token_list_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw721TokenListResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/dao_u_r_i_response.json b/contracts/cwd-core/.cargo/schema/dao_u_r_i_response.json new file mode 100644 index 00000000..2b9566ad --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/dao_u_r_i_response.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoURIResponse", + "type": [ + "string", + "null" + ] +} diff --git a/contracts/cwd-core/.cargo/schema/dump_state_response.json b/contracts/cwd-core/.cargo/schema/dump_state_response.json new file mode 100644 index 00000000..1543ad72 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/dump_state_response.json @@ -0,0 +1,245 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DumpStateResponse", + "description": "Relevant state for the governance module. Returned by the `DumpState` query.", + "type": "object", + "required": [ + "active_proposal_module_count", + "config", + "pause_info", + "proposal_modules", + "total_proposal_module_count", + "version", + "voting_module" + ], + "properties": { + "active_proposal_module_count": { + "description": "The number of active proposal modules.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "config": { + "description": "The governance contract's config.", + "allOf": [ + { + "$ref": "#/definitions/Config" + } + ] + }, + "pause_info": { + "$ref": "#/definitions/PauseInfoResponse" + }, + "proposal_modules": { + "description": "The governance modules associated with the governance contract.", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + } + }, + "total_proposal_module_count": { + "description": "The total number of proposal modules.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "version": { + "description": "The governance contract's version.", + "allOf": [ + { + "$ref": "#/definitions/ContractVersion" + } + ] + }, + "voting_module": { + "description": "The voting module associated with the governance contract.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Config": { + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } + }, + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "PauseInfoResponse": { + "description": "Information about if the contract is currently paused.", + "oneOf": [ + { + "type": "object", + "required": [ + "Paused" + ], + "properties": { + "Paused": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "Unpaused" + ], + "properties": { + "Unpaused": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/execute_msg.json b/contracts/cwd-core/.cargo/schema/execute_msg.json new file mode 100644 index 00000000..be8ebdde --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/execute_msg.json @@ -0,0 +1,1506 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Callable by proposal modules. The DAO will execute the messages in the hook in order.", + "type": "object", + "required": [ + "execute_proposal_hook" + ], + "properties": { + "execute_proposal_hook": { + "type": "object", + "required": [ + "msgs" + ], + "properties": { + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_NeutronMsg" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Pauses the DAO for a set duration. When paused the DAO is unable to execute proposals", + "type": "object", + "required": [ + "pause" + ], + "properties": { + "pause": { + "type": "object", + "required": [ + "duration" + ], + "properties": { + "duration": { + "$ref": "#/definitions/Duration" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Removes an item from the governance contract's item map.", + "type": "object", + "required": [ + "remove_item" + ], + "properties": { + "remove_item": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Adds an item to the governance contract's item map. If the item already exists the existing value is overriden. If the item does not exist a new item is added.", + "type": "object", + "required": [ + "set_item" + ], + "properties": { + "set_item": { + "type": "object", + "required": [ + "addr", + "key" + ], + "properties": { + "addr": { + "type": "string" + }, + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callable by the core contract. Replaces the current governance contract config with the provided config.", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/Config" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Updates the governance contract's governance modules. Module instantiate info in `to_add` is used to create new modules and install them.", + "type": "object", + "required": [ + "update_proposal_modules" + ], + "properties": { + "update_proposal_modules": { + "type": "object", + "required": [ + "to_add", + "to_disable" + ], + "properties": { + "to_add": { + "type": "array", + "items": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + }, + "to_disable": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callable by the core contract. Replaces the current voting module with a new one instantiated by the governance contract.", + "type": "object", + "required": [ + "update_voting_module" + ], + "properties": { + "update_voting_module": { + "type": "object", + "required": [ + "module" + ], + "properties": { + "module": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Update the core module to add/remove SubDAOs and their charters", + "type": "object", + "required": [ + "update_sub_daos" + ], + "properties": { + "update_sub_daos": { + "type": "object", + "required": [ + "to_add", + "to_remove" + ], + "properties": { + "to_add": { + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + } + }, + "to_remove": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "AdminProposal": { + "description": "AdminProposal defines the struct for various proposals which Neutron's Admin Module may accept. Currently only parameter change proposals are implemented, new types of admin proposals may be implemented in future.", + "type": "object", + "properties": { + "param_change_proposal": { + "description": "*param_change_proposal** is a parameter change proposal field.", + "anyOf": [ + { + "$ref": "#/definitions/ParamChangeProposal" + }, + { + "type": "null" + } + ] + } + } + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Config": { + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } + }, + "CosmosMsg_for_NeutronMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/NeutronMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/VoteOption" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcFee": { + "type": "object", + "required": [ + "ack_fee", + "recv_fee", + "timeout_fee" + ], + "properties": { + "ack_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recv_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "timeout_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "KVKey": { + "description": "Describes a KV key for which you want to get value from the storage on remote chain", + "type": "object", + "required": [ + "key", + "path" + ], + "properties": { + "key": { + "description": "*key** is a key you want to read from the storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "*path** is a path to the storage (storage prefix) where you want to read value by key (usually name of cosmos-sdk module: 'staking', 'bank', etc.)", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "NeutronMsg": { + "description": "A number of Custom messages that can call into the Neutron bindings.", + "oneOf": [ + { + "description": "RegisterInterchainAccount registers an interchain account on remote chain.", + "type": "object", + "required": [ + "register_interchain_account" + ], + "properties": { + "register_interchain_account": { + "type": "object", + "required": [ + "connection_id", + "interchain_account_id" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "interchain_account_id": { + "description": "**interchain_account_id** is an identifier of your new interchain account. Can be any string. This identifier allows contracts to have multiple interchain accounts on remote chains.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitTx starts the process of executing any Cosmos-SDK *msgs* on remote chain.", + "type": "object", + "required": [ + "submit_tx" + ], + "properties": { + "submit_tx": { + "type": "object", + "required": [ + "connection_id", + "fee", + "interchain_account_id", + "memo", + "msgs", + "timeout" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "fee": { + "description": "**fee** is an ibc fee for the transaction.", + "allOf": [ + { + "$ref": "#/definitions/IbcFee" + } + ] + }, + "interchain_account_id": { + "description": "*interchain_account_id** is an identifier of your interchain account from which you want to execute msgs.", + "type": "string" + }, + "memo": { + "description": "*memo** is a memo you want to attach to your interchain transaction.It behaves like a memo in usual Cosmos transaction.", + "type": "string" + }, + "msgs": { + "description": "*msgs** is a list of protobuf encoded Cosmos-SDK messages you want to execute on remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/ProtobufAny" + } + }, + "timeout": { + "description": "*timeout** is a timeout in seconds after which the packet times out.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery registers an interchain query.", + "type": "object", + "required": [ + "register_interchain_query" + ], + "properties": { + "register_interchain_query": { + "type": "object", + "required": [ + "connection_id", + "keys", + "query_type", + "transactions_filter", + "update_period" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "keys": { + "description": "*keys** is the KV-storage keys for which we want to get values from remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "query_type": { + "description": "*query_type** is a query type identifier ('tx' or 'kv' for now).", + "type": "string" + }, + "transactions_filter": { + "description": "*transactions_filter** is the filter for transaction search ICQ.", + "type": "string" + }, + "update_period": { + "description": "*update_period** is used to say how often the query must be updated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery updates an interchain query.", + "type": "object", + "required": [ + "update_interchain_query" + ], + "properties": { + "update_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "new_keys": { + "description": "*new_keys** is the new query keys to retrive.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "new_transactions_filter": { + "description": "*new_transactions_filter** is a new transactions filter of the query.", + "type": [ + "string", + "null" + ] + }, + "new_update_period": { + "description": "*new_update_period** is a new update period of the query.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "query_id": { + "description": "*query_id** is the ID of the query we want to update.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RemoveInterchainQuery removes as interchain query.", + "type": "object", + "required": [ + "remove_interchain_query" + ], + "properties": { + "remove_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "description": "*query_id** is ID of the query we want to remove.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "IbcTransfer sends a fungible token packet over IBC.", + "type": "object", + "required": [ + "ibc_transfer" + ], + "properties": { + "ibc_transfer": { + "type": "object", + "required": [ + "fee", + "receiver", + "sender", + "source_channel", + "source_port", + "timeout_height", + "timeout_timestamp", + "token" + ], + "properties": { + "fee": { + "$ref": "#/definitions/IbcFee" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "source_channel": { + "type": "string" + }, + "source_port": { + "type": "string" + }, + "timeout_height": { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + "timeout_timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitAdminProposal sends a proposal to neutron's Admin module. This type of messages can be only executed by Neutron DAO.", + "type": "object", + "required": [ + "submit_admin_proposal" + ], + "properties": { + "submit_admin_proposal": { + "type": "object", + "required": [ + "admin_proposal" + ], + "properties": { + "admin_proposal": { + "$ref": "#/definitions/AdminProposal" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines the struct for parameter change request.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "description": "*key** is a name of parameter. Unique for subspace.", + "type": "string" + }, + "subspace": { + "description": "*subspace** is a key of module to which the parameter to change belongs. Unique for each module.", + "type": "string" + }, + "value": { + "description": "*value** is a new value for given parameter. Non unique.", + "type": "string" + } + } + }, + "ParamChangeProposal": { + "description": "ParamChangeProposal defines the struct for single parameter change proposal.", + "type": "object", + "required": [ + "description", + "param_changes", + "title" + ], + "properties": { + "description": { + "description": "*descriptionr** is a text description of proposal. Non unique.", + "type": "string" + }, + "param_changes": { + "description": "*param_changes** is a vector of params to be changed. Non unique.", + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + }, + "title": { + "description": "*title** is a text title of proposal. Non unique.", + "type": "string" + } + } + }, + "ProtobufAny": { + "description": "Type for wrapping any protobuf message", + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "description": "*type_url** describes the type of the serialized message", + "type": "string" + }, + "value": { + "description": "*value** must be a valid serialized protocol buffer of the above specified type", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/get_item_response.json b/contracts/cwd-core/.cargo/schema/get_item_response.json new file mode 100644 index 00000000..11fd7904 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/get_item_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetItemResponse", + "description": "Returned by the `GetItem` query.", + "type": "object", + "properties": { + "item": { + "description": "`None` if no item with the provided key was found, `Some` otherwise.", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/info_response.json b/contracts/cwd-core/.cargo/schema/info_response.json new file mode 100644 index 00000000..a0516764 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/info_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/instantiate_msg.json b/contracts/cwd-core/.cargo/schema/instantiate_msg.json new file mode 100644 index 00000000..efec760e --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/instantiate_msg.json @@ -0,0 +1,156 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "description", + "name", + "proposal_modules_instantiate_info", + "voting_registry_module_instantiate_info" + ], + "properties": { + "dao_uri": { + "description": "Implements the DAO Star standard: https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the core contract.", + "type": "string" + }, + "initial_items": { + "description": "Initial information for arbitrary contract addresses to be added to the items map. The key is the name of the item in the items map. The value is an enum that either uses an existing address or instantiates a new contract.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/InitialItem" + } + }, + "name": { + "description": "The name of the core contract.", + "type": "string" + }, + "proposal_modules_instantiate_info": { + "description": "Instantiate information for the core contract's proposal modules.", + "type": "array", + "items": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + }, + "voting_registry_module_instantiate_info": { + "description": "Instantiate information for the core contract's voting power module.", + "allOf": [ + { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + ] + } + }, + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "InitialItem": { + "description": "Information about an item to be stored in the items list.", + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "description": "The name of the item.", + "type": "string" + }, + "value": { + "description": "The value the item will have at instantiation time.", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/list_items_response.json b/contracts/cwd-core/.cargo/schema/list_items_response.json new file mode 100644 index 00000000..9b743544 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/list_items_response.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListItemsResponse", + "type": "array", + "items": { + "type": "string" + } +} diff --git a/contracts/cwd-core/.cargo/schema/list_sub_daos_response.json b/contracts/cwd-core/.cargo/schema/list_sub_daos_response.json new file mode 100644 index 00000000..69a1e8bc --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/list_sub_daos_response.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListSubDaosResponse", + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + }, + "definitions": { + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } + } + } +} diff --git a/contracts/voting/schema/execute_msg.json b/contracts/cwd-core/.cargo/schema/migrate_msg.json similarity index 55% rename from contracts/voting/schema/execute_msg.json rename to contracts/cwd-core/.cargo/schema/migrate_msg.json index 103283b5..6b9264c8 100644 --- a/contracts/voting/schema/execute_msg.json +++ b/contracts/cwd-core/.cargo/schema/migrate_msg.json @@ -1,16 +1,23 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", + "title": "MigrateMsg", "oneOf": [ { - "description": "Transfer the contract's ownership to another account", "type": "object", "required": [ - "transfer_ownership" + "from_v1" ], "properties": { - "transfer_ownership": { - "type": "string" + "from_v1": { + "type": "object", + "properties": { + "dao_uri": { + "type": [ + "string", + "null" + ] + } + } } }, "additionalProperties": false @@ -18,10 +25,10 @@ { "type": "object", "required": [ - "init_voting" + "from_compatible" ], "properties": { - "init_voting": { + "from_compatible": { "type": "object" } }, diff --git a/contracts/cwd-core/.cargo/schema/pause_info_response.json b/contracts/cwd-core/.cargo/schema/pause_info_response.json new file mode 100644 index 00000000..eb8e1aeb --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/pause_info_response.json @@ -0,0 +1,99 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PauseInfoResponse", + "description": "Information about if the contract is currently paused.", + "oneOf": [ + { + "type": "object", + "required": [ + "Paused" + ], + "properties": { + "Paused": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "Unpaused" + ], + "properties": { + "Unpaused": { + "type": "object" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/proposal_modules_response.json b/contracts/cwd-core/.cargo/schema/proposal_modules_response.json new file mode 100644 index 00000000..c2940218 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/proposal_modules_response.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/query_msg.json b/contracts/cwd-core/.cargo/schema/query_msg.json new file mode 100644 index 00000000..2692c274 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/query_msg.json @@ -0,0 +1,270 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Gets the contract's config. Returns Config.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Dumps all of the core contract's state in a single query. Useful for frontends as performance for queries is more limited by network times than compute times. Returns `DumpStateResponse`.", + "type": "object", + "required": [ + "dump_state" + ], + "properties": { + "dump_state": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the address associated with an item key.", + "type": "object", + "required": [ + "get_item" + ], + "properties": { + "get_item": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Lists all of the items associted with the contract. For example, given the items `{ \"group\": \"foo\", \"subdao\": \"bar\"}` this query would return `[(\"group\", \"foo\"), (\"subdao\", \"bar\")]`.", + "type": "object", + "required": [ + "list_items" + ], + "properties": { + "list_items": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets all proposal modules associated with the contract. Returns Vec.", + "type": "object", + "required": [ + "proposal_modules" + ], + "properties": { + "proposal_modules": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets the active proposal modules associated with the contract. Returns Vec.", + "type": "object", + "required": [ + "active_proposal_modules" + ], + "properties": { + "active_proposal_modules": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about if the contract is currently paused.", + "type": "object", + "required": [ + "pause_info" + ], + "properties": { + "pause_info": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the contract's voting module. Returns Addr.", + "type": "object", + "required": [ + "voting_module" + ], + "properties": { + "voting_module": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Returns all SubDAOs with their charters in a vec start_after is bound exclusive and asks for a string address", + "type": "object", + "required": [ + "list_sub_daos" + ], + "properties": { + "list_sub_daos": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Implements the DAO Star standard: https://daostar.one/EIP", + "type": "object", + "required": [ + "dao_u_r_i" + ], + "properties": { + "dao_u_r_i": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_power_at_height" + ], + "properties": { + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_power_at_height" + ], + "properties": { + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cwd-core/.cargo/schema/sub_dao.json b/contracts/cwd-core/.cargo/schema/sub_dao.json new file mode 100644 index 00000000..ae12c03a --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/sub_dao.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SubDao", + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/total_power_at_height_response.json b/contracts/cwd-core/.cargo/schema/total_power_at_height_response.json new file mode 100644 index 00000000..8018462b --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/total_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/.cargo/schema/voting_module_response.json b/contracts/cwd-core/.cargo/schema/voting_module_response.json new file mode 100644 index 00000000..fc0cb679 --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/voting_module_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingModuleResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/cwd-core/.cargo/schema/voting_power_at_height_response.json b/contracts/cwd-core/.cargo/schema/voting_power_at_height_response.json new file mode 100644 index 00000000..15e986bf --- /dev/null +++ b/contracts/cwd-core/.cargo/schema/voting_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/.gitignore b/contracts/cwd-core/.gitignore new file mode 100644 index 00000000..dfdaaa6b --- /dev/null +++ b/contracts/cwd-core/.gitignore @@ -0,0 +1,15 @@ +# Build results +/target + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/contracts/cwd-core/Cargo.toml b/contracts/cwd-core/Cargo.toml new file mode 100644 index 00000000..5438a276 --- /dev/null +++ b/contracts/cwd-core/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "cwd-core" +version = "0.2.0" +authors = ["ekez "] +edition = "2021" +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A DAO DAO core module." + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +neutron_bindings = { package = "neutron-sdk", version = "0.1.0", git = "https://github.com/neutron-org/neutron-contracts.git" } +cosmwasm-std = { version = "1.0.0", features = ["ibc3"] } +cosmwasm-storage = { version = "1.0.0" } +cw-storage-plus = "0.13" +cw2 = "0.13" +cw-utils = "0.13" +cw20 = "0.13" +cw721 = "0.13" +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +cwd-interface = { path = "../../packages/cwd-interface" } +cwd-macros = { path = "../../packages/cwd-macros" } +cw-paginate = { path = "../../packages/cw-paginate" } +cw-core-v1 = { package = "cw-core", version = "0.1.0", git = "https://github.com/DA0-DA0/dao-contracts.git", tag = "v1.0.0" } + +[dev-dependencies] +cosmwasm-schema = { version = "1.0.0" } +#cw-multi-test = { version = "0.14", features = ["stargate"] } +#cw20-base = "0.13" +#cw721-base = "0.13" +#cwd-proposal-sudo = { path = "../../test-contracts/cwd-proposal-sudo"} +#cwd-voting-cw20-balance = { path = "../../test-contracts/cwd-voting-cw20-balance"} diff --git a/contracts/cwd-core/README b/contracts/cwd-core/README new file mode 100644 index 00000000..ed7c73be --- /dev/null +++ b/contracts/cwd-core/README @@ -0,0 +1,14 @@ +# cwd-core + +This contract is the core module for all DAO DAO DAOs. It handles +management of voting power and proposal modules, executes messages, +and holds the DAO's treasury. + +For more information about how these modules fit together see +[this](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-v1-Contracts-Design) +wiki page. + +In additon to the wiki spec this contract may also pause. To do so a +`Pause` message must by executed by a proposal module. Pausing the +core module will stop all actions on the module for the duration of +the pause. diff --git a/contracts/cwd-core/examples/schema.rs b/contracts/cwd-core/examples/schema.rs new file mode 100644 index 00000000..113ae1f5 --- /dev/null +++ b/contracts/cwd-core/examples/schema.rs @@ -0,0 +1,63 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_std::Addr; +use cwd_core::{ + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + query::{ + AdminNominationResponse, Cw20BalanceResponse, DumpStateResponse, GetItemResponse, + PauseInfoResponse, SubDao, + }, + state::{Config, ProposalModule}, +}; +use cwd_interface::voting::{ + InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(MigrateMsg), &out_dir); + + export_schema(&schema_for!(DumpStateResponse), &out_dir); + export_schema(&schema_for!(PauseInfoResponse), &out_dir); + export_schema(&schema_for!(GetItemResponse), &out_dir); + export_schema(&schema_for!(InfoResponse), &out_dir); + export_schema(&schema_for!(TotalPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(VotingPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(AdminNominationResponse), &out_dir); + export_schema(&schema_for!(SubDao), &out_dir); + + // Auto TS code generation expects the query return type as QueryNameResponse + // Here we map query responses to the correct name + export_schema_with_title(&schema_for!(Option), &out_dir, "AdminResponse"); + export_schema_with_title(&schema_for!(Option), &out_dir, "DaoURIResponse"); + export_schema_with_title(&schema_for!(Config), &out_dir, "ConfigResponse"); + export_schema_with_title( + &schema_for!(Cw20BalanceResponse), + &out_dir, + "Cw20BalancesResponse", + ); + export_schema_with_title(&schema_for!(Vec), &out_dir, "Cw20TokenListResponse"); + export_schema_with_title(&schema_for!(Vec), &out_dir, "Cw721TokenListResponse"); + export_schema_with_title(&schema_for!(Vec), &out_dir, "ListItemsResponse"); + export_schema_with_title(&schema_for!(Addr), &out_dir, "VotingModuleResponse"); + export_schema_with_title( + &schema_for!(Vec), + &out_dir, + "ProposalModulesResponse", + ); + export_schema_with_title( + &schema_for!(Vec), + &out_dir, + "ActiveProposalModulesResponse", + ); + export_schema_with_title(&schema_for!(Vec), &out_dir, "ListSubDaosResponse"); +} diff --git a/contracts/cwd-core/examples/schema/active_proposal_modules_response.json b/contracts/cwd-core/examples/schema/active_proposal_modules_response.json new file mode 100644 index 00000000..064e8b4e --- /dev/null +++ b/contracts/cwd-core/examples/schema/active_proposal_modules_response.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ActiveProposalModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + } + } +} diff --git a/contracts/cwd-core/examples/schema/admin_nomination_response.json b/contracts/cwd-core/examples/schema/admin_nomination_response.json new file mode 100644 index 00000000..7e2b4af9 --- /dev/null +++ b/contracts/cwd-core/examples/schema/admin_nomination_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminNominationResponse", + "description": "Returned by the `AdminNomination` query.", + "type": "object", + "properties": { + "nomination": { + "description": "The currently nominated admin or None if no nomination is pending.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/examples/schema/admin_response.json b/contracts/cwd-core/examples/schema/admin_response.json new file mode 100644 index 00000000..794c4351 --- /dev/null +++ b/contracts/cwd-core/examples/schema/admin_response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/examples/schema/config_response.json b/contracts/cwd-core/examples/schema/config_response.json new file mode 100644 index 00000000..f0f3d2fc --- /dev/null +++ b/contracts/cwd-core/examples/schema/config_response.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/examples/schema/cw20_balances_response.json b/contracts/cwd-core/examples/schema/cw20_balances_response.json new file mode 100644 index 00000000..34300995 --- /dev/null +++ b/contracts/cwd-core/examples/schema/cw20_balances_response.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20BalancesResponse", + "description": "Returned by the `Cw20Balances` query.", + "type": "object", + "required": [ + "addr", + "balance" + ], + "properties": { + "addr": { + "description": "The address of the token.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "balance": { + "description": "The contract's balance.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/examples/schema/cw20_token_list_response.json b/contracts/cwd-core/examples/schema/cw20_token_list_response.json new file mode 100644 index 00000000..20fb4848 --- /dev/null +++ b/contracts/cwd-core/examples/schema/cw20_token_list_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20TokenListResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/examples/schema/cw721_token_list_response.json b/contracts/cwd-core/examples/schema/cw721_token_list_response.json new file mode 100644 index 00000000..9ca019ec --- /dev/null +++ b/contracts/cwd-core/examples/schema/cw721_token_list_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw721TokenListResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/examples/schema/dao_u_r_i_response.json b/contracts/cwd-core/examples/schema/dao_u_r_i_response.json new file mode 100644 index 00000000..2b9566ad --- /dev/null +++ b/contracts/cwd-core/examples/schema/dao_u_r_i_response.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoURIResponse", + "type": [ + "string", + "null" + ] +} diff --git a/contracts/cwd-core/examples/schema/dump_state_response.json b/contracts/cwd-core/examples/schema/dump_state_response.json new file mode 100644 index 00000000..1543ad72 --- /dev/null +++ b/contracts/cwd-core/examples/schema/dump_state_response.json @@ -0,0 +1,245 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DumpStateResponse", + "description": "Relevant state for the governance module. Returned by the `DumpState` query.", + "type": "object", + "required": [ + "active_proposal_module_count", + "config", + "pause_info", + "proposal_modules", + "total_proposal_module_count", + "version", + "voting_module" + ], + "properties": { + "active_proposal_module_count": { + "description": "The number of active proposal modules.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "config": { + "description": "The governance contract's config.", + "allOf": [ + { + "$ref": "#/definitions/Config" + } + ] + }, + "pause_info": { + "$ref": "#/definitions/PauseInfoResponse" + }, + "proposal_modules": { + "description": "The governance modules associated with the governance contract.", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + } + }, + "total_proposal_module_count": { + "description": "The total number of proposal modules.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "version": { + "description": "The governance contract's version.", + "allOf": [ + { + "$ref": "#/definitions/ContractVersion" + } + ] + }, + "voting_module": { + "description": "The voting module associated with the governance contract.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Config": { + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } + }, + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "PauseInfoResponse": { + "description": "Information about if the contract is currently paused.", + "oneOf": [ + { + "type": "object", + "required": [ + "Paused" + ], + "properties": { + "Paused": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "Unpaused" + ], + "properties": { + "Unpaused": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/examples/schema/execute_msg.json b/contracts/cwd-core/examples/schema/execute_msg.json new file mode 100644 index 00000000..be8ebdde --- /dev/null +++ b/contracts/cwd-core/examples/schema/execute_msg.json @@ -0,0 +1,1506 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Callable by proposal modules. The DAO will execute the messages in the hook in order.", + "type": "object", + "required": [ + "execute_proposal_hook" + ], + "properties": { + "execute_proposal_hook": { + "type": "object", + "required": [ + "msgs" + ], + "properties": { + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_NeutronMsg" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Pauses the DAO for a set duration. When paused the DAO is unable to execute proposals", + "type": "object", + "required": [ + "pause" + ], + "properties": { + "pause": { + "type": "object", + "required": [ + "duration" + ], + "properties": { + "duration": { + "$ref": "#/definitions/Duration" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Removes an item from the governance contract's item map.", + "type": "object", + "required": [ + "remove_item" + ], + "properties": { + "remove_item": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Adds an item to the governance contract's item map. If the item already exists the existing value is overriden. If the item does not exist a new item is added.", + "type": "object", + "required": [ + "set_item" + ], + "properties": { + "set_item": { + "type": "object", + "required": [ + "addr", + "key" + ], + "properties": { + "addr": { + "type": "string" + }, + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callable by the core contract. Replaces the current governance contract config with the provided config.", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/Config" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Updates the governance contract's governance modules. Module instantiate info in `to_add` is used to create new modules and install them.", + "type": "object", + "required": [ + "update_proposal_modules" + ], + "properties": { + "update_proposal_modules": { + "type": "object", + "required": [ + "to_add", + "to_disable" + ], + "properties": { + "to_add": { + "type": "array", + "items": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + }, + "to_disable": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callable by the core contract. Replaces the current voting module with a new one instantiated by the governance contract.", + "type": "object", + "required": [ + "update_voting_module" + ], + "properties": { + "update_voting_module": { + "type": "object", + "required": [ + "module" + ], + "properties": { + "module": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Update the core module to add/remove SubDAOs and their charters", + "type": "object", + "required": [ + "update_sub_daos" + ], + "properties": { + "update_sub_daos": { + "type": "object", + "required": [ + "to_add", + "to_remove" + ], + "properties": { + "to_add": { + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + } + }, + "to_remove": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "AdminProposal": { + "description": "AdminProposal defines the struct for various proposals which Neutron's Admin Module may accept. Currently only parameter change proposals are implemented, new types of admin proposals may be implemented in future.", + "type": "object", + "properties": { + "param_change_proposal": { + "description": "*param_change_proposal** is a parameter change proposal field.", + "anyOf": [ + { + "$ref": "#/definitions/ParamChangeProposal" + }, + { + "type": "null" + } + ] + } + } + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Config": { + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } + }, + "CosmosMsg_for_NeutronMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/NeutronMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/VoteOption" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcFee": { + "type": "object", + "required": [ + "ack_fee", + "recv_fee", + "timeout_fee" + ], + "properties": { + "ack_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recv_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "timeout_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "KVKey": { + "description": "Describes a KV key for which you want to get value from the storage on remote chain", + "type": "object", + "required": [ + "key", + "path" + ], + "properties": { + "key": { + "description": "*key** is a key you want to read from the storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "*path** is a path to the storage (storage prefix) where you want to read value by key (usually name of cosmos-sdk module: 'staking', 'bank', etc.)", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "NeutronMsg": { + "description": "A number of Custom messages that can call into the Neutron bindings.", + "oneOf": [ + { + "description": "RegisterInterchainAccount registers an interchain account on remote chain.", + "type": "object", + "required": [ + "register_interchain_account" + ], + "properties": { + "register_interchain_account": { + "type": "object", + "required": [ + "connection_id", + "interchain_account_id" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "interchain_account_id": { + "description": "**interchain_account_id** is an identifier of your new interchain account. Can be any string. This identifier allows contracts to have multiple interchain accounts on remote chains.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitTx starts the process of executing any Cosmos-SDK *msgs* on remote chain.", + "type": "object", + "required": [ + "submit_tx" + ], + "properties": { + "submit_tx": { + "type": "object", + "required": [ + "connection_id", + "fee", + "interchain_account_id", + "memo", + "msgs", + "timeout" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "fee": { + "description": "**fee** is an ibc fee for the transaction.", + "allOf": [ + { + "$ref": "#/definitions/IbcFee" + } + ] + }, + "interchain_account_id": { + "description": "*interchain_account_id** is an identifier of your interchain account from which you want to execute msgs.", + "type": "string" + }, + "memo": { + "description": "*memo** is a memo you want to attach to your interchain transaction.It behaves like a memo in usual Cosmos transaction.", + "type": "string" + }, + "msgs": { + "description": "*msgs** is a list of protobuf encoded Cosmos-SDK messages you want to execute on remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/ProtobufAny" + } + }, + "timeout": { + "description": "*timeout** is a timeout in seconds after which the packet times out.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery registers an interchain query.", + "type": "object", + "required": [ + "register_interchain_query" + ], + "properties": { + "register_interchain_query": { + "type": "object", + "required": [ + "connection_id", + "keys", + "query_type", + "transactions_filter", + "update_period" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "keys": { + "description": "*keys** is the KV-storage keys for which we want to get values from remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "query_type": { + "description": "*query_type** is a query type identifier ('tx' or 'kv' for now).", + "type": "string" + }, + "transactions_filter": { + "description": "*transactions_filter** is the filter for transaction search ICQ.", + "type": "string" + }, + "update_period": { + "description": "*update_period** is used to say how often the query must be updated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery updates an interchain query.", + "type": "object", + "required": [ + "update_interchain_query" + ], + "properties": { + "update_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "new_keys": { + "description": "*new_keys** is the new query keys to retrive.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "new_transactions_filter": { + "description": "*new_transactions_filter** is a new transactions filter of the query.", + "type": [ + "string", + "null" + ] + }, + "new_update_period": { + "description": "*new_update_period** is a new update period of the query.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "query_id": { + "description": "*query_id** is the ID of the query we want to update.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RemoveInterchainQuery removes as interchain query.", + "type": "object", + "required": [ + "remove_interchain_query" + ], + "properties": { + "remove_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "description": "*query_id** is ID of the query we want to remove.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "IbcTransfer sends a fungible token packet over IBC.", + "type": "object", + "required": [ + "ibc_transfer" + ], + "properties": { + "ibc_transfer": { + "type": "object", + "required": [ + "fee", + "receiver", + "sender", + "source_channel", + "source_port", + "timeout_height", + "timeout_timestamp", + "token" + ], + "properties": { + "fee": { + "$ref": "#/definitions/IbcFee" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "source_channel": { + "type": "string" + }, + "source_port": { + "type": "string" + }, + "timeout_height": { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + "timeout_timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitAdminProposal sends a proposal to neutron's Admin module. This type of messages can be only executed by Neutron DAO.", + "type": "object", + "required": [ + "submit_admin_proposal" + ], + "properties": { + "submit_admin_proposal": { + "type": "object", + "required": [ + "admin_proposal" + ], + "properties": { + "admin_proposal": { + "$ref": "#/definitions/AdminProposal" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines the struct for parameter change request.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "description": "*key** is a name of parameter. Unique for subspace.", + "type": "string" + }, + "subspace": { + "description": "*subspace** is a key of module to which the parameter to change belongs. Unique for each module.", + "type": "string" + }, + "value": { + "description": "*value** is a new value for given parameter. Non unique.", + "type": "string" + } + } + }, + "ParamChangeProposal": { + "description": "ParamChangeProposal defines the struct for single parameter change proposal.", + "type": "object", + "required": [ + "description", + "param_changes", + "title" + ], + "properties": { + "description": { + "description": "*descriptionr** is a text description of proposal. Non unique.", + "type": "string" + }, + "param_changes": { + "description": "*param_changes** is a vector of params to be changed. Non unique.", + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + }, + "title": { + "description": "*title** is a text title of proposal. Non unique.", + "type": "string" + } + } + }, + "ProtobufAny": { + "description": "Type for wrapping any protobuf message", + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "description": "*type_url** describes the type of the serialized message", + "type": "string" + }, + "value": { + "description": "*value** must be a valid serialized protocol buffer of the above specified type", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/cwd-core/examples/schema/get_item_response.json b/contracts/cwd-core/examples/schema/get_item_response.json new file mode 100644 index 00000000..11fd7904 --- /dev/null +++ b/contracts/cwd-core/examples/schema/get_item_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetItemResponse", + "description": "Returned by the `GetItem` query.", + "type": "object", + "properties": { + "item": { + "description": "`None` if no item with the provided key was found, `Some` otherwise.", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/cwd-core/examples/schema/info_response.json b/contracts/cwd-core/examples/schema/info_response.json new file mode 100644 index 00000000..a0516764 --- /dev/null +++ b/contracts/cwd-core/examples/schema/info_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + } + } +} diff --git a/contracts/cwd-core/examples/schema/instantiate_msg.json b/contracts/cwd-core/examples/schema/instantiate_msg.json new file mode 100644 index 00000000..efec760e --- /dev/null +++ b/contracts/cwd-core/examples/schema/instantiate_msg.json @@ -0,0 +1,156 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "description", + "name", + "proposal_modules_instantiate_info", + "voting_registry_module_instantiate_info" + ], + "properties": { + "dao_uri": { + "description": "Implements the DAO Star standard: https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the core contract.", + "type": "string" + }, + "initial_items": { + "description": "Initial information for arbitrary contract addresses to be added to the items map. The key is the name of the item in the items map. The value is an enum that either uses an existing address or instantiates a new contract.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/InitialItem" + } + }, + "name": { + "description": "The name of the core contract.", + "type": "string" + }, + "proposal_modules_instantiate_info": { + "description": "Instantiate information for the core contract's proposal modules.", + "type": "array", + "items": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + }, + "voting_registry_module_instantiate_info": { + "description": "Instantiate information for the core contract's voting power module.", + "allOf": [ + { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + ] + } + }, + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "InitialItem": { + "description": "Information about an item to be stored in the items list.", + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "description": "The name of the item.", + "type": "string" + }, + "value": { + "description": "The value the item will have at instantiation time.", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + } +} diff --git a/contracts/cwd-core/examples/schema/list_items_response.json b/contracts/cwd-core/examples/schema/list_items_response.json new file mode 100644 index 00000000..9b743544 --- /dev/null +++ b/contracts/cwd-core/examples/schema/list_items_response.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListItemsResponse", + "type": "array", + "items": { + "type": "string" + } +} diff --git a/contracts/cwd-core/examples/schema/list_sub_daos_response.json b/contracts/cwd-core/examples/schema/list_sub_daos_response.json new file mode 100644 index 00000000..69a1e8bc --- /dev/null +++ b/contracts/cwd-core/examples/schema/list_sub_daos_response.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListSubDaosResponse", + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + }, + "definitions": { + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } + } + } +} diff --git a/contracts/cwd-core/examples/schema/migrate_msg.json b/contracts/cwd-core/examples/schema/migrate_msg.json new file mode 100644 index 00000000..6b9264c8 --- /dev/null +++ b/contracts/cwd-core/examples/schema/migrate_msg.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "from_v1" + ], + "properties": { + "from_v1": { + "type": "object", + "properties": { + "dao_uri": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "from_compatible" + ], + "properties": { + "from_compatible": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cwd-core/examples/schema/pause_info_response.json b/contracts/cwd-core/examples/schema/pause_info_response.json new file mode 100644 index 00000000..eb8e1aeb --- /dev/null +++ b/contracts/cwd-core/examples/schema/pause_info_response.json @@ -0,0 +1,99 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PauseInfoResponse", + "description": "Information about if the contract is currently paused.", + "oneOf": [ + { + "type": "object", + "required": [ + "Paused" + ], + "properties": { + "Paused": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "Unpaused" + ], + "properties": { + "Unpaused": { + "type": "object" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/examples/schema/proposal_modules_response.json b/contracts/cwd-core/examples/schema/proposal_modules_response.json new file mode 100644 index 00000000..c2940218 --- /dev/null +++ b/contracts/cwd-core/examples/schema/proposal_modules_response.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + } + } +} diff --git a/contracts/cwd-core/examples/schema/query_msg.json b/contracts/cwd-core/examples/schema/query_msg.json new file mode 100644 index 00000000..2692c274 --- /dev/null +++ b/contracts/cwd-core/examples/schema/query_msg.json @@ -0,0 +1,270 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Gets the contract's config. Returns Config.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Dumps all of the core contract's state in a single query. Useful for frontends as performance for queries is more limited by network times than compute times. Returns `DumpStateResponse`.", + "type": "object", + "required": [ + "dump_state" + ], + "properties": { + "dump_state": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the address associated with an item key.", + "type": "object", + "required": [ + "get_item" + ], + "properties": { + "get_item": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Lists all of the items associted with the contract. For example, given the items `{ \"group\": \"foo\", \"subdao\": \"bar\"}` this query would return `[(\"group\", \"foo\"), (\"subdao\", \"bar\")]`.", + "type": "object", + "required": [ + "list_items" + ], + "properties": { + "list_items": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets all proposal modules associated with the contract. Returns Vec.", + "type": "object", + "required": [ + "proposal_modules" + ], + "properties": { + "proposal_modules": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets the active proposal modules associated with the contract. Returns Vec.", + "type": "object", + "required": [ + "active_proposal_modules" + ], + "properties": { + "active_proposal_modules": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about if the contract is currently paused.", + "type": "object", + "required": [ + "pause_info" + ], + "properties": { + "pause_info": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the contract's voting module. Returns Addr.", + "type": "object", + "required": [ + "voting_module" + ], + "properties": { + "voting_module": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Returns all SubDAOs with their charters in a vec start_after is bound exclusive and asks for a string address", + "type": "object", + "required": [ + "list_sub_daos" + ], + "properties": { + "list_sub_daos": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Implements the DAO Star standard: https://daostar.one/EIP", + "type": "object", + "required": [ + "dao_u_r_i" + ], + "properties": { + "dao_u_r_i": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_power_at_height" + ], + "properties": { + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_power_at_height" + ], + "properties": { + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cwd-core/examples/schema/sub_dao.json b/contracts/cwd-core/examples/schema/sub_dao.json new file mode 100644 index 00000000..ae12c03a --- /dev/null +++ b/contracts/cwd-core/examples/schema/sub_dao.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SubDao", + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/cwd-core/examples/schema/total_power_at_height_response.json b/contracts/cwd-core/examples/schema/total_power_at_height_response.json new file mode 100644 index 00000000..8018462b --- /dev/null +++ b/contracts/cwd-core/examples/schema/total_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/examples/schema/voting_module_response.json b/contracts/cwd-core/examples/schema/voting_module_response.json new file mode 100644 index 00000000..fc0cb679 --- /dev/null +++ b/contracts/cwd-core/examples/schema/voting_module_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingModuleResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/cwd-core/examples/schema/voting_power_at_height_response.json b/contracts/cwd-core/examples/schema/voting_power_at_height_response.json new file mode 100644 index 00000000..15e986bf --- /dev/null +++ b/contracts/cwd-core/examples/schema/voting_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/active_proposal_modules_response.json b/contracts/cwd-core/schema/active_proposal_modules_response.json new file mode 100644 index 00000000..064e8b4e --- /dev/null +++ b/contracts/cwd-core/schema/active_proposal_modules_response.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ActiveProposalModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + } + } +} diff --git a/contracts/cwd-core/schema/admin_nomination_response.json b/contracts/cwd-core/schema/admin_nomination_response.json new file mode 100644 index 00000000..7e2b4af9 --- /dev/null +++ b/contracts/cwd-core/schema/admin_nomination_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminNominationResponse", + "description": "Returned by the `AdminNomination` query.", + "type": "object", + "properties": { + "nomination": { + "description": "The currently nominated admin or None if no nomination is pending.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/admin_response.json b/contracts/cwd-core/schema/admin_response.json new file mode 100644 index 00000000..794c4351 --- /dev/null +++ b/contracts/cwd-core/schema/admin_response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/config_response.json b/contracts/cwd-core/schema/config_response.json new file mode 100644 index 00000000..f0f3d2fc --- /dev/null +++ b/contracts/cwd-core/schema/config_response.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/cw20_balances_response.json b/contracts/cwd-core/schema/cw20_balances_response.json new file mode 100644 index 00000000..34300995 --- /dev/null +++ b/contracts/cwd-core/schema/cw20_balances_response.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20BalancesResponse", + "description": "Returned by the `Cw20Balances` query.", + "type": "object", + "required": [ + "addr", + "balance" + ], + "properties": { + "addr": { + "description": "The address of the token.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "balance": { + "description": "The contract's balance.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/cw20_token_list_response.json b/contracts/cwd-core/schema/cw20_token_list_response.json new file mode 100644 index 00000000..20fb4848 --- /dev/null +++ b/contracts/cwd-core/schema/cw20_token_list_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20TokenListResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/cw721_token_list_response.json b/contracts/cwd-core/schema/cw721_token_list_response.json new file mode 100644 index 00000000..9ca019ec --- /dev/null +++ b/contracts/cwd-core/schema/cw721_token_list_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw721TokenListResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/dao_u_r_i_response.json b/contracts/cwd-core/schema/dao_u_r_i_response.json new file mode 100644 index 00000000..2b9566ad --- /dev/null +++ b/contracts/cwd-core/schema/dao_u_r_i_response.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoURIResponse", + "type": [ + "string", + "null" + ] +} diff --git a/contracts/cwd-core/schema/dump_state_response.json b/contracts/cwd-core/schema/dump_state_response.json new file mode 100644 index 00000000..1543ad72 --- /dev/null +++ b/contracts/cwd-core/schema/dump_state_response.json @@ -0,0 +1,245 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DumpStateResponse", + "description": "Relevant state for the governance module. Returned by the `DumpState` query.", + "type": "object", + "required": [ + "active_proposal_module_count", + "config", + "pause_info", + "proposal_modules", + "total_proposal_module_count", + "version", + "voting_module" + ], + "properties": { + "active_proposal_module_count": { + "description": "The number of active proposal modules.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "config": { + "description": "The governance contract's config.", + "allOf": [ + { + "$ref": "#/definitions/Config" + } + ] + }, + "pause_info": { + "$ref": "#/definitions/PauseInfoResponse" + }, + "proposal_modules": { + "description": "The governance modules associated with the governance contract.", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + } + }, + "total_proposal_module_count": { + "description": "The total number of proposal modules.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "version": { + "description": "The governance contract's version.", + "allOf": [ + { + "$ref": "#/definitions/ContractVersion" + } + ] + }, + "voting_module": { + "description": "The voting module associated with the governance contract.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Config": { + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } + }, + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "PauseInfoResponse": { + "description": "Information about if the contract is currently paused.", + "oneOf": [ + { + "type": "object", + "required": [ + "Paused" + ], + "properties": { + "Paused": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "Unpaused" + ], + "properties": { + "Unpaused": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/execute_msg.json b/contracts/cwd-core/schema/execute_msg.json new file mode 100644 index 00000000..be8ebdde --- /dev/null +++ b/contracts/cwd-core/schema/execute_msg.json @@ -0,0 +1,1506 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Callable by proposal modules. The DAO will execute the messages in the hook in order.", + "type": "object", + "required": [ + "execute_proposal_hook" + ], + "properties": { + "execute_proposal_hook": { + "type": "object", + "required": [ + "msgs" + ], + "properties": { + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_NeutronMsg" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Pauses the DAO for a set duration. When paused the DAO is unable to execute proposals", + "type": "object", + "required": [ + "pause" + ], + "properties": { + "pause": { + "type": "object", + "required": [ + "duration" + ], + "properties": { + "duration": { + "$ref": "#/definitions/Duration" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Removes an item from the governance contract's item map.", + "type": "object", + "required": [ + "remove_item" + ], + "properties": { + "remove_item": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Adds an item to the governance contract's item map. If the item already exists the existing value is overriden. If the item does not exist a new item is added.", + "type": "object", + "required": [ + "set_item" + ], + "properties": { + "set_item": { + "type": "object", + "required": [ + "addr", + "key" + ], + "properties": { + "addr": { + "type": "string" + }, + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callable by the core contract. Replaces the current governance contract config with the provided config.", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/Config" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Updates the governance contract's governance modules. Module instantiate info in `to_add` is used to create new modules and install them.", + "type": "object", + "required": [ + "update_proposal_modules" + ], + "properties": { + "update_proposal_modules": { + "type": "object", + "required": [ + "to_add", + "to_disable" + ], + "properties": { + "to_add": { + "type": "array", + "items": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + }, + "to_disable": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callable by the core contract. Replaces the current voting module with a new one instantiated by the governance contract.", + "type": "object", + "required": [ + "update_voting_module" + ], + "properties": { + "update_voting_module": { + "type": "object", + "required": [ + "module" + ], + "properties": { + "module": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Update the core module to add/remove SubDAOs and their charters", + "type": "object", + "required": [ + "update_sub_daos" + ], + "properties": { + "update_sub_daos": { + "type": "object", + "required": [ + "to_add", + "to_remove" + ], + "properties": { + "to_add": { + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + } + }, + "to_remove": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "AdminProposal": { + "description": "AdminProposal defines the struct for various proposals which Neutron's Admin Module may accept. Currently only parameter change proposals are implemented, new types of admin proposals may be implemented in future.", + "type": "object", + "properties": { + "param_change_proposal": { + "description": "*param_change_proposal** is a parameter change proposal field.", + "anyOf": [ + { + "$ref": "#/definitions/ParamChangeProposal" + }, + { + "type": "null" + } + ] + } + } + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Config": { + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } + }, + "CosmosMsg_for_NeutronMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/NeutronMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/VoteOption" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcFee": { + "type": "object", + "required": [ + "ack_fee", + "recv_fee", + "timeout_fee" + ], + "properties": { + "ack_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recv_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "timeout_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "KVKey": { + "description": "Describes a KV key for which you want to get value from the storage on remote chain", + "type": "object", + "required": [ + "key", + "path" + ], + "properties": { + "key": { + "description": "*key** is a key you want to read from the storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "*path** is a path to the storage (storage prefix) where you want to read value by key (usually name of cosmos-sdk module: 'staking', 'bank', etc.)", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "NeutronMsg": { + "description": "A number of Custom messages that can call into the Neutron bindings.", + "oneOf": [ + { + "description": "RegisterInterchainAccount registers an interchain account on remote chain.", + "type": "object", + "required": [ + "register_interchain_account" + ], + "properties": { + "register_interchain_account": { + "type": "object", + "required": [ + "connection_id", + "interchain_account_id" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "interchain_account_id": { + "description": "**interchain_account_id** is an identifier of your new interchain account. Can be any string. This identifier allows contracts to have multiple interchain accounts on remote chains.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitTx starts the process of executing any Cosmos-SDK *msgs* on remote chain.", + "type": "object", + "required": [ + "submit_tx" + ], + "properties": { + "submit_tx": { + "type": "object", + "required": [ + "connection_id", + "fee", + "interchain_account_id", + "memo", + "msgs", + "timeout" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "fee": { + "description": "**fee** is an ibc fee for the transaction.", + "allOf": [ + { + "$ref": "#/definitions/IbcFee" + } + ] + }, + "interchain_account_id": { + "description": "*interchain_account_id** is an identifier of your interchain account from which you want to execute msgs.", + "type": "string" + }, + "memo": { + "description": "*memo** is a memo you want to attach to your interchain transaction.It behaves like a memo in usual Cosmos transaction.", + "type": "string" + }, + "msgs": { + "description": "*msgs** is a list of protobuf encoded Cosmos-SDK messages you want to execute on remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/ProtobufAny" + } + }, + "timeout": { + "description": "*timeout** is a timeout in seconds after which the packet times out.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery registers an interchain query.", + "type": "object", + "required": [ + "register_interchain_query" + ], + "properties": { + "register_interchain_query": { + "type": "object", + "required": [ + "connection_id", + "keys", + "query_type", + "transactions_filter", + "update_period" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "keys": { + "description": "*keys** is the KV-storage keys for which we want to get values from remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "query_type": { + "description": "*query_type** is a query type identifier ('tx' or 'kv' for now).", + "type": "string" + }, + "transactions_filter": { + "description": "*transactions_filter** is the filter for transaction search ICQ.", + "type": "string" + }, + "update_period": { + "description": "*update_period** is used to say how often the query must be updated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery updates an interchain query.", + "type": "object", + "required": [ + "update_interchain_query" + ], + "properties": { + "update_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "new_keys": { + "description": "*new_keys** is the new query keys to retrive.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "new_transactions_filter": { + "description": "*new_transactions_filter** is a new transactions filter of the query.", + "type": [ + "string", + "null" + ] + }, + "new_update_period": { + "description": "*new_update_period** is a new update period of the query.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "query_id": { + "description": "*query_id** is the ID of the query we want to update.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RemoveInterchainQuery removes as interchain query.", + "type": "object", + "required": [ + "remove_interchain_query" + ], + "properties": { + "remove_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "description": "*query_id** is ID of the query we want to remove.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "IbcTransfer sends a fungible token packet over IBC.", + "type": "object", + "required": [ + "ibc_transfer" + ], + "properties": { + "ibc_transfer": { + "type": "object", + "required": [ + "fee", + "receiver", + "sender", + "source_channel", + "source_port", + "timeout_height", + "timeout_timestamp", + "token" + ], + "properties": { + "fee": { + "$ref": "#/definitions/IbcFee" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "source_channel": { + "type": "string" + }, + "source_port": { + "type": "string" + }, + "timeout_height": { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + "timeout_timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitAdminProposal sends a proposal to neutron's Admin module. This type of messages can be only executed by Neutron DAO.", + "type": "object", + "required": [ + "submit_admin_proposal" + ], + "properties": { + "submit_admin_proposal": { + "type": "object", + "required": [ + "admin_proposal" + ], + "properties": { + "admin_proposal": { + "$ref": "#/definitions/AdminProposal" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines the struct for parameter change request.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "description": "*key** is a name of parameter. Unique for subspace.", + "type": "string" + }, + "subspace": { + "description": "*subspace** is a key of module to which the parameter to change belongs. Unique for each module.", + "type": "string" + }, + "value": { + "description": "*value** is a new value for given parameter. Non unique.", + "type": "string" + } + } + }, + "ParamChangeProposal": { + "description": "ParamChangeProposal defines the struct for single parameter change proposal.", + "type": "object", + "required": [ + "description", + "param_changes", + "title" + ], + "properties": { + "description": { + "description": "*descriptionr** is a text description of proposal. Non unique.", + "type": "string" + }, + "param_changes": { + "description": "*param_changes** is a vector of params to be changed. Non unique.", + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + }, + "title": { + "description": "*title** is a text title of proposal. Non unique.", + "type": "string" + } + } + }, + "ProtobufAny": { + "description": "Type for wrapping any protobuf message", + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "description": "*type_url** describes the type of the serialized message", + "type": "string" + }, + "value": { + "description": "*value** must be a valid serialized protocol buffer of the above specified type", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/cwd-core/schema/get_item_response.json b/contracts/cwd-core/schema/get_item_response.json new file mode 100644 index 00000000..11fd7904 --- /dev/null +++ b/contracts/cwd-core/schema/get_item_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetItemResponse", + "description": "Returned by the `GetItem` query.", + "type": "object", + "properties": { + "item": { + "description": "`None` if no item with the provided key was found, `Some` otherwise.", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/cwd-core/schema/info_response.json b/contracts/cwd-core/schema/info_response.json new file mode 100644 index 00000000..a0516764 --- /dev/null +++ b/contracts/cwd-core/schema/info_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + } + } +} diff --git a/contracts/cwd-core/schema/instantiate_msg.json b/contracts/cwd-core/schema/instantiate_msg.json new file mode 100644 index 00000000..efec760e --- /dev/null +++ b/contracts/cwd-core/schema/instantiate_msg.json @@ -0,0 +1,156 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "description", + "name", + "proposal_modules_instantiate_info", + "voting_registry_module_instantiate_info" + ], + "properties": { + "dao_uri": { + "description": "Implements the DAO Star standard: https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the core contract.", + "type": "string" + }, + "initial_items": { + "description": "Initial information for arbitrary contract addresses to be added to the items map. The key is the name of the item in the items map. The value is an enum that either uses an existing address or instantiates a new contract.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/InitialItem" + } + }, + "name": { + "description": "The name of the core contract.", + "type": "string" + }, + "proposal_modules_instantiate_info": { + "description": "Instantiate information for the core contract's proposal modules.", + "type": "array", + "items": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + }, + "voting_registry_module_instantiate_info": { + "description": "Instantiate information for the core contract's voting power module.", + "allOf": [ + { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + ] + } + }, + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "InitialItem": { + "description": "Information about an item to be stored in the items list.", + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "description": "The name of the item.", + "type": "string" + }, + "value": { + "description": "The value the item will have at instantiation time.", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + } +} diff --git a/contracts/cwd-core/schema/list_items_response.json b/contracts/cwd-core/schema/list_items_response.json new file mode 100644 index 00000000..9b743544 --- /dev/null +++ b/contracts/cwd-core/schema/list_items_response.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListItemsResponse", + "type": "array", + "items": { + "type": "string" + } +} diff --git a/contracts/cwd-core/schema/list_sub_daos_response.json b/contracts/cwd-core/schema/list_sub_daos_response.json new file mode 100644 index 00000000..69a1e8bc --- /dev/null +++ b/contracts/cwd-core/schema/list_sub_daos_response.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListSubDaosResponse", + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + }, + "definitions": { + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } + } + } +} diff --git a/contracts/cwd-core/schema/migrate_msg.json b/contracts/cwd-core/schema/migrate_msg.json new file mode 100644 index 00000000..6b9264c8 --- /dev/null +++ b/contracts/cwd-core/schema/migrate_msg.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "from_v1" + ], + "properties": { + "from_v1": { + "type": "object", + "properties": { + "dao_uri": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "from_compatible" + ], + "properties": { + "from_compatible": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cwd-core/schema/pause_info_response.json b/contracts/cwd-core/schema/pause_info_response.json new file mode 100644 index 00000000..eb8e1aeb --- /dev/null +++ b/contracts/cwd-core/schema/pause_info_response.json @@ -0,0 +1,99 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PauseInfoResponse", + "description": "Information about if the contract is currently paused.", + "oneOf": [ + { + "type": "object", + "required": [ + "Paused" + ], + "properties": { + "Paused": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "Unpaused" + ], + "properties": { + "Unpaused": { + "type": "object" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/proposal_modules_response.json b/contracts/cwd-core/schema/proposal_modules_response.json new file mode 100644 index 00000000..c2940218 --- /dev/null +++ b/contracts/cwd-core/schema/proposal_modules_response.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + } + } +} diff --git a/contracts/cwd-core/schema/query_msg.json b/contracts/cwd-core/schema/query_msg.json new file mode 100644 index 00000000..2692c274 --- /dev/null +++ b/contracts/cwd-core/schema/query_msg.json @@ -0,0 +1,270 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Gets the contract's config. Returns Config.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Dumps all of the core contract's state in a single query. Useful for frontends as performance for queries is more limited by network times than compute times. Returns `DumpStateResponse`.", + "type": "object", + "required": [ + "dump_state" + ], + "properties": { + "dump_state": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the address associated with an item key.", + "type": "object", + "required": [ + "get_item" + ], + "properties": { + "get_item": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Lists all of the items associted with the contract. For example, given the items `{ \"group\": \"foo\", \"subdao\": \"bar\"}` this query would return `[(\"group\", \"foo\"), (\"subdao\", \"bar\")]`.", + "type": "object", + "required": [ + "list_items" + ], + "properties": { + "list_items": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets all proposal modules associated with the contract. Returns Vec.", + "type": "object", + "required": [ + "proposal_modules" + ], + "properties": { + "proposal_modules": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets the active proposal modules associated with the contract. Returns Vec.", + "type": "object", + "required": [ + "active_proposal_modules" + ], + "properties": { + "active_proposal_modules": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about if the contract is currently paused.", + "type": "object", + "required": [ + "pause_info" + ], + "properties": { + "pause_info": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the contract's voting module. Returns Addr.", + "type": "object", + "required": [ + "voting_module" + ], + "properties": { + "voting_module": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Returns all SubDAOs with their charters in a vec start_after is bound exclusive and asks for a string address", + "type": "object", + "required": [ + "list_sub_daos" + ], + "properties": { + "list_sub_daos": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Implements the DAO Star standard: https://daostar.one/EIP", + "type": "object", + "required": [ + "dao_u_r_i" + ], + "properties": { + "dao_u_r_i": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_power_at_height" + ], + "properties": { + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_power_at_height" + ], + "properties": { + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cwd-core/schema/schema/active_proposal_modules_response.json b/contracts/cwd-core/schema/schema/active_proposal_modules_response.json new file mode 100644 index 00000000..064e8b4e --- /dev/null +++ b/contracts/cwd-core/schema/schema/active_proposal_modules_response.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ActiveProposalModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + } + } +} diff --git a/contracts/cwd-core/schema/schema/admin_nomination_response.json b/contracts/cwd-core/schema/schema/admin_nomination_response.json new file mode 100644 index 00000000..7e2b4af9 --- /dev/null +++ b/contracts/cwd-core/schema/schema/admin_nomination_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminNominationResponse", + "description": "Returned by the `AdminNomination` query.", + "type": "object", + "properties": { + "nomination": { + "description": "The currently nominated admin or None if no nomination is pending.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/schema/admin_response.json b/contracts/cwd-core/schema/schema/admin_response.json new file mode 100644 index 00000000..794c4351 --- /dev/null +++ b/contracts/cwd-core/schema/schema/admin_response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/schema/config_response.json b/contracts/cwd-core/schema/schema/config_response.json new file mode 100644 index 00000000..f0f3d2fc --- /dev/null +++ b/contracts/cwd-core/schema/schema/config_response.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/schema/cw20_balances_response.json b/contracts/cwd-core/schema/schema/cw20_balances_response.json new file mode 100644 index 00000000..34300995 --- /dev/null +++ b/contracts/cwd-core/schema/schema/cw20_balances_response.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20BalancesResponse", + "description": "Returned by the `Cw20Balances` query.", + "type": "object", + "required": [ + "addr", + "balance" + ], + "properties": { + "addr": { + "description": "The address of the token.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "balance": { + "description": "The contract's balance.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/schema/cw20_token_list_response.json b/contracts/cwd-core/schema/schema/cw20_token_list_response.json new file mode 100644 index 00000000..20fb4848 --- /dev/null +++ b/contracts/cwd-core/schema/schema/cw20_token_list_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20TokenListResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/schema/cw721_token_list_response.json b/contracts/cwd-core/schema/schema/cw721_token_list_response.json new file mode 100644 index 00000000..9ca019ec --- /dev/null +++ b/contracts/cwd-core/schema/schema/cw721_token_list_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw721TokenListResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/schema/dao_u_r_i_response.json b/contracts/cwd-core/schema/schema/dao_u_r_i_response.json new file mode 100644 index 00000000..2b9566ad --- /dev/null +++ b/contracts/cwd-core/schema/schema/dao_u_r_i_response.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoURIResponse", + "type": [ + "string", + "null" + ] +} diff --git a/contracts/cwd-core/schema/schema/dump_state_response.json b/contracts/cwd-core/schema/schema/dump_state_response.json new file mode 100644 index 00000000..1543ad72 --- /dev/null +++ b/contracts/cwd-core/schema/schema/dump_state_response.json @@ -0,0 +1,245 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DumpStateResponse", + "description": "Relevant state for the governance module. Returned by the `DumpState` query.", + "type": "object", + "required": [ + "active_proposal_module_count", + "config", + "pause_info", + "proposal_modules", + "total_proposal_module_count", + "version", + "voting_module" + ], + "properties": { + "active_proposal_module_count": { + "description": "The number of active proposal modules.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "config": { + "description": "The governance contract's config.", + "allOf": [ + { + "$ref": "#/definitions/Config" + } + ] + }, + "pause_info": { + "$ref": "#/definitions/PauseInfoResponse" + }, + "proposal_modules": { + "description": "The governance modules associated with the governance contract.", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + } + }, + "total_proposal_module_count": { + "description": "The total number of proposal modules.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "version": { + "description": "The governance contract's version.", + "allOf": [ + { + "$ref": "#/definitions/ContractVersion" + } + ] + }, + "voting_module": { + "description": "The voting module associated with the governance contract.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Config": { + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } + }, + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "PauseInfoResponse": { + "description": "Information about if the contract is currently paused.", + "oneOf": [ + { + "type": "object", + "required": [ + "Paused" + ], + "properties": { + "Paused": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "Unpaused" + ], + "properties": { + "Unpaused": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/schema/execute_msg.json b/contracts/cwd-core/schema/schema/execute_msg.json new file mode 100644 index 00000000..be8ebdde --- /dev/null +++ b/contracts/cwd-core/schema/schema/execute_msg.json @@ -0,0 +1,1506 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Callable by proposal modules. The DAO will execute the messages in the hook in order.", + "type": "object", + "required": [ + "execute_proposal_hook" + ], + "properties": { + "execute_proposal_hook": { + "type": "object", + "required": [ + "msgs" + ], + "properties": { + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_NeutronMsg" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Pauses the DAO for a set duration. When paused the DAO is unable to execute proposals", + "type": "object", + "required": [ + "pause" + ], + "properties": { + "pause": { + "type": "object", + "required": [ + "duration" + ], + "properties": { + "duration": { + "$ref": "#/definitions/Duration" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Removes an item from the governance contract's item map.", + "type": "object", + "required": [ + "remove_item" + ], + "properties": { + "remove_item": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Adds an item to the governance contract's item map. If the item already exists the existing value is overriden. If the item does not exist a new item is added.", + "type": "object", + "required": [ + "set_item" + ], + "properties": { + "set_item": { + "type": "object", + "required": [ + "addr", + "key" + ], + "properties": { + "addr": { + "type": "string" + }, + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callable by the core contract. Replaces the current governance contract config with the provided config.", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/Config" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Updates the governance contract's governance modules. Module instantiate info in `to_add` is used to create new modules and install them.", + "type": "object", + "required": [ + "update_proposal_modules" + ], + "properties": { + "update_proposal_modules": { + "type": "object", + "required": [ + "to_add", + "to_disable" + ], + "properties": { + "to_add": { + "type": "array", + "items": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + }, + "to_disable": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callable by the core contract. Replaces the current voting module with a new one instantiated by the governance contract.", + "type": "object", + "required": [ + "update_voting_module" + ], + "properties": { + "update_voting_module": { + "type": "object", + "required": [ + "module" + ], + "properties": { + "module": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Update the core module to add/remove SubDAOs and their charters", + "type": "object", + "required": [ + "update_sub_daos" + ], + "properties": { + "update_sub_daos": { + "type": "object", + "required": [ + "to_add", + "to_remove" + ], + "properties": { + "to_add": { + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + } + }, + "to_remove": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "AdminProposal": { + "description": "AdminProposal defines the struct for various proposals which Neutron's Admin Module may accept. Currently only parameter change proposals are implemented, new types of admin proposals may be implemented in future.", + "type": "object", + "properties": { + "param_change_proposal": { + "description": "*param_change_proposal** is a parameter change proposal field.", + "anyOf": [ + { + "$ref": "#/definitions/ParamChangeProposal" + }, + { + "type": "null" + } + ] + } + } + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Config": { + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } + }, + "CosmosMsg_for_NeutronMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/NeutronMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/VoteOption" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcFee": { + "type": "object", + "required": [ + "ack_fee", + "recv_fee", + "timeout_fee" + ], + "properties": { + "ack_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recv_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "timeout_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "KVKey": { + "description": "Describes a KV key for which you want to get value from the storage on remote chain", + "type": "object", + "required": [ + "key", + "path" + ], + "properties": { + "key": { + "description": "*key** is a key you want to read from the storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "*path** is a path to the storage (storage prefix) where you want to read value by key (usually name of cosmos-sdk module: 'staking', 'bank', etc.)", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "NeutronMsg": { + "description": "A number of Custom messages that can call into the Neutron bindings.", + "oneOf": [ + { + "description": "RegisterInterchainAccount registers an interchain account on remote chain.", + "type": "object", + "required": [ + "register_interchain_account" + ], + "properties": { + "register_interchain_account": { + "type": "object", + "required": [ + "connection_id", + "interchain_account_id" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "interchain_account_id": { + "description": "**interchain_account_id** is an identifier of your new interchain account. Can be any string. This identifier allows contracts to have multiple interchain accounts on remote chains.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitTx starts the process of executing any Cosmos-SDK *msgs* on remote chain.", + "type": "object", + "required": [ + "submit_tx" + ], + "properties": { + "submit_tx": { + "type": "object", + "required": [ + "connection_id", + "fee", + "interchain_account_id", + "memo", + "msgs", + "timeout" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "fee": { + "description": "**fee** is an ibc fee for the transaction.", + "allOf": [ + { + "$ref": "#/definitions/IbcFee" + } + ] + }, + "interchain_account_id": { + "description": "*interchain_account_id** is an identifier of your interchain account from which you want to execute msgs.", + "type": "string" + }, + "memo": { + "description": "*memo** is a memo you want to attach to your interchain transaction.It behaves like a memo in usual Cosmos transaction.", + "type": "string" + }, + "msgs": { + "description": "*msgs** is a list of protobuf encoded Cosmos-SDK messages you want to execute on remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/ProtobufAny" + } + }, + "timeout": { + "description": "*timeout** is a timeout in seconds after which the packet times out.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery registers an interchain query.", + "type": "object", + "required": [ + "register_interchain_query" + ], + "properties": { + "register_interchain_query": { + "type": "object", + "required": [ + "connection_id", + "keys", + "query_type", + "transactions_filter", + "update_period" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "keys": { + "description": "*keys** is the KV-storage keys for which we want to get values from remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "query_type": { + "description": "*query_type** is a query type identifier ('tx' or 'kv' for now).", + "type": "string" + }, + "transactions_filter": { + "description": "*transactions_filter** is the filter for transaction search ICQ.", + "type": "string" + }, + "update_period": { + "description": "*update_period** is used to say how often the query must be updated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery updates an interchain query.", + "type": "object", + "required": [ + "update_interchain_query" + ], + "properties": { + "update_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "new_keys": { + "description": "*new_keys** is the new query keys to retrive.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "new_transactions_filter": { + "description": "*new_transactions_filter** is a new transactions filter of the query.", + "type": [ + "string", + "null" + ] + }, + "new_update_period": { + "description": "*new_update_period** is a new update period of the query.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "query_id": { + "description": "*query_id** is the ID of the query we want to update.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RemoveInterchainQuery removes as interchain query.", + "type": "object", + "required": [ + "remove_interchain_query" + ], + "properties": { + "remove_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "description": "*query_id** is ID of the query we want to remove.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "IbcTransfer sends a fungible token packet over IBC.", + "type": "object", + "required": [ + "ibc_transfer" + ], + "properties": { + "ibc_transfer": { + "type": "object", + "required": [ + "fee", + "receiver", + "sender", + "source_channel", + "source_port", + "timeout_height", + "timeout_timestamp", + "token" + ], + "properties": { + "fee": { + "$ref": "#/definitions/IbcFee" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "source_channel": { + "type": "string" + }, + "source_port": { + "type": "string" + }, + "timeout_height": { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + "timeout_timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitAdminProposal sends a proposal to neutron's Admin module. This type of messages can be only executed by Neutron DAO.", + "type": "object", + "required": [ + "submit_admin_proposal" + ], + "properties": { + "submit_admin_proposal": { + "type": "object", + "required": [ + "admin_proposal" + ], + "properties": { + "admin_proposal": { + "$ref": "#/definitions/AdminProposal" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines the struct for parameter change request.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "description": "*key** is a name of parameter. Unique for subspace.", + "type": "string" + }, + "subspace": { + "description": "*subspace** is a key of module to which the parameter to change belongs. Unique for each module.", + "type": "string" + }, + "value": { + "description": "*value** is a new value for given parameter. Non unique.", + "type": "string" + } + } + }, + "ParamChangeProposal": { + "description": "ParamChangeProposal defines the struct for single parameter change proposal.", + "type": "object", + "required": [ + "description", + "param_changes", + "title" + ], + "properties": { + "description": { + "description": "*descriptionr** is a text description of proposal. Non unique.", + "type": "string" + }, + "param_changes": { + "description": "*param_changes** is a vector of params to be changed. Non unique.", + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + }, + "title": { + "description": "*title** is a text title of proposal. Non unique.", + "type": "string" + } + } + }, + "ProtobufAny": { + "description": "Type for wrapping any protobuf message", + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "description": "*type_url** describes the type of the serialized message", + "type": "string" + }, + "value": { + "description": "*value** must be a valid serialized protocol buffer of the above specified type", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/cwd-core/schema/schema/get_item_response.json b/contracts/cwd-core/schema/schema/get_item_response.json new file mode 100644 index 00000000..11fd7904 --- /dev/null +++ b/contracts/cwd-core/schema/schema/get_item_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetItemResponse", + "description": "Returned by the `GetItem` query.", + "type": "object", + "properties": { + "item": { + "description": "`None` if no item with the provided key was found, `Some` otherwise.", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/cwd-core/schema/schema/info_response.json b/contracts/cwd-core/schema/schema/info_response.json new file mode 100644 index 00000000..a0516764 --- /dev/null +++ b/contracts/cwd-core/schema/schema/info_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + } + } +} diff --git a/contracts/cwd-core/schema/schema/instantiate_msg.json b/contracts/cwd-core/schema/schema/instantiate_msg.json new file mode 100644 index 00000000..efec760e --- /dev/null +++ b/contracts/cwd-core/schema/schema/instantiate_msg.json @@ -0,0 +1,156 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "description", + "name", + "proposal_modules_instantiate_info", + "voting_registry_module_instantiate_info" + ], + "properties": { + "dao_uri": { + "description": "Implements the DAO Star standard: https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the core contract.", + "type": "string" + }, + "initial_items": { + "description": "Initial information for arbitrary contract addresses to be added to the items map. The key is the name of the item in the items map. The value is an enum that either uses an existing address or instantiates a new contract.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/InitialItem" + } + }, + "name": { + "description": "The name of the core contract.", + "type": "string" + }, + "proposal_modules_instantiate_info": { + "description": "Instantiate information for the core contract's proposal modules.", + "type": "array", + "items": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + }, + "voting_registry_module_instantiate_info": { + "description": "Instantiate information for the core contract's voting power module.", + "allOf": [ + { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + ] + } + }, + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "InitialItem": { + "description": "Information about an item to be stored in the items list.", + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "description": "The name of the item.", + "type": "string" + }, + "value": { + "description": "The value the item will have at instantiation time.", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + } +} diff --git a/contracts/cwd-core/schema/schema/list_items_response.json b/contracts/cwd-core/schema/schema/list_items_response.json new file mode 100644 index 00000000..9b743544 --- /dev/null +++ b/contracts/cwd-core/schema/schema/list_items_response.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListItemsResponse", + "type": "array", + "items": { + "type": "string" + } +} diff --git a/contracts/cwd-core/schema/schema/list_sub_daos_response.json b/contracts/cwd-core/schema/schema/list_sub_daos_response.json new file mode 100644 index 00000000..69a1e8bc --- /dev/null +++ b/contracts/cwd-core/schema/schema/list_sub_daos_response.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListSubDaosResponse", + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + }, + "definitions": { + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } + } + } +} diff --git a/contracts/cwd-core/schema/schema/migrate_msg.json b/contracts/cwd-core/schema/schema/migrate_msg.json new file mode 100644 index 00000000..6b9264c8 --- /dev/null +++ b/contracts/cwd-core/schema/schema/migrate_msg.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "from_v1" + ], + "properties": { + "from_v1": { + "type": "object", + "properties": { + "dao_uri": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "from_compatible" + ], + "properties": { + "from_compatible": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cwd-core/schema/schema/pause_info_response.json b/contracts/cwd-core/schema/schema/pause_info_response.json new file mode 100644 index 00000000..eb8e1aeb --- /dev/null +++ b/contracts/cwd-core/schema/schema/pause_info_response.json @@ -0,0 +1,99 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PauseInfoResponse", + "description": "Information about if the contract is currently paused.", + "oneOf": [ + { + "type": "object", + "required": [ + "Paused" + ], + "properties": { + "Paused": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "Unpaused" + ], + "properties": { + "Unpaused": { + "type": "object" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/schema/proposal_modules_response.json b/contracts/cwd-core/schema/schema/proposal_modules_response.json new file mode 100644 index 00000000..c2940218 --- /dev/null +++ b/contracts/cwd-core/schema/schema/proposal_modules_response.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + } + } +} diff --git a/contracts/cwd-core/schema/schema/query_msg.json b/contracts/cwd-core/schema/schema/query_msg.json new file mode 100644 index 00000000..2692c274 --- /dev/null +++ b/contracts/cwd-core/schema/schema/query_msg.json @@ -0,0 +1,270 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Gets the contract's config. Returns Config.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Dumps all of the core contract's state in a single query. Useful for frontends as performance for queries is more limited by network times than compute times. Returns `DumpStateResponse`.", + "type": "object", + "required": [ + "dump_state" + ], + "properties": { + "dump_state": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the address associated with an item key.", + "type": "object", + "required": [ + "get_item" + ], + "properties": { + "get_item": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Lists all of the items associted with the contract. For example, given the items `{ \"group\": \"foo\", \"subdao\": \"bar\"}` this query would return `[(\"group\", \"foo\"), (\"subdao\", \"bar\")]`.", + "type": "object", + "required": [ + "list_items" + ], + "properties": { + "list_items": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets all proposal modules associated with the contract. Returns Vec.", + "type": "object", + "required": [ + "proposal_modules" + ], + "properties": { + "proposal_modules": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets the active proposal modules associated with the contract. Returns Vec.", + "type": "object", + "required": [ + "active_proposal_modules" + ], + "properties": { + "active_proposal_modules": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about if the contract is currently paused.", + "type": "object", + "required": [ + "pause_info" + ], + "properties": { + "pause_info": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the contract's voting module. Returns Addr.", + "type": "object", + "required": [ + "voting_module" + ], + "properties": { + "voting_module": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Returns all SubDAOs with their charters in a vec start_after is bound exclusive and asks for a string address", + "type": "object", + "required": [ + "list_sub_daos" + ], + "properties": { + "list_sub_daos": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Implements the DAO Star standard: https://daostar.one/EIP", + "type": "object", + "required": [ + "dao_u_r_i" + ], + "properties": { + "dao_u_r_i": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_power_at_height" + ], + "properties": { + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_power_at_height" + ], + "properties": { + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cwd-core/schema/schema/sub_dao.json b/contracts/cwd-core/schema/schema/sub_dao.json new file mode 100644 index 00000000..ae12c03a --- /dev/null +++ b/contracts/cwd-core/schema/schema/sub_dao.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SubDao", + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/cwd-core/schema/schema/total_power_at_height_response.json b/contracts/cwd-core/schema/schema/total_power_at_height_response.json new file mode 100644 index 00000000..8018462b --- /dev/null +++ b/contracts/cwd-core/schema/schema/total_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/schema/voting_module_response.json b/contracts/cwd-core/schema/schema/voting_module_response.json new file mode 100644 index 00000000..fc0cb679 --- /dev/null +++ b/contracts/cwd-core/schema/schema/voting_module_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingModuleResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/cwd-core/schema/schema/voting_power_at_height_response.json b/contracts/cwd-core/schema/schema/voting_power_at_height_response.json new file mode 100644 index 00000000..15e986bf --- /dev/null +++ b/contracts/cwd-core/schema/schema/voting_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/sub_dao.json b/contracts/cwd-core/schema/sub_dao.json new file mode 100644 index 00000000..ae12c03a --- /dev/null +++ b/contracts/cwd-core/schema/sub_dao.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SubDao", + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/cwd-core/schema/total_power_at_height_response.json b/contracts/cwd-core/schema/total_power_at_height_response.json new file mode 100644 index 00000000..8018462b --- /dev/null +++ b/contracts/cwd-core/schema/total_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/schema/voting_module_response.json b/contracts/cwd-core/schema/voting_module_response.json new file mode 100644 index 00000000..fc0cb679 --- /dev/null +++ b/contracts/cwd-core/schema/voting_module_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingModuleResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/cwd-core/schema/voting_power_at_height_response.json b/contracts/cwd-core/schema/voting_power_at_height_response.json new file mode 100644 index 00000000..15e986bf --- /dev/null +++ b/contracts/cwd-core/schema/voting_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/contract.rs b/contracts/cwd-core/src/contract.rs new file mode 100644 index 00000000..1415e082 --- /dev/null +++ b/contracts/cwd-core/src/contract.rs @@ -0,0 +1,624 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Reply, Response, + StdError, StdResult, SubMsg, +}; +use cw2::{get_contract_version, set_contract_version}; +use cw_utils::{parse_reply_instantiate_data, Duration}; + +use cw_paginate::{paginate_map, paginate_map_values}; +use cwd_interface::{voting, ModuleInstantiateInfo}; +use neutron_bindings::bindings::msg::NeutronMsg; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InitialItem, InstantiateMsg, MigrateMsg, QueryMsg}; +use crate::query::{DumpStateResponse, GetItemResponse, PauseInfoResponse, SubDao}; +use crate::state::{ + Config, ProposalModule, ProposalModuleStatus, ACTIVE_PROPOSAL_MODULE_COUNT, CONFIG, ITEMS, + PAUSED, PROPOSAL_MODULES, SUBDAO_LIST, TOTAL_PROPOSAL_MODULE_COUNT, VOTING_REGISTRY_MODULE, +}; + +pub(crate) const CONTRACT_NAME: &str = "crates.io:cwd-core"; +pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +const PROPOSAL_MODULE_REPLY_ID: u64 = 0; +const VOTE_MODULE_INSTANTIATE_REPLY_ID: u64 = 1; +const VOTE_MODULE_UPDATE_REPLY_ID: u64 = 2; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let config = Config { + name: msg.name, + description: msg.description, + dao_uri: msg.dao_uri, + }; + CONFIG.save(deps.storage, &config)?; + + let vote_module_msg = msg + .voting_registry_module_instantiate_info + .into_wasm_msg(env.contract.address.clone()); + let vote_module_msg: SubMsg = + SubMsg::reply_on_success(vote_module_msg, VOTE_MODULE_INSTANTIATE_REPLY_ID); + + let proposal_module_msgs: Vec> = msg + .proposal_modules_instantiate_info + .into_iter() + .map(|info| info.into_wasm_msg(env.contract.address.clone())) + .map(|wasm| SubMsg::reply_on_success(wasm, PROPOSAL_MODULE_REPLY_ID)) + .collect(); + if proposal_module_msgs.is_empty() { + return Err(ContractError::NoActiveProposalModules {}); + } + + for InitialItem { key, value } in msg.initial_items.unwrap_or_default() { + ITEMS.save(deps.storage, key, &value)?; + } + + TOTAL_PROPOSAL_MODULE_COUNT.save(deps.storage, &0)?; + ACTIVE_PROPOSAL_MODULE_COUNT.save(deps.storage, &0)?; + + Ok(Response::new() + .add_attribute("action", "instantiate") + .add_attribute("sender", info.sender) + .add_submessage(vote_module_msg) + .add_submessages(proposal_module_msgs)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result, ContractError> { + // No actions can be performed while the DAO is paused. + if let Some(expiration) = PAUSED.may_load(deps.storage)? { + if !expiration.is_expired(&env.block) { + return Err(ContractError::Paused {}); + } + } + + match msg { + ExecuteMsg::ExecuteProposalHook { msgs } => { + execute_proposal_hook(deps.as_ref(), info.sender, msgs) + } + ExecuteMsg::Pause { duration } => execute_pause(deps, env, info.sender, duration), + ExecuteMsg::RemoveItem { key } => execute_remove_item(deps, env, info.sender, key), + ExecuteMsg::SetItem { key, addr } => execute_set_item(deps, env, info.sender, key, addr), + ExecuteMsg::UpdateConfig { config } => { + execute_update_config(deps, env, info.sender, config) + } + ExecuteMsg::UpdateVotingModule { module } => { + execute_update_voting_module(env, info.sender, module) + } + ExecuteMsg::UpdateProposalModules { to_add, to_disable } => { + execute_update_proposal_modules(deps, env, info.sender, to_add, to_disable) + } + ExecuteMsg::UpdateSubDaos { to_add, to_remove } => { + execute_update_sub_daos_list(deps, env, info.sender, to_add, to_remove) + } + } +} + +pub fn execute_pause( + deps: DepsMut, + env: Env, + sender: Addr, + pause_duration: Duration, +) -> Result, ContractError> { + if sender != env.contract.address { + return Err(ContractError::Unauthorized {}); + } + + let until = pause_duration.after(&env.block); + + PAUSED.save(deps.storage, &until)?; + + Ok(Response::new() + .add_attribute("action", "execute_pause") + .add_attribute("sender", sender) + .add_attribute("until", until.to_string())) +} + +pub fn execute_proposal_hook( + deps: Deps, + sender: Addr, + msgs: Vec>, +) -> Result, ContractError> { + let module = PROPOSAL_MODULES + .may_load(deps.storage, sender.clone())? + .ok_or(ContractError::Unauthorized {})?; + + // Check that the message has come from an active module + if module.status != ProposalModuleStatus::Enabled { + return Err(ContractError::ModuleDisabledCannotExecute { address: sender }); + } + + Ok(Response::default() + .add_attribute("action", "execute_proposal_hook") + .add_messages(msgs)) +} + +pub fn execute_update_config( + deps: DepsMut, + env: Env, + sender: Addr, + config: Config, +) -> Result, ContractError> { + if env.contract.address != sender { + return Err(ContractError::Unauthorized {}); + } + + CONFIG.save(deps.storage, &config)?; + // We incur some gas costs by having the config's fields in the + // response. This has the benefit that it makes it reasonably + // simple to ask "when did this field in the config change" by + // running something like `junod query txs --events + // 'wasm._contract_address=core&wasm.name=name'`. + Ok(Response::default() + .add_attribute("action", "execute_update_config") + .add_attribute("name", config.name) + .add_attribute("description", config.description)) +} + +pub fn execute_update_voting_module( + env: Env, + sender: Addr, + module: ModuleInstantiateInfo, +) -> Result, ContractError> { + if env.contract.address != sender { + return Err(ContractError::Unauthorized {}); + } + + let wasm = module.into_wasm_msg(env.contract.address); + let submessage = SubMsg::reply_on_success(wasm, VOTE_MODULE_UPDATE_REPLY_ID); + + Ok(Response::default() + .add_attribute("action", "execute_update_voting_module") + .add_submessage(submessage)) +} + +pub fn execute_update_proposal_modules( + deps: DepsMut, + env: Env, + sender: Addr, + to_add: Vec, + to_disable: Vec, +) -> Result, ContractError> { + if env.contract.address != sender { + return Err(ContractError::Unauthorized {}); + } + + let disable_count = to_disable.len() as u32; + for addr in to_disable { + let addr = deps.api.addr_validate(&addr)?; + let mut module = PROPOSAL_MODULES + .load(deps.storage, addr.clone()) + .map_err(|_| ContractError::ProposalModuleDoesNotExist { + address: addr.clone(), + })?; + + if module.status == ProposalModuleStatus::Disabled { + return Err(ContractError::ModuleAlreadyDisabled { + address: module.address, + }); + } + + module.status = ProposalModuleStatus::Disabled {}; + PROPOSAL_MODULES.save(deps.storage, addr, &module)?; + } + + // If disabling this module will cause there to be no active modules, return error. + // We don't check the active count before disabling because there may erroneously be + // modules in to_disable which are already disabled. + ACTIVE_PROPOSAL_MODULE_COUNT.update(deps.storage, |count| { + if count <= disable_count && to_add.is_empty() { + return Err(ContractError::NoActiveProposalModules {}); + } + Ok(count - disable_count) + })?; + + let to_add: Vec> = to_add + .into_iter() + .map(|info| info.into_wasm_msg(env.contract.address.clone())) + .map(|wasm| SubMsg::reply_on_success(wasm, PROPOSAL_MODULE_REPLY_ID)) + .collect(); + + Ok(Response::default() + .add_attribute("action", "execute_update_proposal_modules") + .add_submessages(to_add)) +} + +pub fn execute_set_item( + deps: DepsMut, + env: Env, + sender: Addr, + key: String, + value: String, +) -> Result, ContractError> { + if env.contract.address != sender { + return Err(ContractError::Unauthorized {}); + } + + ITEMS.save(deps.storage, key.clone(), &value)?; + Ok(Response::default() + .add_attribute("action", "execute_set_item") + .add_attribute("key", key) + .add_attribute("addr", value)) +} + +pub fn execute_remove_item( + deps: DepsMut, + env: Env, + sender: Addr, + key: String, +) -> Result, ContractError> { + if env.contract.address != sender { + return Err(ContractError::Unauthorized {}); + } + + if ITEMS.has(deps.storage, key.clone()) { + ITEMS.remove(deps.storage, key.clone()); + Ok(Response::default() + .add_attribute("action", "execute_remove_item") + .add_attribute("key", key)) + } else { + Err(ContractError::KeyMissing {}) + } +} + +pub fn execute_update_sub_daos_list( + deps: DepsMut, + env: Env, + sender: Addr, + to_add: Vec, + to_remove: Vec, +) -> Result, ContractError> { + if env.contract.address != sender { + return Err(ContractError::Unauthorized {}); + } + + for addr in to_remove { + let addr = deps.api.addr_validate(&addr)?; + SUBDAO_LIST.remove(deps.storage, &addr); + } + + for subdao in to_add { + let addr = deps.api.addr_validate(&subdao.addr)?; + SUBDAO_LIST.save(deps.storage, &addr, &subdao.charter)?; + } + + Ok(Response::default() + .add_attribute("action", "execute_update_sub_daos_list") + .add_attribute("sender", sender)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => query_config(deps), + QueryMsg::DumpState {} => query_dump_state(deps, env), + QueryMsg::GetItem { key } => query_get_item(deps, key), + QueryMsg::Info {} => query_info(deps), + QueryMsg::ListItems { start_after, limit } => query_list_items(deps, start_after, limit), + QueryMsg::PauseInfo {} => query_paused(deps, env), + QueryMsg::ProposalModules { start_after, limit } => { + query_proposal_modules(deps, start_after, limit) + } + QueryMsg::TotalPowerAtHeight { height } => query_total_power_at_height(deps, height), + QueryMsg::VotingModule {} => query_voting_module(deps), + QueryMsg::VotingPowerAtHeight { address, height } => { + query_voting_power_at_height(deps, address, height) + } + QueryMsg::ActiveProposalModules { start_after, limit } => { + query_active_proposal_modules(deps, start_after, limit) + } + QueryMsg::ListSubDaos { start_after, limit } => { + query_list_sub_daos(deps, start_after, limit) + } + QueryMsg::DaoURI {} => query_dao_uri(deps), + } +} + +pub fn query_config(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + to_binary(&config) +} + +pub fn query_voting_module(deps: Deps) -> StdResult { + let voting_module = VOTING_REGISTRY_MODULE.load(deps.storage)?; + to_binary(&voting_module) +} + +pub fn query_proposal_modules( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + // This query is will run out of gas due to the size of the + // returned message before it runs out of compute so taking a + // limit here is still nice. As removes happen in constant time + // the contract is still recoverable if too many items end up in + // here. + // + // Further, as the `range` method on a map returns an iterator it + // is possible (though implementation dependent) that new keys are + // loaded on demand as the iterator is moved. Should this be the + // case we are only paying for what we need here. + // + // Even if this does lock up one can determine the existing + // proposal modules by looking at past transactions on chain. + to_binary(&paginate_map_values( + deps, + &PROPOSAL_MODULES, + start_after + .map(|s| deps.api.addr_validate(&s)) + .transpose()?, + limit, + cosmwasm_std::Order::Ascending, + )?) +} + +/// Note: this is not gas efficient as we need to potentially visit all modules in order to +/// filter out the modules with active status. +pub fn query_active_proposal_modules( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + let values = paginate_map_values( + deps, + &PROPOSAL_MODULES, + start_after + .map(|s| deps.api.addr_validate(&s)) + .transpose()?, + None, + cosmwasm_std::Order::Ascending, + )?; + + let limit = limit.unwrap_or(values.len() as u32); + + to_binary::>( + &values + .into_iter() + .filter(|module: &ProposalModule| module.status == ProposalModuleStatus::Enabled) + .take(limit as usize) + .collect(), + ) +} + +fn get_pause_info(deps: Deps, env: Env) -> StdResult { + Ok(match PAUSED.may_load(deps.storage)? { + Some(expiration) => { + if expiration.is_expired(&env.block) { + PauseInfoResponse::Unpaused {} + } else { + PauseInfoResponse::Paused { expiration } + } + } + None => PauseInfoResponse::Unpaused {}, + }) +} + +pub fn query_paused(deps: Deps, env: Env) -> StdResult { + to_binary(&get_pause_info(deps, env)?) +} + +pub fn query_dump_state(deps: Deps, env: Env) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let voting_registry_module = VOTING_REGISTRY_MODULE.load(deps.storage)?; + let proposal_modules = PROPOSAL_MODULES + .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .map(|kv| Ok(kv?.1)) + .collect::>>()?; + let pause_info = get_pause_info(deps, env)?; + let version = get_contract_version(deps.storage)?; + let active_proposal_module_count = ACTIVE_PROPOSAL_MODULE_COUNT.load(deps.storage)?; + let total_proposal_module_count = TOTAL_PROPOSAL_MODULE_COUNT.load(deps.storage)?; + to_binary(&DumpStateResponse { + config, + version, + pause_info, + proposal_modules, + voting_module: voting_registry_module, + active_proposal_module_count, + total_proposal_module_count, + }) +} + +pub fn query_voting_power_at_height( + deps: Deps, + address: String, + height: Option, +) -> StdResult { + let voting_registry_module = VOTING_REGISTRY_MODULE.load(deps.storage)?; + let voting_power: voting::VotingPowerAtHeightResponse = deps.querier.query_wasm_smart( + voting_registry_module, + &voting::Query::VotingPowerAtHeight { height, address }, + )?; + to_binary(&voting_power) +} + +pub fn query_total_power_at_height(deps: Deps, height: Option) -> StdResult { + let voting_registry_module = VOTING_REGISTRY_MODULE.load(deps.storage)?; + let total_power: voting::TotalPowerAtHeightResponse = deps.querier.query_wasm_smart( + voting_registry_module, + &voting::Query::TotalPowerAtHeight { height }, + )?; + to_binary(&total_power) +} + +pub fn query_get_item(deps: Deps, item: String) -> StdResult { + let item = ITEMS.may_load(deps.storage, item)?; + to_binary(&GetItemResponse { item }) +} + +pub fn query_info(deps: Deps) -> StdResult { + let info = cw2::get_contract_version(deps.storage)?; + to_binary(&cwd_interface::voting::InfoResponse { info }) +} + +pub fn query_list_items( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + to_binary(&paginate_map( + deps, + &ITEMS, + start_after, + limit, + cosmwasm_std::Order::Descending, + )?) +} + +pub fn query_list_sub_daos( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + let start_at = start_after + .map(|addr| deps.api.addr_validate(&addr)) + .transpose()?; + + let subdaos = cw_paginate::paginate_map( + deps, + &SUBDAO_LIST, + start_at.as_ref(), + limit, + cosmwasm_std::Order::Ascending, + )?; + + let subdaos: Vec = subdaos + .into_iter() + .map(|(address, charter)| SubDao { + addr: address.into_string(), + charter, + }) + .collect(); + + to_binary(&subdaos) +} + +pub fn query_dao_uri(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + to_binary(&config.dao_uri) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { + match msg.id { + PROPOSAL_MODULE_REPLY_ID => { + let res = parse_reply_instantiate_data(msg)?; + let prop_module_addr = deps.api.addr_validate(&res.contract_address)?; + let total_module_count = TOTAL_PROPOSAL_MODULE_COUNT.load(deps.storage)?; + + let prefix = derive_proposal_module_prefix(total_module_count as usize)?; + let prop_module = ProposalModule { + address: prop_module_addr.clone(), + status: ProposalModuleStatus::Enabled, + prefix, + }; + + PROPOSAL_MODULES.save(deps.storage, prop_module_addr, &prop_module)?; + + // Save active and total proposal module counts. + ACTIVE_PROPOSAL_MODULE_COUNT + .update::<_, StdError>(deps.storage, |count| Ok(count + 1))?; + TOTAL_PROPOSAL_MODULE_COUNT.save(deps.storage, &(total_module_count + 1))?; + + Ok(Response::default().add_attribute("prop_module".to_string(), res.contract_address)) + } + + VOTE_MODULE_INSTANTIATE_REPLY_ID => { + let res = parse_reply_instantiate_data(msg)?; + let voting_registry_addr = deps.api.addr_validate(&res.contract_address)?; + let current = VOTING_REGISTRY_MODULE.may_load(deps.storage)?; + + // Make sure a bug in instantiation isn't causing us to + // make more than one voting module. + if current.is_some() { + return Err(ContractError::MultipleVotingModules {}); + } + + VOTING_REGISTRY_MODULE.save(deps.storage, &voting_registry_addr)?; + + Ok(Response::default().add_attribute("voting_regsitry_module", voting_registry_addr)) + } + VOTE_MODULE_UPDATE_REPLY_ID => { + let res = parse_reply_instantiate_data(msg)?; + let voting_registry_addr = deps.api.addr_validate(&res.contract_address)?; + + VOTING_REGISTRY_MODULE.save(deps.storage, &voting_registry_addr)?; + + Ok(Response::default().add_attribute("voting_registry_module", voting_registry_addr)) + } + _ => Err(ContractError::UnknownReplyID {}), + } +} + +pub(crate) fn derive_proposal_module_prefix(mut dividend: usize) -> StdResult { + dividend += 1; + // Pre-allocate string + let mut prefix = String::with_capacity(10); + loop { + let remainder = (dividend - 1) % 26; + dividend = (dividend - remainder) / 26; + let remainder_str = std::str::from_utf8(&[(remainder + 65) as u8])?.to_owned(); + prefix.push_str(&remainder_str); + if dividend == 0 { + break; + } + } + Ok(prefix.chars().rev().collect()) +} + +#[cfg(test)] +mod test { + use crate::contract::derive_proposal_module_prefix; + use std::collections::HashSet; + + #[test] + fn test_prefix_generation() { + assert_eq!("A", derive_proposal_module_prefix(0).unwrap()); + assert_eq!("B", derive_proposal_module_prefix(1).unwrap()); + assert_eq!("C", derive_proposal_module_prefix(2).unwrap()); + assert_eq!("AA", derive_proposal_module_prefix(26).unwrap()); + assert_eq!("AB", derive_proposal_module_prefix(27).unwrap()); + assert_eq!("BA", derive_proposal_module_prefix(26 * 2).unwrap()); + assert_eq!("BB", derive_proposal_module_prefix(26 * 2 + 1).unwrap()); + assert_eq!("CA", derive_proposal_module_prefix(26 * 3).unwrap()); + assert_eq!("JA", derive_proposal_module_prefix(26 * 10).unwrap()); + assert_eq!("YA", derive_proposal_module_prefix(26 * 25).unwrap()); + assert_eq!("ZA", derive_proposal_module_prefix(26 * 26).unwrap()); + assert_eq!("ZZ", derive_proposal_module_prefix(26 * 26 + 25).unwrap()); + assert_eq!("AAA", derive_proposal_module_prefix(26 * 26 + 26).unwrap()); + assert_eq!("YZA", derive_proposal_module_prefix(26 * 26 * 26).unwrap()); + assert_eq!("ZZ", derive_proposal_module_prefix(26 * 26 + 25).unwrap()); + } + + #[test] + fn test_prefixes_no_collisions() { + let mut seen = HashSet::::new(); + for i in 0..25 * 25 * 25 { + let prefix = derive_proposal_module_prefix(i).unwrap(); + if seen.contains(&prefix) { + panic!("already seen value") + } + seen.insert(prefix); + } + } +} diff --git a/contracts/cwd-core/src/error.rs b/contracts/cwd-core/src/error.rs new file mode 100644 index 00000000..ff10efb7 --- /dev/null +++ b/contracts/cwd-core/src/error.rs @@ -0,0 +1,39 @@ +use cosmwasm_std::{Addr, StdError}; +use cw_utils::ParseReplyError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error(transparent)] + Std(#[from] StdError), + + #[error(transparent)] + ParseReplyError(#[from] ParseReplyError), + + #[error("Unauthorized.")] + Unauthorized {}, + + #[error("The contract is paused.")] + Paused {}, + + #[error("Execution would result in no proposal modules being active.")] + NoActiveProposalModules {}, + + #[error("An unknown reply ID was received.")] + UnknownReplyID {}, + + #[error("Multiple voting modules during instantiation.")] + MultipleVotingModules {}, + + #[error("Key is missing from storage")] + KeyMissing {}, + + #[error("Proposal module with address ({address}) does not exist.")] + ProposalModuleDoesNotExist { address: Addr }, + + #[error("Proposal module with address ({address}) is already disabled.")] + ModuleAlreadyDisabled { address: Addr }, + + #[error("Proposal module with address is disabled and cannot execute messages.")] + ModuleDisabledCannotExecute { address: Addr }, +} diff --git a/contracts/cwd-core/src/lib.rs b/contracts/cwd-core/src/lib.rs new file mode 100644 index 00000000..993aa0d7 --- /dev/null +++ b/contracts/cwd-core/src/lib.rs @@ -0,0 +1,22 @@ +//! # cw-core +//! +//! This contract is the core module for all DAO DAO DAOs. It handles +//! management of voting power and proposal modules, executes messages, +//! and holds the DAO's treasury. +//! +//! For more information about how these modules fit together see +//! [this](https://!github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-v1-Contracts-Design) +//! wiki page. +//! +//! In additon to the wiki spec this contract may also pause. To do so a +//! `Pause` message must by executed by a proposal module. Pausing the +//! core module will stop all actions on the module for the duration of +//! the pause. + +pub mod contract; +mod error; +pub mod msg; +pub mod query; +pub mod state; + +pub use crate::error::ContractError; diff --git a/contracts/cwd-core/src/msg.rs b/contracts/cwd-core/src/msg.rs new file mode 100644 index 00000000..7cbfefe9 --- /dev/null +++ b/contracts/cwd-core/src/msg.rs @@ -0,0 +1,138 @@ +use cosmwasm_std::CosmosMsg; +use cw_utils::Duration; +use cwd_interface::ModuleInstantiateInfo; +use neutron_bindings::bindings::msg::NeutronMsg; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cwd_macros::{info_query, voting_query}; + +use crate::query::SubDao; +use crate::state::Config; + +/// Information about an item to be stored in the items list. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct InitialItem { + /// The name of the item. + pub key: String, + /// The value the item will have at instantiation time. + pub value: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct InstantiateMsg { + /// The name of the core contract. + pub name: String, + /// A description of the core contract. + pub description: String, + + /// Instantiate information for the core contract's voting + /// power module. + pub voting_registry_module_instantiate_info: ModuleInstantiateInfo, + /// Instantiate information for the core contract's + /// proposal modules. + // NOTE: the pre-propose-base package depends on it being the case + // that the core module instantiates its proposal module. + pub proposal_modules_instantiate_info: Vec, + + /// Initial information for arbitrary contract addresses to be + /// added to the items map. The key is the name of the item in the + /// items map. The value is an enum that either uses an existing + /// address or instantiates a new contract. + pub initial_items: Option>, + /// Implements the DAO Star standard: https://daostar.one/EIP + pub dao_uri: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + /// Callable by proposal modules. The DAO will execute the + /// messages in the hook in order. + ExecuteProposalHook { msgs: Vec> }, + /// Pauses the DAO for a set duration. + /// When paused the DAO is unable to execute proposals + Pause { duration: Duration }, + /// Removes an item from the governance contract's item map. + RemoveItem { key: String }, + /// Adds an item to the governance contract's item map. If the + /// item already exists the existing value is overriden. If the + /// item does not exist a new item is added. + SetItem { key: String, addr: String }, + /// Callable by the core contract. Replaces the current + /// governance contract config with the provided config. + UpdateConfig { config: Config }, + /// Updates the governance contract's governance modules. Module + /// instantiate info in `to_add` is used to create new modules and + /// install them. + UpdateProposalModules { + // NOTE: the pre-propose-base package depends on it being the + // case that the core module instantiates its proposal module. + to_add: Vec, + to_disable: Vec, + }, + /// Callable by the core contract. Replaces the current + /// voting module with a new one instantiated by the governance + /// contract. + UpdateVotingModule { module: ModuleInstantiateInfo }, + /// Update the core module to add/remove SubDAOs and their charters + UpdateSubDaos { + to_add: Vec, + to_remove: Vec, + }, +} + +#[voting_query] +#[info_query] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + /// Gets the contract's config. Returns Config. + Config {}, + /// Dumps all of the core contract's state in a single + /// query. Useful for frontends as performance for queries is more + /// limited by network times than compute times. Returns + /// `DumpStateResponse`. + DumpState {}, + /// Gets the address associated with an item key. + GetItem { key: String }, + /// Lists all of the items associted with the contract. For + /// example, given the items `{ "group": "foo", "subdao": "bar"}` + /// this query would return `[("group", "foo"), ("subdao", + /// "bar")]`. + ListItems { + start_after: Option, + limit: Option, + }, + /// Gets all proposal modules associated with the + /// contract. Returns Vec. + ProposalModules { + start_after: Option, + limit: Option, + }, + /// Gets the active proposal modules associated with the + /// contract. Returns Vec. + ActiveProposalModules { + start_after: Option, + limit: Option, + }, + /// Returns information about if the contract is currently paused. + PauseInfo {}, + /// Gets the contract's voting module. Returns Addr. + VotingModule {}, + /// Returns all SubDAOs with their charters in a vec + /// start_after is bound exclusive and asks for a string address + ListSubDaos { + start_after: Option, + limit: Option, + }, + /// Implements the DAO Star standard: https://daostar.one/EIP + DaoURI {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum MigrateMsg { + FromV1 { dao_uri: Option }, + FromCompatible {}, +} diff --git a/contracts/cwd-core/src/query.rs b/contracts/cwd-core/src/query.rs new file mode 100644 index 00000000..44a160f2 --- /dev/null +++ b/contracts/cwd-core/src/query.rs @@ -0,0 +1,68 @@ +use cosmwasm_std::{Addr, Uint128}; +use cw2::ContractVersion; +use cw_utils::Expiration; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::state::{Config, ProposalModule}; + +/// Relevant state for the governance module. Returned by the +/// `DumpState` query. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct DumpStateResponse { + /// The governance contract's config. + pub config: Config, + // True if the contract is currently paused. + pub pause_info: PauseInfoResponse, + /// The governance contract's version. + pub version: ContractVersion, + /// The governance modules associated with the governance + /// contract. + pub proposal_modules: Vec, + /// The voting module associated with the governance contract. + pub voting_module: Addr, + /// The number of active proposal modules. + pub active_proposal_module_count: u32, + /// The total number of proposal modules. + pub total_proposal_module_count: u32, +} + +/// Information about if the contract is currently paused. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub enum PauseInfoResponse { + Paused { expiration: Expiration }, + Unpaused {}, +} + +/// Returned by the `GetItem` query. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct GetItemResponse { + /// `None` if no item with the provided key was found, `Some` + /// otherwise. + pub item: Option, +} + +/// Returned by the `Cw20Balances` query. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Cw20BalanceResponse { + /// The address of the token. + pub addr: Addr, + /// The contract's balance. + pub balance: Uint128, +} + +/// Returned by the `AdminNomination` query. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct AdminNominationResponse { + /// The currently nominated admin or None if no nomination is + /// pending. + pub nomination: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct SubDao { + /// The contract address of the SubDAO + pub addr: String, + /// The purpose/constitution for the SubDAO + pub charter: Option, +} diff --git a/contracts/cwd-core/src/schema/active_proposal_modules_response.json b/contracts/cwd-core/src/schema/active_proposal_modules_response.json new file mode 100644 index 00000000..064e8b4e --- /dev/null +++ b/contracts/cwd-core/src/schema/active_proposal_modules_response.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ActiveProposalModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + } + } +} diff --git a/contracts/cwd-core/src/schema/admin_nomination_response.json b/contracts/cwd-core/src/schema/admin_nomination_response.json new file mode 100644 index 00000000..7e2b4af9 --- /dev/null +++ b/contracts/cwd-core/src/schema/admin_nomination_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminNominationResponse", + "description": "Returned by the `AdminNomination` query.", + "type": "object", + "properties": { + "nomination": { + "description": "The currently nominated admin or None if no nomination is pending.", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/schema/admin_response.json b/contracts/cwd-core/src/schema/admin_response.json new file mode 100644 index 00000000..794c4351 --- /dev/null +++ b/contracts/cwd-core/src/schema/admin_response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AdminResponse", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/schema/config_response.json b/contracts/cwd-core/src/schema/config_response.json new file mode 100644 index 00000000..f0f3d2fc --- /dev/null +++ b/contracts/cwd-core/src/schema/config_response.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/schema/cw20_balances_response.json b/contracts/cwd-core/src/schema/cw20_balances_response.json new file mode 100644 index 00000000..34300995 --- /dev/null +++ b/contracts/cwd-core/src/schema/cw20_balances_response.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20BalancesResponse", + "description": "Returned by the `Cw20Balances` query.", + "type": "object", + "required": [ + "addr", + "balance" + ], + "properties": { + "addr": { + "description": "The address of the token.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "balance": { + "description": "The contract's balance.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/schema/cw20_token_list_response.json b/contracts/cwd-core/src/schema/cw20_token_list_response.json new file mode 100644 index 00000000..20fb4848 --- /dev/null +++ b/contracts/cwd-core/src/schema/cw20_token_list_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20TokenListResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/schema/cw721_token_list_response.json b/contracts/cwd-core/src/schema/cw721_token_list_response.json new file mode 100644 index 00000000..9ca019ec --- /dev/null +++ b/contracts/cwd-core/src/schema/cw721_token_list_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw721TokenListResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/schema/dao_u_r_i_response.json b/contracts/cwd-core/src/schema/dao_u_r_i_response.json new file mode 100644 index 00000000..2b9566ad --- /dev/null +++ b/contracts/cwd-core/src/schema/dao_u_r_i_response.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoURIResponse", + "type": [ + "string", + "null" + ] +} diff --git a/contracts/cwd-core/src/schema/dump_state_response.json b/contracts/cwd-core/src/schema/dump_state_response.json new file mode 100644 index 00000000..1543ad72 --- /dev/null +++ b/contracts/cwd-core/src/schema/dump_state_response.json @@ -0,0 +1,245 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DumpStateResponse", + "description": "Relevant state for the governance module. Returned by the `DumpState` query.", + "type": "object", + "required": [ + "active_proposal_module_count", + "config", + "pause_info", + "proposal_modules", + "total_proposal_module_count", + "version", + "voting_module" + ], + "properties": { + "active_proposal_module_count": { + "description": "The number of active proposal modules.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "config": { + "description": "The governance contract's config.", + "allOf": [ + { + "$ref": "#/definitions/Config" + } + ] + }, + "pause_info": { + "$ref": "#/definitions/PauseInfoResponse" + }, + "proposal_modules": { + "description": "The governance modules associated with the governance contract.", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + } + }, + "total_proposal_module_count": { + "description": "The total number of proposal modules.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "version": { + "description": "The governance contract's version.", + "allOf": [ + { + "$ref": "#/definitions/ContractVersion" + } + ] + }, + "voting_module": { + "description": "The voting module associated with the governance contract.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Config": { + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } + }, + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "PauseInfoResponse": { + "description": "Information about if the contract is currently paused.", + "oneOf": [ + { + "type": "object", + "required": [ + "Paused" + ], + "properties": { + "Paused": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "Unpaused" + ], + "properties": { + "Unpaused": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/schema/execute_msg.json b/contracts/cwd-core/src/schema/execute_msg.json new file mode 100644 index 00000000..be8ebdde --- /dev/null +++ b/contracts/cwd-core/src/schema/execute_msg.json @@ -0,0 +1,1506 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Callable by proposal modules. The DAO will execute the messages in the hook in order.", + "type": "object", + "required": [ + "execute_proposal_hook" + ], + "properties": { + "execute_proposal_hook": { + "type": "object", + "required": [ + "msgs" + ], + "properties": { + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_NeutronMsg" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Pauses the DAO for a set duration. When paused the DAO is unable to execute proposals", + "type": "object", + "required": [ + "pause" + ], + "properties": { + "pause": { + "type": "object", + "required": [ + "duration" + ], + "properties": { + "duration": { + "$ref": "#/definitions/Duration" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Removes an item from the governance contract's item map.", + "type": "object", + "required": [ + "remove_item" + ], + "properties": { + "remove_item": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Adds an item to the governance contract's item map. If the item already exists the existing value is overriden. If the item does not exist a new item is added.", + "type": "object", + "required": [ + "set_item" + ], + "properties": { + "set_item": { + "type": "object", + "required": [ + "addr", + "key" + ], + "properties": { + "addr": { + "type": "string" + }, + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callable by the core contract. Replaces the current governance contract config with the provided config.", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "$ref": "#/definitions/Config" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Updates the governance contract's governance modules. Module instantiate info in `to_add` is used to create new modules and install them.", + "type": "object", + "required": [ + "update_proposal_modules" + ], + "properties": { + "update_proposal_modules": { + "type": "object", + "required": [ + "to_add", + "to_disable" + ], + "properties": { + "to_add": { + "type": "array", + "items": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + }, + "to_disable": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callable by the core contract. Replaces the current voting module with a new one instantiated by the governance contract.", + "type": "object", + "required": [ + "update_voting_module" + ], + "properties": { + "update_voting_module": { + "type": "object", + "required": [ + "module" + ], + "properties": { + "module": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Update the core module to add/remove SubDAOs and their charters", + "type": "object", + "required": [ + "update_sub_daos" + ], + "properties": { + "update_sub_daos": { + "type": "object", + "required": [ + "to_add", + "to_remove" + ], + "properties": { + "to_add": { + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + } + }, + "to_remove": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "AdminProposal": { + "description": "AdminProposal defines the struct for various proposals which Neutron's Admin Module may accept. Currently only parameter change proposals are implemented, new types of admin proposals may be implemented in future.", + "type": "object", + "properties": { + "param_change_proposal": { + "description": "*param_change_proposal** is a parameter change proposal field.", + "anyOf": [ + { + "$ref": "#/definitions/ParamChangeProposal" + }, + { + "type": "null" + } + ] + } + } + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Config": { + "description": "Top level config type for core module.", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "dao_uri": { + "description": "The URI for the DAO as defined by the DAOstar standard https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the contract.", + "type": "string" + }, + "name": { + "description": "The name of the contract.", + "type": "string" + } + } + }, + "CosmosMsg_for_NeutronMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/NeutronMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/VoteOption" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcFee": { + "type": "object", + "required": [ + "ack_fee", + "recv_fee", + "timeout_fee" + ], + "properties": { + "ack_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recv_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "timeout_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "KVKey": { + "description": "Describes a KV key for which you want to get value from the storage on remote chain", + "type": "object", + "required": [ + "key", + "path" + ], + "properties": { + "key": { + "description": "*key** is a key you want to read from the storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "*path** is a path to the storage (storage prefix) where you want to read value by key (usually name of cosmos-sdk module: 'staking', 'bank', etc.)", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "NeutronMsg": { + "description": "A number of Custom messages that can call into the Neutron bindings.", + "oneOf": [ + { + "description": "RegisterInterchainAccount registers an interchain account on remote chain.", + "type": "object", + "required": [ + "register_interchain_account" + ], + "properties": { + "register_interchain_account": { + "type": "object", + "required": [ + "connection_id", + "interchain_account_id" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "interchain_account_id": { + "description": "**interchain_account_id** is an identifier of your new interchain account. Can be any string. This identifier allows contracts to have multiple interchain accounts on remote chains.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitTx starts the process of executing any Cosmos-SDK *msgs* on remote chain.", + "type": "object", + "required": [ + "submit_tx" + ], + "properties": { + "submit_tx": { + "type": "object", + "required": [ + "connection_id", + "fee", + "interchain_account_id", + "memo", + "msgs", + "timeout" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "fee": { + "description": "**fee** is an ibc fee for the transaction.", + "allOf": [ + { + "$ref": "#/definitions/IbcFee" + } + ] + }, + "interchain_account_id": { + "description": "*interchain_account_id** is an identifier of your interchain account from which you want to execute msgs.", + "type": "string" + }, + "memo": { + "description": "*memo** is a memo you want to attach to your interchain transaction.It behaves like a memo in usual Cosmos transaction.", + "type": "string" + }, + "msgs": { + "description": "*msgs** is a list of protobuf encoded Cosmos-SDK messages you want to execute on remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/ProtobufAny" + } + }, + "timeout": { + "description": "*timeout** is a timeout in seconds after which the packet times out.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery registers an interchain query.", + "type": "object", + "required": [ + "register_interchain_query" + ], + "properties": { + "register_interchain_query": { + "type": "object", + "required": [ + "connection_id", + "keys", + "query_type", + "transactions_filter", + "update_period" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "keys": { + "description": "*keys** is the KV-storage keys for which we want to get values from remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "query_type": { + "description": "*query_type** is a query type identifier ('tx' or 'kv' for now).", + "type": "string" + }, + "transactions_filter": { + "description": "*transactions_filter** is the filter for transaction search ICQ.", + "type": "string" + }, + "update_period": { + "description": "*update_period** is used to say how often the query must be updated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery updates an interchain query.", + "type": "object", + "required": [ + "update_interchain_query" + ], + "properties": { + "update_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "new_keys": { + "description": "*new_keys** is the new query keys to retrive.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "new_transactions_filter": { + "description": "*new_transactions_filter** is a new transactions filter of the query.", + "type": [ + "string", + "null" + ] + }, + "new_update_period": { + "description": "*new_update_period** is a new update period of the query.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "query_id": { + "description": "*query_id** is the ID of the query we want to update.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RemoveInterchainQuery removes as interchain query.", + "type": "object", + "required": [ + "remove_interchain_query" + ], + "properties": { + "remove_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "description": "*query_id** is ID of the query we want to remove.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "IbcTransfer sends a fungible token packet over IBC.", + "type": "object", + "required": [ + "ibc_transfer" + ], + "properties": { + "ibc_transfer": { + "type": "object", + "required": [ + "fee", + "receiver", + "sender", + "source_channel", + "source_port", + "timeout_height", + "timeout_timestamp", + "token" + ], + "properties": { + "fee": { + "$ref": "#/definitions/IbcFee" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "source_channel": { + "type": "string" + }, + "source_port": { + "type": "string" + }, + "timeout_height": { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + "timeout_timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitAdminProposal sends a proposal to neutron's Admin module. This type of messages can be only executed by Neutron DAO.", + "type": "object", + "required": [ + "submit_admin_proposal" + ], + "properties": { + "submit_admin_proposal": { + "type": "object", + "required": [ + "admin_proposal" + ], + "properties": { + "admin_proposal": { + "$ref": "#/definitions/AdminProposal" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines the struct for parameter change request.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "description": "*key** is a name of parameter. Unique for subspace.", + "type": "string" + }, + "subspace": { + "description": "*subspace** is a key of module to which the parameter to change belongs. Unique for each module.", + "type": "string" + }, + "value": { + "description": "*value** is a new value for given parameter. Non unique.", + "type": "string" + } + } + }, + "ParamChangeProposal": { + "description": "ParamChangeProposal defines the struct for single parameter change proposal.", + "type": "object", + "required": [ + "description", + "param_changes", + "title" + ], + "properties": { + "description": { + "description": "*descriptionr** is a text description of proposal. Non unique.", + "type": "string" + }, + "param_changes": { + "description": "*param_changes** is a vector of params to be changed. Non unique.", + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + }, + "title": { + "description": "*title** is a text title of proposal. Non unique.", + "type": "string" + } + } + }, + "ProtobufAny": { + "description": "Type for wrapping any protobuf message", + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "description": "*type_url** describes the type of the serialized message", + "type": "string" + }, + "value": { + "description": "*value** must be a valid serialized protocol buffer of the above specified type", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/cwd-core/src/schema/get_item_response.json b/contracts/cwd-core/src/schema/get_item_response.json new file mode 100644 index 00000000..11fd7904 --- /dev/null +++ b/contracts/cwd-core/src/schema/get_item_response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetItemResponse", + "description": "Returned by the `GetItem` query.", + "type": "object", + "properties": { + "item": { + "description": "`None` if no item with the provided key was found, `Some` otherwise.", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/cwd-core/src/schema/info_response.json b/contracts/cwd-core/src/schema/info_response.json new file mode 100644 index 00000000..a0516764 --- /dev/null +++ b/contracts/cwd-core/src/schema/info_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + } + } +} diff --git a/contracts/cwd-core/src/schema/instantiate_msg.json b/contracts/cwd-core/src/schema/instantiate_msg.json new file mode 100644 index 00000000..efec760e --- /dev/null +++ b/contracts/cwd-core/src/schema/instantiate_msg.json @@ -0,0 +1,156 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "description", + "name", + "proposal_modules_instantiate_info", + "voting_registry_module_instantiate_info" + ], + "properties": { + "dao_uri": { + "description": "Implements the DAO Star standard: https://daostar.one/EIP", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "A description of the core contract.", + "type": "string" + }, + "initial_items": { + "description": "Initial information for arbitrary contract addresses to be added to the items map. The key is the name of the item in the items map. The value is an enum that either uses an existing address or instantiates a new contract.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/InitialItem" + } + }, + "name": { + "description": "The name of the core contract.", + "type": "string" + }, + "proposal_modules_instantiate_info": { + "description": "Instantiate information for the core contract's proposal modules.", + "type": "array", + "items": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + }, + "voting_registry_module_instantiate_info": { + "description": "Instantiate information for the core contract's voting power module.", + "allOf": [ + { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + ] + } + }, + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "InitialItem": { + "description": "Information about an item to be stored in the items list.", + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "description": "The name of the item.", + "type": "string" + }, + "value": { + "description": "The value the item will have at instantiation time.", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + } +} diff --git a/contracts/cwd-core/src/schema/list_items_response.json b/contracts/cwd-core/src/schema/list_items_response.json new file mode 100644 index 00000000..9b743544 --- /dev/null +++ b/contracts/cwd-core/src/schema/list_items_response.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListItemsResponse", + "type": "array", + "items": { + "type": "string" + } +} diff --git a/contracts/cwd-core/src/schema/list_sub_daos_response.json b/contracts/cwd-core/src/schema/list_sub_daos_response.json new file mode 100644 index 00000000..69a1e8bc --- /dev/null +++ b/contracts/cwd-core/src/schema/list_sub_daos_response.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListSubDaosResponse", + "type": "array", + "items": { + "$ref": "#/definitions/SubDao" + }, + "definitions": { + "SubDao": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } + } + } +} diff --git a/contracts/cwd-core/src/schema/migrate_msg.json b/contracts/cwd-core/src/schema/migrate_msg.json new file mode 100644 index 00000000..6b9264c8 --- /dev/null +++ b/contracts/cwd-core/src/schema/migrate_msg.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "from_v1" + ], + "properties": { + "from_v1": { + "type": "object", + "properties": { + "dao_uri": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "from_compatible" + ], + "properties": { + "from_compatible": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cwd-core/src/schema/pause_info_response.json b/contracts/cwd-core/src/schema/pause_info_response.json new file mode 100644 index 00000000..eb8e1aeb --- /dev/null +++ b/contracts/cwd-core/src/schema/pause_info_response.json @@ -0,0 +1,99 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PauseInfoResponse", + "description": "Information about if the contract is currently paused.", + "oneOf": [ + { + "type": "object", + "required": [ + "Paused" + ], + "properties": { + "Paused": { + "type": "object", + "required": [ + "expiration" + ], + "properties": { + "expiration": { + "$ref": "#/definitions/Expiration" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "Unpaused" + ], + "properties": { + "Unpaused": { + "type": "object" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/schema/proposal_modules_response.json b/contracts/cwd-core/src/schema/proposal_modules_response.json new file mode 100644 index 00000000..c2940218 --- /dev/null +++ b/contracts/cwd-core/src/schema/proposal_modules_response.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalModule" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "ProposalModule": { + "description": "Top level type describing a proposal module.", + "type": "object", + "required": [ + "address", + "prefix", + "status" + ], + "properties": { + "address": { + "description": "The address of the proposal module.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "prefix": { + "description": "The URL prefix of this proposal module as derived from the module ID. Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'.", + "type": "string" + }, + "status": { + "description": "The status of the proposal module, e.g. 'Active' or 'Disabled.'", + "allOf": [ + { + "$ref": "#/definitions/ProposalModuleStatus" + } + ] + } + } + }, + "ProposalModuleStatus": { + "description": "The status of a proposal module.", + "type": "string", + "enum": [ + "Enabled", + "Disabled" + ] + } + } +} diff --git a/contracts/cwd-core/src/schema/query_msg.json b/contracts/cwd-core/src/schema/query_msg.json new file mode 100644 index 00000000..2692c274 --- /dev/null +++ b/contracts/cwd-core/src/schema/query_msg.json @@ -0,0 +1,270 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Gets the contract's config. Returns Config.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Dumps all of the core contract's state in a single query. Useful for frontends as performance for queries is more limited by network times than compute times. Returns `DumpStateResponse`.", + "type": "object", + "required": [ + "dump_state" + ], + "properties": { + "dump_state": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the address associated with an item key.", + "type": "object", + "required": [ + "get_item" + ], + "properties": { + "get_item": { + "type": "object", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Lists all of the items associted with the contract. For example, given the items `{ \"group\": \"foo\", \"subdao\": \"bar\"}` this query would return `[(\"group\", \"foo\"), (\"subdao\", \"bar\")]`.", + "type": "object", + "required": [ + "list_items" + ], + "properties": { + "list_items": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets all proposal modules associated with the contract. Returns Vec.", + "type": "object", + "required": [ + "proposal_modules" + ], + "properties": { + "proposal_modules": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets the active proposal modules associated with the contract. Returns Vec.", + "type": "object", + "required": [ + "active_proposal_modules" + ], + "properties": { + "active_proposal_modules": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about if the contract is currently paused.", + "type": "object", + "required": [ + "pause_info" + ], + "properties": { + "pause_info": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the contract's voting module. Returns Addr.", + "type": "object", + "required": [ + "voting_module" + ], + "properties": { + "voting_module": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Returns all SubDAOs with their charters in a vec start_after is bound exclusive and asks for a string address", + "type": "object", + "required": [ + "list_sub_daos" + ], + "properties": { + "list_sub_daos": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Implements the DAO Star standard: https://daostar.one/EIP", + "type": "object", + "required": [ + "dao_u_r_i" + ], + "properties": { + "dao_u_r_i": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_power_at_height" + ], + "properties": { + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_power_at_height" + ], + "properties": { + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cwd-core/src/schema/sub_dao.json b/contracts/cwd-core/src/schema/sub_dao.json new file mode 100644 index 00000000..ae12c03a --- /dev/null +++ b/contracts/cwd-core/src/schema/sub_dao.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SubDao", + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "description": "The contract address of the SubDAO", + "type": "string" + }, + "charter": { + "description": "The purpose/constitution for the SubDAO", + "type": [ + "string", + "null" + ] + } + } +} diff --git a/contracts/cwd-core/src/schema/total_power_at_height_response.json b/contracts/cwd-core/src/schema/total_power_at_height_response.json new file mode 100644 index 00000000..8018462b --- /dev/null +++ b/contracts/cwd-core/src/schema/total_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/schema/voting_module_response.json b/contracts/cwd-core/src/schema/voting_module_response.json new file mode 100644 index 00000000..fc0cb679 --- /dev/null +++ b/contracts/cwd-core/src/schema/voting_module_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingModuleResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/cwd-core/src/schema/voting_power_at_height_response.json b/contracts/cwd-core/src/schema/voting_power_at_height_response.json new file mode 100644 index 00000000..15e986bf --- /dev/null +++ b/contracts/cwd-core/src/schema/voting_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cwd-core/src/state.rs b/contracts/cwd-core/src/state.rs new file mode 100644 index 00000000..ab730e92 --- /dev/null +++ b/contracts/cwd-core/src/state.rs @@ -0,0 +1,64 @@ +use cw_utils::Expiration; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; + +/// Top level config type for core module. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Config { + /// The name of the contract. + pub name: String, + /// A description of the contract. + pub description: String, + /// The URI for the DAO as defined by the DAOstar standard + /// https://daostar.one/EIP + pub dao_uri: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +/// Top level type describing a proposal module. +pub struct ProposalModule { + /// The address of the proposal module. + pub address: Addr, + /// The URL prefix of this proposal module as derived from the module ID. + /// Prefixes are mapped to letters, e.g. 0 is 'A', and 26 is 'AA'. + pub prefix: String, + /// The status of the proposal module, e.g. 'Active' or 'Disabled.' + pub status: ProposalModuleStatus, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +/// The status of a proposal module. +pub enum ProposalModuleStatus { + Enabled, + Disabled, +} + +/// The current configuration of the module. +pub const CONFIG: Item = Item::new("config_v2"); + +/// The time the DAO will unpause. Here be dragons: this is not set if +/// the DAO has never been paused. +pub const PAUSED: Item = Item::new("paused"); + +/// The voting module associated with this contract. +pub const VOTING_REGISTRY_MODULE: Item = Item::new("voting_module"); + +/// The proposal modules associated with this contract. +/// When we change the data format of this map, we update the key (previously "proposal_modules") +/// to create a new namespace for the changed state. +pub const PROPOSAL_MODULES: Map = Map::new("proposal_modules_v2"); + +/// The count of active proposal modules associated with this contract. +pub const ACTIVE_PROPOSAL_MODULE_COUNT: Item = Item::new("active_proposal_module_count"); + +/// The count of total proposal modules associated with this contract. +pub const TOTAL_PROPOSAL_MODULE_COUNT: Item = Item::new("total_proposal_module_count"); + +// General purpose KV store for DAO associated state. +pub const ITEMS: Map = Map::new("items"); + +/// List of SubDAOs associated to this DAO. Each SubDAO has an optional charter. +pub const SUBDAO_LIST: Map<&Addr, Option> = Map::new("sub_daos"); diff --git a/contracts/pre-propose/README b/contracts/pre-propose/README new file mode 100644 index 00000000..e69de29b diff --git a/contracts/pre-propose/cwd-pre-propose-single/.cargo/config b/contracts/pre-propose/cwd-pre-propose-single/.cargo/config new file mode 100644 index 00000000..336b618a --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/pre-propose/cwd-pre-propose-single/Cargo.toml b/contracts/pre-propose/cwd-pre-propose-single/Cargo.toml new file mode 100644 index 00000000..94b2b490 --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "cwd-pre-propose-single" +version = "0.2.0" +authors = ["ekez "] +edition = "2021" +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A DAO DAO pre-propose module for cwd-proposal-single for native and cw20 deposits." + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-std = "1.0.0" +cw2 = "0.13.2" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +cwd-pre-propose-base = { version = "*", path = "../../../packages/cwd-pre-propose-base" } +neutron_bindings = { package = "neutron-sdk", version = "0.1.0", git = "https://github.com/neutron-org/neutron-contracts.git" } +schemars = "0.8.8" + +[dev-dependencies] +cosmwasm-schema = "1.0.0" +cw-multi-test = "0.13.2" +cw-utils = "0.13.2" +cw4-group = "0.13.2" +cw20 = "0.13.2" +cw20-base = "0.13.2" +#cwd-voting-cw20-staked = { path = "../../voting/cwd-voting-cw20-staked" } +cwd-proposal-single = { path = "../../proposal/cwd-proposal-single" } +cwd-core = { path = "../../cwd-core" } +#cwd-voting-cw4 = { path = "../../voting/cwd-voting-cw4" } +cwd-voting = { path = "../../../packages/cwd-voting" } +cw-denom = { path = "../../../packages/cw-denom" } +cwd-interface = { path = "../../../packages/cwd-interface" } +#cwd-testing = { path = "../../../packages/cwd-testing" } +cwd-proposal-hooks = { path = "../../../packages/cwd-proposal-hooks" } diff --git a/contracts/pre-propose/cwd-pre-propose-single/README.md b/contracts/pre-propose/cwd-pre-propose-single/README.md new file mode 100644 index 00000000..fe813293 --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/README.md @@ -0,0 +1,27 @@ +# Single choice proposal deposit contract + +This is a pre-propose module that manages proposal deposits for the +`cwd-proposal-single` proposal module. + +It may accept either native ([bank +module](https://docs.cosmos.network/main/modules/bank/)), +[cw20](https://github.com/CosmWasm/cw-plus/tree/bc339368b1ee33c97c55a19d4cff983c7708ce36/packages/cw20) +tokens, or no tokens as a deposit. If a proposal deposit is enabled +the following refund strategies are avaliable: + +1. Never refund deposits. All deposits are sent to the DAO on proposal + completion. +2. Always refund deposits. Deposits are returned to the proposer on + proposal completion. +3. Only refund passed proposals. Deposits are only returned to the + proposer if the proposal passes. Otherwise, they are sent to the + DAO. + +This module may also be configured to only accept proposals from +members (addresses with voting power) of the DAO. + +Here is a flowchart showing the proposal creation process using this +module: + +![](https://bafkreig42cxswefi2ks7vhrwyvkcnumbnwdk7ov643yaafm7loi6vh2gja.ipfs.nftstorage.link) + diff --git a/contracts/pre-propose/cwd-pre-propose-single/examples/schema.rs b/contracts/pre-propose/cwd-pre-propose-single/examples/schema.rs new file mode 100644 index 00000000..c269e8ca --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/examples/schema.rs @@ -0,0 +1,22 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_std::Addr; +use cwd_pre_propose_base::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use cwd_pre_propose_single::{contract::ProposeMessage, DepositInfoResponse}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(DepositInfoResponse), &out_dir); + + export_schema_with_title(&schema_for!(Addr), &out_dir, "ProposalModuleResponse"); + export_schema_with_title(&schema_for!(Addr), &out_dir, "DaoResponse"); +} diff --git a/contracts/pre-propose/cwd-pre-propose-single/schema/dao_response.json b/contracts/pre-propose/cwd-pre-propose-single/schema/dao_response.json new file mode 100644 index 00000000..9518ba3b --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/schema/dao_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/pre-propose/cwd-pre-propose-single/schema/deposit_info_response.json b/contracts/pre-propose/cwd-pre-propose-single/schema/deposit_info_response.json new file mode 100644 index 00000000..76590d23 --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/schema/deposit_info_response.json @@ -0,0 +1,130 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DepositInfoResponse", + "type": "object", + "required": [ + "proposer" + ], + "properties": { + "deposit_info": { + "description": "The deposit that has been paid for the specified proposal.", + "anyOf": [ + { + "$ref": "#/definitions/CheckedDepositInfo" + }, + { + "type": "null" + } + ] + }, + "proposer": { + "description": "The address that created the proposal.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "CheckedDenom": { + "description": "A denom that has been checked to point to a valid asset. This enum should never be constructed literally and should always be built by calling `into_checked` on an `UncheckedDenom` instance.", + "oneOf": [ + { + "description": "A native (bank module) asset.", + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "A cw20 asset.", + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + ] + }, + "CheckedDepositInfo": { + "description": "Counterpart to the `DepositInfo` struct which has been processed. This type should never be constructed literally and should always by built by calling `into_checked` on a `DepositInfo` instance.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_policy" + ], + "properties": { + "amount": { + "description": "The number of tokens that must be deposited to create a proposal. This is validated to be non-zero if this struct is constructed by converted via the `into_checked` method on `DepositInfo`.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The address of the cw20 token to be used for proposal deposits.", + "allOf": [ + { + "$ref": "#/definitions/CheckedDenom" + } + ] + }, + "refund_policy": { + "description": "The policy used for refunding proposal deposits.", + "allOf": [ + { + "$ref": "#/definitions/DepositRefundPolicy" + } + ] + } + } + }, + "DepositRefundPolicy": { + "oneOf": [ + { + "description": "Deposits should always be refunded.", + "type": "string", + "enum": [ + "always" + ] + }, + { + "description": "Deposits should only be refunded for passed proposals.", + "type": "string", + "enum": [ + "only_passed" + ] + }, + { + "description": "Deposits should never be refunded.", + "type": "string", + "enum": [ + "never" + ] + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/pre-propose/cwd-pre-propose-single/schema/execute_msg_for__propose_message.json b/contracts/pre-propose/cwd-pre-propose-single/schema/execute_msg_for__propose_message.json new file mode 100644 index 00000000..3c757f92 --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/schema/execute_msg_for__propose_message.json @@ -0,0 +1,1497 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg_for_ProposeMessage", + "oneOf": [ + { + "description": "Creates a new proposal in the pre-propose module. MSG will be serialized and used as the proposal creation message.", + "type": "object", + "required": [ + "propose" + ], + "properties": { + "propose": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ProposeMessage" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Updates the configuration of this module. This will completely override the existing configuration. This new configuration will only apply to proposals created after the config is updated. Only the DAO may execute this message.", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "open_proposal_submission" + ], + "properties": { + "deposit_info": { + "anyOf": [ + { + "$ref": "#/definitions/UncheckedDepositInfo" + }, + { + "type": "null" + } + ] + }, + "open_proposal_submission": { + "type": "boolean" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Withdraws funds inside of this contract to the message sender. The contracts entire balance for the specifed DENOM is withdrawn to the message sender. Only the DAO may call this method.\n\nThis is intended only as an escape hatch in the event of a critical bug in this contract or it's proposal module. Withdrawing funds will cause future attempts to return proposal deposits to fail their transactions as the contract will have insufficent balance to return them. In the case of `cw-proposal-single` this transaction failure will cause the module to remove the pre-propose module from its proposal hook receivers.\n\nMore likely than not, this should NEVER BE CALLED unless a bug in this contract or the proposal module it is associated with has caused it to stop receiving proposal hook messages, or if a critical security vulnerability has been found that allows an attacker to drain proposal deposits.", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "properties": { + "denom": { + "description": "The denom to withdraw funds for. If no denom is specified, the denomination currently configured for proposal deposits will be used.\n\nYou may want to specify a denomination here if you are withdrawing funds that were previously accepted for proposal deposits but are not longer used due to an `UpdateConfig` message being executed on the contract.", + "anyOf": [ + { + "$ref": "#/definitions/UncheckedDenom" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Handles proposal hook fired by the associated proposal module when a proposal is created. By default, the base contract will return deposits proposals, when they are closed. when proposals are executed, or, if it is refunding failed", + "type": "object", + "required": [ + "proposal_created_hook" + ], + "properties": { + "proposal_created_hook": { + "type": "object", + "required": [ + "proposal_id", + "proposer" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposer": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Handles proposal hook fired by the associated proposal module when a proposal is completed (ie executed or rejected). By default, the base contract will return deposits proposals, when they are closed. when proposals are executed, or, if it is refunding failed", + "type": "object", + "required": [ + "proposal_completed_hook" + ], + "properties": { + "proposal_completed_hook": { + "type": "object", + "required": [ + "new_status", + "proposal_id" + ], + "properties": { + "new_status": { + "$ref": "#/definitions/Status" + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "AdminProposal": { + "description": "AdminProposal defines the struct for various proposals which Neutron's Admin Module may accept. Currently only parameter change proposals are implemented, new types of admin proposals may be implemented in future.", + "type": "object", + "properties": { + "param_change_proposal": { + "description": "*param_change_proposal** is a parameter change proposal field.", + "anyOf": [ + { + "$ref": "#/definitions/ParamChangeProposal" + }, + { + "type": "null" + } + ] + } + } + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_NeutronMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/NeutronMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DepositRefundPolicy": { + "oneOf": [ + { + "description": "Deposits should always be refunded.", + "type": "string", + "enum": [ + "always" + ] + }, + { + "description": "Deposits should only be refunded for passed proposals.", + "type": "string", + "enum": [ + "only_passed" + ] + }, + { + "description": "Deposits should never be refunded.", + "type": "string", + "enum": [ + "never" + ] + } + ] + }, + "DepositToken": { + "description": "Information about the token to use for proposal deposits.", + "oneOf": [ + { + "description": "Use a specific token address as the deposit token.", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "$ref": "#/definitions/UncheckedDenom" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Use the token address of the associated DAO's voting module. NOTE: in order to use the token address of the voting module the voting module must (1) use a cw20 token and (2) implement the `TokenContract {}` query type defined by `cwd_macros::token_query`. Failing to implement that and using this option will cause instantiation to fail.", + "type": "object", + "required": [ + "voting_module_token" + ], + "properties": { + "voting_module_token": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/VoteOption" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcFee": { + "type": "object", + "required": [ + "ack_fee", + "recv_fee", + "timeout_fee" + ], + "properties": { + "ack_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recv_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "timeout_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "KVKey": { + "description": "Describes a KV key for which you want to get value from the storage on remote chain", + "type": "object", + "required": [ + "key", + "path" + ], + "properties": { + "key": { + "description": "*key** is a key you want to read from the storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "*path** is a path to the storage (storage prefix) where you want to read value by key (usually name of cosmos-sdk module: 'staking', 'bank', etc.)", + "type": "string" + } + } + }, + "NeutronMsg": { + "description": "A number of Custom messages that can call into the Neutron bindings.", + "oneOf": [ + { + "description": "RegisterInterchainAccount registers an interchain account on remote chain.", + "type": "object", + "required": [ + "register_interchain_account" + ], + "properties": { + "register_interchain_account": { + "type": "object", + "required": [ + "connection_id", + "interchain_account_id" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "interchain_account_id": { + "description": "**interchain_account_id** is an identifier of your new interchain account. Can be any string. This identifier allows contracts to have multiple interchain accounts on remote chains.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitTx starts the process of executing any Cosmos-SDK *msgs* on remote chain.", + "type": "object", + "required": [ + "submit_tx" + ], + "properties": { + "submit_tx": { + "type": "object", + "required": [ + "connection_id", + "fee", + "interchain_account_id", + "memo", + "msgs", + "timeout" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "fee": { + "description": "**fee** is an ibc fee for the transaction.", + "allOf": [ + { + "$ref": "#/definitions/IbcFee" + } + ] + }, + "interchain_account_id": { + "description": "*interchain_account_id** is an identifier of your interchain account from which you want to execute msgs.", + "type": "string" + }, + "memo": { + "description": "*memo** is a memo you want to attach to your interchain transaction.It behaves like a memo in usual Cosmos transaction.", + "type": "string" + }, + "msgs": { + "description": "*msgs** is a list of protobuf encoded Cosmos-SDK messages you want to execute on remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/ProtobufAny" + } + }, + "timeout": { + "description": "*timeout** is a timeout in seconds after which the packet times out.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery registers an interchain query.", + "type": "object", + "required": [ + "register_interchain_query" + ], + "properties": { + "register_interchain_query": { + "type": "object", + "required": [ + "connection_id", + "keys", + "query_type", + "transactions_filter", + "update_period" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "keys": { + "description": "*keys** is the KV-storage keys for which we want to get values from remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "query_type": { + "description": "*query_type** is a query type identifier ('tx' or 'kv' for now).", + "type": "string" + }, + "transactions_filter": { + "description": "*transactions_filter** is the filter for transaction search ICQ.", + "type": "string" + }, + "update_period": { + "description": "*update_period** is used to say how often the query must be updated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery updates an interchain query.", + "type": "object", + "required": [ + "update_interchain_query" + ], + "properties": { + "update_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "new_keys": { + "description": "*new_keys** is the new query keys to retrive.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "new_transactions_filter": { + "description": "*new_transactions_filter** is a new transactions filter of the query.", + "type": [ + "string", + "null" + ] + }, + "new_update_period": { + "description": "*new_update_period** is a new update period of the query.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "query_id": { + "description": "*query_id** is the ID of the query we want to update.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RemoveInterchainQuery removes as interchain query.", + "type": "object", + "required": [ + "remove_interchain_query" + ], + "properties": { + "remove_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "description": "*query_id** is ID of the query we want to remove.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "IbcTransfer sends a fungible token packet over IBC.", + "type": "object", + "required": [ + "ibc_transfer" + ], + "properties": { + "ibc_transfer": { + "type": "object", + "required": [ + "fee", + "receiver", + "sender", + "source_channel", + "source_port", + "timeout_height", + "timeout_timestamp", + "token" + ], + "properties": { + "fee": { + "$ref": "#/definitions/IbcFee" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "source_channel": { + "type": "string" + }, + "source_port": { + "type": "string" + }, + "timeout_height": { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + "timeout_timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitAdminProposal sends a proposal to neutron's Admin module. This type of messages can be only executed by Neutron DAO.", + "type": "object", + "required": [ + "submit_admin_proposal" + ], + "properties": { + "submit_admin_proposal": { + "type": "object", + "required": [ + "admin_proposal" + ], + "properties": { + "admin_proposal": { + "$ref": "#/definitions/AdminProposal" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines the struct for parameter change request.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "description": "*key** is a name of parameter. Unique for subspace.", + "type": "string" + }, + "subspace": { + "description": "*subspace** is a key of module to which the parameter to change belongs. Unique for each module.", + "type": "string" + }, + "value": { + "description": "*value** is a new value for given parameter. Non unique.", + "type": "string" + } + } + }, + "ParamChangeProposal": { + "description": "ParamChangeProposal defines the struct for single parameter change proposal.", + "type": "object", + "required": [ + "description", + "param_changes", + "title" + ], + "properties": { + "description": { + "description": "*descriptionr** is a text description of proposal. Non unique.", + "type": "string" + }, + "param_changes": { + "description": "*param_changes** is a vector of params to be changed. Non unique.", + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + }, + "title": { + "description": "*title** is a text title of proposal. Non unique.", + "type": "string" + } + } + }, + "ProposeMessage": { + "oneOf": [ + { + "type": "object", + "required": [ + "propose" + ], + "properties": { + "propose": { + "type": "object", + "required": [ + "description", + "msgs", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "msgs": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_NeutronMsg" + } + }, + "title": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ProtobufAny": { + "description": "Type for wrapping any protobuf message", + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "description": "*type_url** describes the type of the serialized message", + "type": "string" + }, + "value": { + "description": "*value** must be a valid serialized protocol buffer of the above specified type", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Status": { + "oneOf": [ + { + "description": "The proposal is open for voting.", + "type": "string", + "enum": [ + "open" + ] + }, + { + "description": "The proposal has been rejected.", + "type": "string", + "enum": [ + "rejected" + ] + }, + { + "description": "The proposal has been passed but has not been executed.", + "type": "string", + "enum": [ + "passed" + ] + }, + { + "description": "The proposal has been passed and executed.", + "type": "string", + "enum": [ + "executed" + ] + }, + { + "description": "The proposal has failed or expired and has been closed. A proposal deposit refund has been issued if applicable.", + "type": "string", + "enum": [ + "closed" + ] + }, + { + "description": "The proposal's execution failed.", + "type": "string", + "enum": [ + "execution_failed" + ] + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "UncheckedDenom": { + "description": "A denom that has not been checked to confirm it points to a valid asset.", + "oneOf": [ + { + "description": "A native (bank module) asset.", + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "A cw20 asset.", + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "UncheckedDepositInfo": { + "description": "Information about the deposit required to create a proposal.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_policy" + ], + "properties": { + "amount": { + "description": "The number of tokens that must be deposited to create a proposal. Must be a positive, non-zero number.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The address of the token to be used for proposal deposits.", + "allOf": [ + { + "$ref": "#/definitions/DepositToken" + } + ] + }, + "refund_policy": { + "description": "The policy used for refunding deposits on proposal completion.", + "allOf": [ + { + "$ref": "#/definitions/DepositRefundPolicy" + } + ] + } + } + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/pre-propose/cwd-pre-propose-single/schema/instantiate_msg.json b/contracts/pre-propose/cwd-pre-propose-single/schema/instantiate_msg.json new file mode 100644 index 00000000..cae0ab79 --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/schema/instantiate_msg.json @@ -0,0 +1,161 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "open_proposal_submission" + ], + "properties": { + "deposit_info": { + "description": "Information about the deposit requirements for this module. None if no deposit.", + "anyOf": [ + { + "$ref": "#/definitions/UncheckedDepositInfo" + }, + { + "type": "null" + } + ] + }, + "open_proposal_submission": { + "description": "If false, only members (addresses with voting power) may create proposals in the DAO. Otherwise, any address may create a proposal so long as they pay the deposit.", + "type": "boolean" + } + }, + "definitions": { + "DepositRefundPolicy": { + "oneOf": [ + { + "description": "Deposits should always be refunded.", + "type": "string", + "enum": [ + "always" + ] + }, + { + "description": "Deposits should only be refunded for passed proposals.", + "type": "string", + "enum": [ + "only_passed" + ] + }, + { + "description": "Deposits should never be refunded.", + "type": "string", + "enum": [ + "never" + ] + } + ] + }, + "DepositToken": { + "description": "Information about the token to use for proposal deposits.", + "oneOf": [ + { + "description": "Use a specific token address as the deposit token.", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "$ref": "#/definitions/UncheckedDenom" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Use the token address of the associated DAO's voting module. NOTE: in order to use the token address of the voting module the voting module must (1) use a cw20 token and (2) implement the `TokenContract {}` query type defined by `cwd_macros::token_query`. Failing to implement that and using this option will cause instantiation to fail.", + "type": "object", + "required": [ + "voting_module_token" + ], + "properties": { + "voting_module_token": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UncheckedDenom": { + "description": "A denom that has not been checked to confirm it points to a valid asset.", + "oneOf": [ + { + "description": "A native (bank module) asset.", + "type": "object", + "required": [ + "native" + ], + "properties": { + "native": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "A cw20 asset.", + "type": "object", + "required": [ + "cw20" + ], + "properties": { + "cw20": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + }, + "UncheckedDepositInfo": { + "description": "Information about the deposit required to create a proposal.", + "type": "object", + "required": [ + "amount", + "denom", + "refund_policy" + ], + "properties": { + "amount": { + "description": "The number of tokens that must be deposited to create a proposal. Must be a positive, non-zero number.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "denom": { + "description": "The address of the token to be used for proposal deposits.", + "allOf": [ + { + "$ref": "#/definitions/DepositToken" + } + ] + }, + "refund_policy": { + "description": "The policy used for refunding deposits on proposal completion.", + "allOf": [ + { + "$ref": "#/definitions/DepositRefundPolicy" + } + ] + } + } + } + } +} diff --git a/contracts/pre-propose/cwd-pre-propose-single/schema/proposal_module_response.json b/contracts/pre-propose/cwd-pre-propose-single/schema/proposal_module_response.json new file mode 100644 index 00000000..839797fc --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/schema/proposal_module_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalModuleResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/pre-propose/cwd-pre-propose-single/schema/query_msg.json b/contracts/pre-propose/cwd-pre-propose-single/schema/query_msg.json new file mode 100644 index 00000000..a057bb1e --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/schema/query_msg.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Gets the proposal module that this pre propose module is associated with. Returns `Addr`.", + "type": "object", + "required": [ + "proposal_module" + ], + "properties": { + "proposal_module": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the DAO (cw-dao-core) module this contract is associated with. Returns `Addr`.", + "type": "object", + "required": [ + "dao" + ], + "properties": { + "dao": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the module's configuration. Returns `state::Config`.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets the deposit info for the proposal identified by PROPOSAL_ID. Returns `DepositInfoResponse`.", + "type": "object", + "required": [ + "deposit_info" + ], + "properties": { + "deposit_info": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/pre-propose/cwd-pre-propose-single/src/contract.rs b/contracts/pre-propose/cwd-pre-propose-single/src/contract.rs new file mode 100644 index 00000000..606ae375 --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/src/contract.rs @@ -0,0 +1,119 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cw2::set_contract_version; +use neutron_bindings::bindings::msg::NeutronMsg; + +use cwd_pre_propose_base::{ + error::PreProposeError, + msg::{ExecuteMsg as ExecuteBase, InstantiateMsg as InstantiateBase, QueryMsg as QueryBase}, + state::PreProposeContract, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +pub(crate) const CONTRACT_NAME: &str = "crates.io:cwd-pre-propose-single"; +pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(Serialize, JsonSchema, Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum ProposeMessage { + Propose { + title: String, + description: String, + msgs: Vec>, + }, +} + +pub type InstantiateMsg = InstantiateBase; +pub type ExecuteMsg = ExecuteBase; +pub type QueryMsg = QueryBase; + +/// Internal version of the propose message that includes the +/// `proposer` field. The module will fill this in based on the sender +/// of the external message. +#[derive(Serialize, JsonSchema, Deserialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +enum ProposeMessageInternal { + Propose { + title: String, + description: String, + msgs: Vec>, + proposer: Option, + }, +} + +type PrePropose = PreProposeContract; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let resp = PrePropose::default().instantiate(deps.branch(), env, info, msg)?; + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(resp) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + // We don't want to expose the `proposer` field on the propose + // message externally as that is to be set by this module. Here, + // we transform an external message which omits that field into an + // internal message which sets it. + type ExecuteInternal = ExecuteBase; + let internalized = match msg { + ExecuteMsg::Propose { + msg: + ProposeMessage::Propose { + title, + description, + msgs, + }, + } => ExecuteInternal::Propose { + msg: ProposeMessageInternal::Propose { + // Fill in proposer based on message sender. + proposer: Some(info.sender.to_string()), + title, + description, + msgs, + }, + }, + ExecuteMsg::Withdraw { denom } => ExecuteInternal::Withdraw { denom }, + ExecuteMsg::UpdateConfig { + deposit_info, + open_proposal_submission, + } => ExecuteInternal::UpdateConfig { + deposit_info, + open_proposal_submission, + }, + ExecuteMsg::ProposalCreatedHook { + proposal_id, + proposer, + } => ExecuteInternal::ProposalCreatedHook { + proposal_id, + proposer, + }, + ExecuteMsg::ProposalCompletedHook { + proposal_id, + new_status, + } => ExecuteInternal::ProposalCompletedHook { + proposal_id, + new_status, + }, + }; + + PrePropose::default().execute(deps, env, info, internalized) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + PrePropose::default().query(deps, env, msg) +} diff --git a/contracts/pre-propose/cwd-pre-propose-single/src/lib.rs b/contracts/pre-propose/cwd-pre-propose-single/src/lib.rs new file mode 100644 index 00000000..d9b96e43 --- /dev/null +++ b/contracts/pre-propose/cwd-pre-propose-single/src/lib.rs @@ -0,0 +1,8 @@ +pub mod contract; + +pub use contract::{ExecuteMsg, InstantiateMsg, ProposeMessage, QueryMsg}; + +// Exporting these means that contracts interacting with this one don't +// need an explicit dependency on the base contract to read queries. +pub use cwd_pre_propose_base::msg::DepositInfoResponse; +pub use cwd_pre_propose_base::state::Config; diff --git a/contracts/proposal/cwd-proposal-single/.cargo/config b/contracts/proposal/cwd-proposal-single/.cargo/config new file mode 100644 index 00000000..336b618a --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/proposal/cwd-proposal-single/Cargo.toml b/contracts/proposal/cwd-proposal-single/Cargo.toml new file mode 100644 index 00000000..29b8168d --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "cwd-proposal-single" +version = "0.2.0" +authors = ["ekez "] +edition = "2021" +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A DAO DAO proposal module for single choice (yes / no) voting." + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +neutron_bindings = { package = "neutron-sdk", git = "https://github.com/neutron-org/neutron-contracts.git" } +cosmwasm-std = { version = "1.0.0", features = ["ibc3"] } +cosmwasm-storage = { version = "1.0.0" } +cw-storage-plus = "0.13" +cw-utils = "0.13" +cw2 = "0.13" +cw20 = "0.13" +cw3 = "0.13" +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } + +cwd-core = { path = "../../cwd-core", features = ["library"] } +cwd-macros = { path = "../../../packages/cwd-macros" } +cwd-pre-propose-base = { path = "../../../packages/cwd-pre-propose-base" } +cwd-pre-propose-single = { path = "../../pre-propose/cwd-pre-propose-single" } +cwd-interface = { path = "../../../packages/cwd-interface" } +cwd-voting = { path = "../../../packages/cwd-voting" } +cwd-hooks = { path = "../../../packages/cwd-hooks" } +cwd-proposal-hooks = { path = "../../../packages/cwd-proposal-hooks" } +cwd-vote-hooks = { path = "../../../packages/cwd-vote-hooks" } + +voting-v1 = { package = "voting", version = "0.1.0", git = "https://github.com/DA0-DA0/dao-contracts.git", tag = "v1.0.0" } +cw-proposal-single-v1 = { package = "cw-proposal-single", version = "0.1.0", git = "https://github.com/DA0-DA0/dao-contracts.git", tag = "v1.0.0" } + +[dev-dependencies] +cosmwasm-schema = { version = "1.0.0" } +cw-multi-test = "0.13" +cw-denom = { path = "../../../packages/cw-denom" } +cw20-base = "0.13" +cw721-base = "0.13" +cw4 = "0.13" +cw4-group = "0.13" diff --git a/contracts/proposal/cwd-proposal-single/README.md b/contracts/proposal/cwd-proposal-single/README.md new file mode 100644 index 00000000..85ded42d --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/README.md @@ -0,0 +1,37 @@ +# cw-proposal-single + +A proposal module for a DAO DAO DAO which supports simple "yes", "no", +"abstain" voting. Proposals may have associated messages which will be +executed by the core module upon the proposal being passed and +executed. + +For more information about how these modules fit together see +[this](https://github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-v1-Contracts-Design) +wiki page. + +For information about how this module counts votes and handles passing +thresholds see +[this](https://github.com/DA0-DA0/dao-contracts/wiki/A-brief-overview-of-DAO-DAO-voting#proposal-status) +wiki page. + +## Proposal deposits + +Proposal deposits for this module are handled by the +[`cwd-pre-propose-single`](../../pre-propose/cwd-pre-propose-single) +contract. + +## Hooks + +This module supports hooks for voting and proposal status changes. One +may register a contract to receive these hooks with the `AddVoteHook` +and `AddProposalHook` methods. Upon registration the contract will +receive messages whenever a vote is cast and a proposal's status +changes (for example, when the proposal passes). + +The format for these hook messages can be located in the +`proposal-hooks` and `vote-hooks` packages located in +`packages/proposal-hooks` and `packages/vote-hooks` respectively. + +To stop an invalid hook receiver from locking the proposal module +receivers will be removed from the hook list if they error when +handling a hook. diff --git a/contracts/proposal/cwd-proposal-single/examples/schema.rs b/contracts/proposal/cwd-proposal-single/examples/schema.rs new file mode 100644 index 00000000..871f1750 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/examples/schema.rs @@ -0,0 +1,67 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_std::Addr; +use cwd_hooks::HooksResponse; +use cwd_interface::voting::InfoResponse; +use cwd_proposal_single::{ + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + query::{ProposalListResponse, ProposalResponse, VoteListResponse, VoteResponse}, + state::Config, +}; +use cwd_voting::pre_propose::ProposalCreationPolicy; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(MigrateMsg), &out_dir); + + export_schema(&schema_for!(InfoResponse), &out_dir); + export_schema(&schema_for!(ProposalResponse), &out_dir); + export_schema(&schema_for!(VoteResponse), &out_dir); + + // Auto TS code generation expects the query return type as QueryNameResponse + // Here we map query resonses to the correct name + export_schema_with_title(&schema_for!(Config), &out_dir, "ConfigResponse"); + export_schema_with_title( + &schema_for!(Vec), + &out_dir, + "GovernanceModulesResponse", + ); + export_schema_with_title( + &schema_for!(ProposalListResponse), + &out_dir, + "ListProposalsResponse", + ); + export_schema_with_title( + &schema_for!(VoteListResponse), + &out_dir, + "ListVotesResponse", + ); + export_schema_with_title(&schema_for!(u64), &out_dir, "ProposalCountResponse"); + export_schema_with_title( + &schema_for!(ProposalListResponse), + &out_dir, + "ReverseProposalsResponse", + ); + export_schema_with_title(&schema_for!(Addr), &out_dir, "DaoResponse"); + export_schema_with_title( + &schema_for!(HooksResponse), + &out_dir, + "ProposalHooksResponse", + ); + export_schema_with_title(&schema_for!(HooksResponse), &out_dir, "VoteHooksResponse"); + export_schema_with_title(&schema_for!(VoteResponse), &out_dir, "GetVoteResponse"); + export_schema_with_title( + &schema_for!(ProposalCreationPolicy), + &out_dir, + "ProposalCreationPolicyResponse", + ); +} diff --git a/contracts/proposal/cwd-proposal-single/schema/config_response.json b/contracts/proposal/cwd-proposal-single/schema/config_response.json new file mode 100644 index 00000000..ae289bea --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/config_response.json @@ -0,0 +1,209 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigResponse", + "description": "The governance module's configuration.", + "type": "object", + "required": [ + "allow_revoting", + "close_proposal_on_execution_failure", + "dao", + "max_voting_period", + "threshold" + ], + "properties": { + "allow_revoting": { + "description": "Allows changing votes before the proposal expires. If this is enabled proposals will not be able to complete early as final vote information is not known until the time of proposal expiration.", + "type": "boolean" + }, + "close_proposal_on_execution_failure": { + "description": "If set to true proposals will be closed if their execution fails. Otherwise, proposals will remain open after execution failure. For example, with this enabled a proposal to send 5 tokens out of a DAO's treasury with 4 tokens would be closed when it is executed. With this disabled, that same proposal would remain open until the DAO's treasury was large enough for it to be executed.", + "type": "boolean" + }, + "dao": { + "description": "The address of the DAO that this governance module is associated with.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "max_voting_period": { + "description": "The default maximum amount of time a proposal may be voted on before expiring.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "min_voting_period": { + "description": "The minimum amount of time a proposal must be open before passing. A proposal may fail before this amount of time has elapsed, but it will not pass. This can be useful for preventing governance attacks wherein an attacker aquires a large number of tokens and forces a proposal through.", + "anyOf": [ + { + "$ref": "#/definitions/Duration" + }, + { + "type": "null" + } + ] + }, + "threshold": { + "description": "The threshold a proposal must reach to complete.", + "allOf": [ + { + "$ref": "#/definitions/Threshold" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "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" + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "PercentageThreshold": { + "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `yes_votes >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", + "oneOf": [ + { + "description": "The majority of voters must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "majority" + ], + "properties": { + "majority": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "percent" + ], + "properties": { + "percent": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + ] + }, + "Threshold": { + "description": "The ways a proposal may reach its passing / failing threshold.", + "oneOf": [ + { + "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/PercentageThreshold" + }, + "threshold": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "threshold" + ], + "properties": { + "threshold": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/dao_response.json b/contracts/proposal/cwd-proposal-single/schema/dao_response.json new file mode 100644 index 00000000..9518ba3b --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/dao_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/proposal/cwd-proposal-single/schema/execute_msg.json b/contracts/proposal/cwd-proposal-single/schema/execute_msg.json new file mode 100644 index 00000000..c4e62171 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/execute_msg.json @@ -0,0 +1,1726 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Creates a proposal in the module.", + "type": "object", + "required": [ + "propose" + ], + "properties": { + "propose": { + "type": "object", + "required": [ + "description", + "msgs", + "title" + ], + "properties": { + "description": { + "description": "A description of the proposal.", + "type": "string" + }, + "msgs": { + "description": "The messages that should be executed in response to this proposal passing.", + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_NeutronMsg" + } + }, + "proposer": { + "description": "The address creating the proposal. If no pre-propose module is attached to this module this must always be None as the proposer is the sender of the propose message. If a pre-propose module is attached, this must be Some and will set the proposer of the proposal it creates.", + "type": [ + "string", + "null" + ] + }, + "title": { + "description": "The title of the proposal.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Votes on a proposal. Voting power is determined by the DAO's voting power module.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "description": "The ID of the proposal to vote on.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The senders position on the proposal.", + "allOf": [ + { + "$ref": "#/definitions/Vote" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Causes the messages associated with a passed proposal to be executed by the DAO.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "description": "The ID of the proposal to execute.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Closes a proposal that has failed (either not passed or timed out). If applicable this will cause the proposal deposit associated wth said proposal to be returned.", + "type": "object", + "required": [ + "close" + ], + "properties": { + "close": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "description": "The ID of the proposal to close.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Updates the governance module's config.", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "allow_revoting", + "close_proposal_on_execution_failure", + "dao", + "max_voting_period", + "threshold" + ], + "properties": { + "allow_revoting": { + "description": "Allows changing votes before the proposal expires. If this is enabled proposals will not be able to complete early as final vote information is not known until the time of proposal expiration.", + "type": "boolean" + }, + "close_proposal_on_execution_failure": { + "description": "If set to true proposals will be closed if their execution fails. Otherwise, proposals will remain open after execution failure. For example, with this enabled a proposal to send 5 tokens out of a DAO's treasury with 4 tokens would be closed when it is executed. With this disabled, that same proposal would remain open until the DAO's treasury was large enough for it to be executed.", + "type": "boolean" + }, + "dao": { + "description": "The address if tge DAO that this governance module is associated with.", + "type": "string" + }, + "max_voting_period": { + "description": "The default maximum amount of time a proposal may be voted on before expiring. This will only apply to proposals created after the config update.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "min_voting_period": { + "description": "The minimum amount of time a proposal must be open before passing. A proposal may fail before this amount of time has elapsed, but it will not pass. This can be useful for preventing governance attacks wherein an attacker aquires a large number of tokens and forces a proposal through.", + "anyOf": [ + { + "$ref": "#/definitions/Duration" + }, + { + "type": "null" + } + ] + }, + "threshold": { + "description": "The new proposal passing threshold. This will only apply to proposals created after the config update.", + "allOf": [ + { + "$ref": "#/definitions/Threshold" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Update's the proposal creation policy used for this module. Only the DAO may call this method.", + "type": "object", + "required": [ + "update_pre_propose_info" + ], + "properties": { + "update_pre_propose_info": { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/PreProposeInfo" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Adds an address as a consumer of proposal hooks. Consumers of proposal hooks have hook messages executed on them whenever the status of a proposal changes or a proposal is created. If a consumer contract errors when handling a hook message it will be removed from the list of consumers.", + "type": "object", + "required": [ + "add_proposal_hook" + ], + "properties": { + "add_proposal_hook": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Removes a consumer of proposal hooks.", + "type": "object", + "required": [ + "remove_proposal_hook" + ], + "properties": { + "remove_proposal_hook": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Adds an address as a consumer of vote hooks. Consumers of vote hooks have hook messages executed on them whenever the a vote is cast. If a consumer contract errors when handling a hook message it will be removed from the list of consumers.", + "type": "object", + "required": [ + "add_vote_hook" + ], + "properties": { + "add_vote_hook": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Removed a consumer of vote hooks.", + "type": "object", + "required": [ + "remove_vote_hook" + ], + "properties": { + "remove_vote_hook": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "AdminProposal": { + "description": "AdminProposal defines the struct for various proposals which Neutron's Admin Module may accept. Currently only parameter change proposals are implemented, new types of admin proposals may be implemented in future.", + "type": "object", + "properties": { + "param_change_proposal": { + "description": "*param_change_proposal** is a parameter change proposal field.", + "anyOf": [ + { + "$ref": "#/definitions/ParamChangeProposal" + }, + { + "type": "null" + } + ] + } + } + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_NeutronMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/NeutronMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "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" + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/VoteOption" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcFee": { + "type": "object", + "required": [ + "ack_fee", + "recv_fee", + "timeout_fee" + ], + "properties": { + "ack_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recv_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "timeout_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "KVKey": { + "description": "Describes a KV key for which you want to get value from the storage on remote chain", + "type": "object", + "required": [ + "key", + "path" + ], + "properties": { + "key": { + "description": "*key** is a key you want to read from the storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "*path** is a path to the storage (storage prefix) where you want to read value by key (usually name of cosmos-sdk module: 'staking', 'bank', etc.)", + "type": "string" + } + } + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "NeutronMsg": { + "description": "A number of Custom messages that can call into the Neutron bindings.", + "oneOf": [ + { + "description": "RegisterInterchainAccount registers an interchain account on remote chain.", + "type": "object", + "required": [ + "register_interchain_account" + ], + "properties": { + "register_interchain_account": { + "type": "object", + "required": [ + "connection_id", + "interchain_account_id" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "interchain_account_id": { + "description": "**interchain_account_id** is an identifier of your new interchain account. Can be any string. This identifier allows contracts to have multiple interchain accounts on remote chains.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitTx starts the process of executing any Cosmos-SDK *msgs* on remote chain.", + "type": "object", + "required": [ + "submit_tx" + ], + "properties": { + "submit_tx": { + "type": "object", + "required": [ + "connection_id", + "fee", + "interchain_account_id", + "memo", + "msgs", + "timeout" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "fee": { + "description": "**fee** is an ibc fee for the transaction.", + "allOf": [ + { + "$ref": "#/definitions/IbcFee" + } + ] + }, + "interchain_account_id": { + "description": "*interchain_account_id** is an identifier of your interchain account from which you want to execute msgs.", + "type": "string" + }, + "memo": { + "description": "*memo** is a memo you want to attach to your interchain transaction.It behaves like a memo in usual Cosmos transaction.", + "type": "string" + }, + "msgs": { + "description": "*msgs** is a list of protobuf encoded Cosmos-SDK messages you want to execute on remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/ProtobufAny" + } + }, + "timeout": { + "description": "*timeout** is a timeout in seconds after which the packet times out.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery registers an interchain query.", + "type": "object", + "required": [ + "register_interchain_query" + ], + "properties": { + "register_interchain_query": { + "type": "object", + "required": [ + "connection_id", + "keys", + "query_type", + "transactions_filter", + "update_period" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "keys": { + "description": "*keys** is the KV-storage keys for which we want to get values from remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "query_type": { + "description": "*query_type** is a query type identifier ('tx' or 'kv' for now).", + "type": "string" + }, + "transactions_filter": { + "description": "*transactions_filter** is the filter for transaction search ICQ.", + "type": "string" + }, + "update_period": { + "description": "*update_period** is used to say how often the query must be updated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery updates an interchain query.", + "type": "object", + "required": [ + "update_interchain_query" + ], + "properties": { + "update_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "new_keys": { + "description": "*new_keys** is the new query keys to retrive.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "new_transactions_filter": { + "description": "*new_transactions_filter** is a new transactions filter of the query.", + "type": [ + "string", + "null" + ] + }, + "new_update_period": { + "description": "*new_update_period** is a new update period of the query.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "query_id": { + "description": "*query_id** is the ID of the query we want to update.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RemoveInterchainQuery removes as interchain query.", + "type": "object", + "required": [ + "remove_interchain_query" + ], + "properties": { + "remove_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "description": "*query_id** is ID of the query we want to remove.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "IbcTransfer sends a fungible token packet over IBC.", + "type": "object", + "required": [ + "ibc_transfer" + ], + "properties": { + "ibc_transfer": { + "type": "object", + "required": [ + "fee", + "receiver", + "sender", + "source_channel", + "source_port", + "timeout_height", + "timeout_timestamp", + "token" + ], + "properties": { + "fee": { + "$ref": "#/definitions/IbcFee" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "source_channel": { + "type": "string" + }, + "source_port": { + "type": "string" + }, + "timeout_height": { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + "timeout_timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitAdminProposal sends a proposal to neutron's Admin module. This type of messages can be only executed by Neutron DAO.", + "type": "object", + "required": [ + "submit_admin_proposal" + ], + "properties": { + "submit_admin_proposal": { + "type": "object", + "required": [ + "admin_proposal" + ], + "properties": { + "admin_proposal": { + "$ref": "#/definitions/AdminProposal" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines the struct for parameter change request.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "description": "*key** is a name of parameter. Unique for subspace.", + "type": "string" + }, + "subspace": { + "description": "*subspace** is a key of module to which the parameter to change belongs. Unique for each module.", + "type": "string" + }, + "value": { + "description": "*value** is a new value for given parameter. Non unique.", + "type": "string" + } + } + }, + "ParamChangeProposal": { + "description": "ParamChangeProposal defines the struct for single parameter change proposal.", + "type": "object", + "required": [ + "description", + "param_changes", + "title" + ], + "properties": { + "description": { + "description": "*descriptionr** is a text description of proposal. Non unique.", + "type": "string" + }, + "param_changes": { + "description": "*param_changes** is a vector of params to be changed. Non unique.", + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + }, + "title": { + "description": "*title** is a text title of proposal. Non unique.", + "type": "string" + } + } + }, + "PercentageThreshold": { + "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `yes_votes >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", + "oneOf": [ + { + "description": "The majority of voters must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "majority" + ], + "properties": { + "majority": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "percent" + ], + "properties": { + "percent": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + ] + }, + "PreProposeInfo": { + "oneOf": [ + { + "description": "Anyone may create a proposal free of charge.", + "type": "object", + "required": [ + "AnyoneMayPropose" + ], + "properties": { + "AnyoneMayPropose": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "The module specified in INFO has exclusive rights to proposal creation.", + "type": "object", + "required": [ + "ModuleMayPropose" + ], + "properties": { + "ModuleMayPropose": { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ProtobufAny": { + "description": "Type for wrapping any protobuf message", + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "description": "*type_url** describes the type of the serialized message", + "type": "string" + }, + "value": { + "description": "*value** must be a valid serialized protocol buffer of the above specified type", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Threshold": { + "description": "The ways a proposal may reach its passing / failing threshold.", + "oneOf": [ + { + "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/PercentageThreshold" + }, + "threshold": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "threshold" + ], + "properties": { + "threshold": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "Vote": { + "oneOf": [ + { + "description": "Marks support for the proposal.", + "type": "string", + "enum": [ + "yes" + ] + }, + { + "description": "Marks opposition to the proposal.", + "type": "string", + "enum": [ + "no" + ] + }, + { + "description": "Marks participation but does not count towards the ratio of support / opposed.", + "type": "string", + "enum": [ + "abstain" + ] + } + ] + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/get_vote_response.json b/contracts/proposal/cwd-proposal-single/schema/get_vote_response.json new file mode 100644 index 00000000..213af333 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/get_vote_response.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetVoteResponse", + "description": "Information about a vote.", + "type": "object", + "properties": { + "vote": { + "description": "None if no such vote, Some otherwise.", + "anyOf": [ + { + "$ref": "#/definitions/VoteInfo" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Vote": { + "oneOf": [ + { + "description": "Marks support for the proposal.", + "type": "string", + "enum": [ + "yes" + ] + }, + { + "description": "Marks opposition to the proposal.", + "type": "string", + "enum": [ + "no" + ] + }, + { + "description": "Marks participation but does not count towards the ratio of support / opposed.", + "type": "string", + "enum": [ + "abstain" + ] + } + ] + }, + "VoteInfo": { + "description": "Information about a vote that was cast.", + "type": "object", + "required": [ + "power", + "vote", + "voter" + ], + "properties": { + "power": { + "description": "The voting power behind the vote.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "vote": { + "description": "Position on the vote.", + "allOf": [ + { + "$ref": "#/definitions/Vote" + } + ] + }, + "voter": { + "description": "The address that voted.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + } + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/governance_modules_response.json b/contracts/proposal/cwd-proposal-single/schema/governance_modules_response.json new file mode 100644 index 00000000..152ef861 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/governance_modules_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GovernanceModulesResponse", + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/info_response.json b/contracts/proposal/cwd-proposal-single/schema/info_response.json new file mode 100644 index 00000000..a0516764 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/info_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/instantiate_msg.json b/contracts/proposal/cwd-proposal-single/schema/instantiate_msg.json new file mode 100644 index 00000000..824f08fa --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/instantiate_msg.json @@ -0,0 +1,325 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "allow_revoting", + "close_proposal_on_execution_failure", + "max_voting_period", + "pre_propose_info", + "threshold" + ], + "properties": { + "allow_revoting": { + "description": "Allows changing votes before the proposal expires. If this is enabled proposals will not be able to complete early as final vote information is not known until the time of proposal expiration.", + "type": "boolean" + }, + "close_proposal_on_execution_failure": { + "description": "If set to true proposals will be closed if their execution fails. Otherwise, proposals will remain open after execution failure. For example, with this enabled a proposal to send 5 tokens out of a DAO's treasury with 4 tokens would be closed when it is executed. With this disabled, that same proposal would remain open until the DAO's treasury was large enough for it to be executed.", + "type": "boolean" + }, + "max_voting_period": { + "description": "The default maximum amount of time a proposal may be voted on before expiring.", + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + }, + "min_voting_period": { + "description": "The minimum amount of time a proposal must be open before passing. A proposal may fail before this amount of time has elapsed, but it will not pass. This can be useful for preventing governance attacks wherein an attacker aquires a large number of tokens and forces a proposal through.", + "anyOf": [ + { + "$ref": "#/definitions/Duration" + }, + { + "type": "null" + } + ] + }, + "pre_propose_info": { + "description": "Information about what addresses may create proposals.", + "allOf": [ + { + "$ref": "#/definitions/PreProposeInfo" + } + ] + }, + "threshold": { + "description": "The threshold a proposal must reach to complete.", + "allOf": [ + { + "$ref": "#/definitions/Threshold" + } + ] + } + }, + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "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" + }, + "Duration": { + "description": "Duration is a delta of time. You can add it to a BlockInfo or Expiration to move that further in the future. Note that an height-based Duration and a time-based Expiration cannot be combined", + "oneOf": [ + { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Time in seconds", + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + }, + "ModuleInstantiateInfo": { + "description": "Information needed to instantiate a module.", + "type": "object", + "required": [ + "code_id", + "label", + "msg" + ], + "properties": { + "admin": { + "description": "CosmWasm level admin of the instantiated contract. See: ", + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "code_id": { + "description": "Code ID of the contract to be instantiated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "label": { + "description": "Label for the instantiated contract.", + "type": "string" + }, + "msg": { + "description": "Instantiate message to be used to create the contract.", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "PercentageThreshold": { + "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `yes_votes >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", + "oneOf": [ + { + "description": "The majority of voters must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "majority" + ], + "properties": { + "majority": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "percent" + ], + "properties": { + "percent": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + ] + }, + "PreProposeInfo": { + "oneOf": [ + { + "description": "Anyone may create a proposal free of charge.", + "type": "object", + "required": [ + "AnyoneMayPropose" + ], + "properties": { + "AnyoneMayPropose": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "The module specified in INFO has exclusive rights to proposal creation.", + "type": "object", + "required": [ + "ModuleMayPropose" + ], + "properties": { + "ModuleMayPropose": { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ModuleInstantiateInfo" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Threshold": { + "description": "The ways a proposal may reach its passing / failing threshold.", + "oneOf": [ + { + "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/PercentageThreshold" + }, + "threshold": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "threshold" + ], + "properties": { + "threshold": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/list_proposals_response.json b/contracts/proposal/cwd-proposal-single/schema/list_proposals_response.json new file mode 100644 index 00000000..d36e2b5d --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/list_proposals_response.json @@ -0,0 +1,1495 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListProposalsResponse", + "description": "A list of proposals returned by `ListProposals` and `ReverseProposals`.", + "type": "object", + "required": [ + "proposals" + ], + "properties": { + "proposals": { + "type": "array", + "items": { + "$ref": "#/definitions/ProposalResponse" + } + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AdminProposal": { + "description": "AdminProposal defines the struct for various proposals which Neutron's Admin Module may accept. Currently only parameter change proposals are implemented, new types of admin proposals may be implemented in future.", + "type": "object", + "properties": { + "param_change_proposal": { + "description": "*param_change_proposal** is a parameter change proposal field.", + "anyOf": [ + { + "$ref": "#/definitions/ParamChangeProposal" + }, + { + "type": "null" + } + ] + } + } + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_NeutronMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/NeutronMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "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" + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/VoteOption" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcFee": { + "type": "object", + "required": [ + "ack_fee", + "recv_fee", + "timeout_fee" + ], + "properties": { + "ack_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recv_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "timeout_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "KVKey": { + "description": "Describes a KV key for which you want to get value from the storage on remote chain", + "type": "object", + "required": [ + "key", + "path" + ], + "properties": { + "key": { + "description": "*key** is a key you want to read from the storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "*path** is a path to the storage (storage prefix) where you want to read value by key (usually name of cosmos-sdk module: 'staking', 'bank', etc.)", + "type": "string" + } + } + }, + "NeutronMsg": { + "description": "A number of Custom messages that can call into the Neutron bindings.", + "oneOf": [ + { + "description": "RegisterInterchainAccount registers an interchain account on remote chain.", + "type": "object", + "required": [ + "register_interchain_account" + ], + "properties": { + "register_interchain_account": { + "type": "object", + "required": [ + "connection_id", + "interchain_account_id" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "interchain_account_id": { + "description": "**interchain_account_id** is an identifier of your new interchain account. Can be any string. This identifier allows contracts to have multiple interchain accounts on remote chains.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitTx starts the process of executing any Cosmos-SDK *msgs* on remote chain.", + "type": "object", + "required": [ + "submit_tx" + ], + "properties": { + "submit_tx": { + "type": "object", + "required": [ + "connection_id", + "fee", + "interchain_account_id", + "memo", + "msgs", + "timeout" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "fee": { + "description": "**fee** is an ibc fee for the transaction.", + "allOf": [ + { + "$ref": "#/definitions/IbcFee" + } + ] + }, + "interchain_account_id": { + "description": "*interchain_account_id** is an identifier of your interchain account from which you want to execute msgs.", + "type": "string" + }, + "memo": { + "description": "*memo** is a memo you want to attach to your interchain transaction.It behaves like a memo in usual Cosmos transaction.", + "type": "string" + }, + "msgs": { + "description": "*msgs** is a list of protobuf encoded Cosmos-SDK messages you want to execute on remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/ProtobufAny" + } + }, + "timeout": { + "description": "*timeout** is a timeout in seconds after which the packet times out.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery registers an interchain query.", + "type": "object", + "required": [ + "register_interchain_query" + ], + "properties": { + "register_interchain_query": { + "type": "object", + "required": [ + "connection_id", + "keys", + "query_type", + "transactions_filter", + "update_period" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "keys": { + "description": "*keys** is the KV-storage keys for which we want to get values from remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "query_type": { + "description": "*query_type** is a query type identifier ('tx' or 'kv' for now).", + "type": "string" + }, + "transactions_filter": { + "description": "*transactions_filter** is the filter for transaction search ICQ.", + "type": "string" + }, + "update_period": { + "description": "*update_period** is used to say how often the query must be updated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery updates an interchain query.", + "type": "object", + "required": [ + "update_interchain_query" + ], + "properties": { + "update_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "new_keys": { + "description": "*new_keys** is the new query keys to retrive.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "new_transactions_filter": { + "description": "*new_transactions_filter** is a new transactions filter of the query.", + "type": [ + "string", + "null" + ] + }, + "new_update_period": { + "description": "*new_update_period** is a new update period of the query.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "query_id": { + "description": "*query_id** is the ID of the query we want to update.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RemoveInterchainQuery removes as interchain query.", + "type": "object", + "required": [ + "remove_interchain_query" + ], + "properties": { + "remove_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "description": "*query_id** is ID of the query we want to remove.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "IbcTransfer sends a fungible token packet over IBC.", + "type": "object", + "required": [ + "ibc_transfer" + ], + "properties": { + "ibc_transfer": { + "type": "object", + "required": [ + "fee", + "receiver", + "sender", + "source_channel", + "source_port", + "timeout_height", + "timeout_timestamp", + "token" + ], + "properties": { + "fee": { + "$ref": "#/definitions/IbcFee" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "source_channel": { + "type": "string" + }, + "source_port": { + "type": "string" + }, + "timeout_height": { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + "timeout_timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitAdminProposal sends a proposal to neutron's Admin module. This type of messages can be only executed by Neutron DAO.", + "type": "object", + "required": [ + "submit_admin_proposal" + ], + "properties": { + "submit_admin_proposal": { + "type": "object", + "required": [ + "admin_proposal" + ], + "properties": { + "admin_proposal": { + "$ref": "#/definitions/AdminProposal" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines the struct for parameter change request.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "description": "*key** is a name of parameter. Unique for subspace.", + "type": "string" + }, + "subspace": { + "description": "*subspace** is a key of module to which the parameter to change belongs. Unique for each module.", + "type": "string" + }, + "value": { + "description": "*value** is a new value for given parameter. Non unique.", + "type": "string" + } + } + }, + "ParamChangeProposal": { + "description": "ParamChangeProposal defines the struct for single parameter change proposal.", + "type": "object", + "required": [ + "description", + "param_changes", + "title" + ], + "properties": { + "description": { + "description": "*descriptionr** is a text description of proposal. Non unique.", + "type": "string" + }, + "param_changes": { + "description": "*param_changes** is a vector of params to be changed. Non unique.", + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + }, + "title": { + "description": "*title** is a text title of proposal. Non unique.", + "type": "string" + } + } + }, + "PercentageThreshold": { + "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `yes_votes >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", + "oneOf": [ + { + "description": "The majority of voters must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "majority" + ], + "properties": { + "majority": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "percent" + ], + "properties": { + "percent": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + ] + }, + "ProposalResponse": { + "description": "Information about a proposal returned by proposal queries.", + "type": "object", + "required": [ + "id", + "proposal" + ], + "properties": { + "id": { + "description": "The ID of the proposal being returned.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal": { + "$ref": "#/definitions/SingleChoiceProposal" + } + } + }, + "ProtobufAny": { + "description": "Type for wrapping any protobuf message", + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "description": "*type_url** describes the type of the serialized message", + "type": "string" + }, + "value": { + "description": "*value** must be a valid serialized protocol buffer of the above specified type", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "SingleChoiceProposal": { + "type": "object", + "required": [ + "allow_revoting", + "description", + "expiration", + "msgs", + "proposer", + "start_height", + "status", + "threshold", + "title", + "total_power", + "votes" + ], + "properties": { + "allow_revoting": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "expiration": { + "description": "The the time at which this proposal will expire and close for additional votes.", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "min_voting_period": { + "description": "The minimum amount of time this proposal must remain open for voting. The proposal may not pass unless this is expired or None.", + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "msgs": { + "description": "The messages that will be executed should this proposal pass.", + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_NeutronMsg" + } + }, + "proposer": { + "description": "The address that created this proposal.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "start_height": { + "description": "The block height at which this proposal was created. Voting power queries should query for voting power at this block height.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "$ref": "#/definitions/Status" + }, + "threshold": { + "description": "The threshold at which this proposal will pass.", + "allOf": [ + { + "$ref": "#/definitions/Threshold" + } + ] + }, + "title": { + "type": "string" + }, + "total_power": { + "description": "The total amount of voting power at the time of this proposal's creation.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "votes": { + "$ref": "#/definitions/Votes" + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Status": { + "oneOf": [ + { + "description": "The proposal is open for voting.", + "type": "string", + "enum": [ + "open" + ] + }, + { + "description": "The proposal has been rejected.", + "type": "string", + "enum": [ + "rejected" + ] + }, + { + "description": "The proposal has been passed but has not been executed.", + "type": "string", + "enum": [ + "passed" + ] + }, + { + "description": "The proposal has been passed and executed.", + "type": "string", + "enum": [ + "executed" + ] + }, + { + "description": "The proposal has failed or expired and has been closed. A proposal deposit refund has been issued if applicable.", + "type": "string", + "enum": [ + "closed" + ] + }, + { + "description": "The proposal's execution failed.", + "type": "string", + "enum": [ + "execution_failed" + ] + } + ] + }, + "Threshold": { + "description": "The ways a proposal may reach its passing / failing threshold.", + "oneOf": [ + { + "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/PercentageThreshold" + }, + "threshold": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "threshold" + ], + "properties": { + "threshold": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "Votes": { + "type": "object", + "required": [ + "abstain", + "no", + "yes" + ], + "properties": { + "abstain": { + "$ref": "#/definitions/Uint128" + }, + "no": { + "$ref": "#/definitions/Uint128" + }, + "yes": { + "$ref": "#/definitions/Uint128" + } + } + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/list_votes_response.json b/contracts/proposal/cwd-proposal-single/schema/list_votes_response.json new file mode 100644 index 00000000..a899b560 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/list_votes_response.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListVotesResponse", + "description": "Information about the votes for a proposal.", + "type": "object", + "required": [ + "votes" + ], + "properties": { + "votes": { + "type": "array", + "items": { + "$ref": "#/definitions/VoteInfo" + } + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Vote": { + "oneOf": [ + { + "description": "Marks support for the proposal.", + "type": "string", + "enum": [ + "yes" + ] + }, + { + "description": "Marks opposition to the proposal.", + "type": "string", + "enum": [ + "no" + ] + }, + { + "description": "Marks participation but does not count towards the ratio of support / opposed.", + "type": "string", + "enum": [ + "abstain" + ] + } + ] + }, + "VoteInfo": { + "description": "Information about a vote that was cast.", + "type": "object", + "required": [ + "power", + "vote", + "voter" + ], + "properties": { + "power": { + "description": "The voting power behind the vote.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "vote": { + "description": "Position on the vote.", + "allOf": [ + { + "$ref": "#/definitions/Vote" + } + ] + }, + "voter": { + "description": "The address that voted.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + } + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/migrate_msg.json b/contracts/proposal/cwd-proposal-single/schema/migrate_msg.json new file mode 100644 index 00000000..7e58c323 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/migrate_msg.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "string", + "enum": [] +} diff --git a/contracts/proposal/cwd-proposal-single/schema/proposal_count_response.json b/contracts/proposal/cwd-proposal-single/schema/proposal_count_response.json new file mode 100644 index 00000000..6786c59a --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/proposal_count_response.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalCountResponse", + "type": "integer", + "format": "uint64", + "minimum": 0.0 +} diff --git a/contracts/proposal/cwd-proposal-single/schema/proposal_creation_policy_response.json b/contracts/proposal/cwd-proposal-single/schema/proposal_creation_policy_response.json new file mode 100644 index 00000000..d8d44545 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/proposal_creation_policy_response.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalCreationPolicyResponse", + "oneOf": [ + { + "description": "Anyone may create a proposal, free of charge.", + "type": "object", + "required": [ + "Anyone" + ], + "properties": { + "Anyone": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Only ADDR may create proposals. It is expected that ADDR is a pre-propose module, though we only require that it is a valid address.", + "type": "object", + "required": [ + "Module" + ], + "properties": { + "Module": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "$ref": "#/definitions/Addr" + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/proposal_hooks_response.json b/contracts/proposal/cwd-proposal-single/schema/proposal_hooks_response.json new file mode 100644 index 00000000..b335b278 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/proposal_hooks_response.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalHooksResponse", + "type": "object", + "required": [ + "hooks" + ], + "properties": { + "hooks": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/proposal_response.json b/contracts/proposal/cwd-proposal-single/schema/proposal_response.json new file mode 100644 index 00000000..27848219 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/proposal_response.json @@ -0,0 +1,1480 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalResponse", + "description": "Information about a proposal returned by proposal queries.", + "type": "object", + "required": [ + "id", + "proposal" + ], + "properties": { + "id": { + "description": "The ID of the proposal being returned.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal": { + "$ref": "#/definitions/SingleChoiceProposal" + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AdminProposal": { + "description": "AdminProposal defines the struct for various proposals which Neutron's Admin Module may accept. Currently only parameter change proposals are implemented, new types of admin proposals may be implemented in future.", + "type": "object", + "properties": { + "param_change_proposal": { + "description": "*param_change_proposal** is a parameter change proposal field.", + "anyOf": [ + { + "$ref": "#/definitions/ParamChangeProposal" + }, + { + "type": "null" + } + ] + } + } + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_NeutronMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/NeutronMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "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" + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/VoteOption" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcFee": { + "type": "object", + "required": [ + "ack_fee", + "recv_fee", + "timeout_fee" + ], + "properties": { + "ack_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recv_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "timeout_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "KVKey": { + "description": "Describes a KV key for which you want to get value from the storage on remote chain", + "type": "object", + "required": [ + "key", + "path" + ], + "properties": { + "key": { + "description": "*key** is a key you want to read from the storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "*path** is a path to the storage (storage prefix) where you want to read value by key (usually name of cosmos-sdk module: 'staking', 'bank', etc.)", + "type": "string" + } + } + }, + "NeutronMsg": { + "description": "A number of Custom messages that can call into the Neutron bindings.", + "oneOf": [ + { + "description": "RegisterInterchainAccount registers an interchain account on remote chain.", + "type": "object", + "required": [ + "register_interchain_account" + ], + "properties": { + "register_interchain_account": { + "type": "object", + "required": [ + "connection_id", + "interchain_account_id" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "interchain_account_id": { + "description": "**interchain_account_id** is an identifier of your new interchain account. Can be any string. This identifier allows contracts to have multiple interchain accounts on remote chains.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitTx starts the process of executing any Cosmos-SDK *msgs* on remote chain.", + "type": "object", + "required": [ + "submit_tx" + ], + "properties": { + "submit_tx": { + "type": "object", + "required": [ + "connection_id", + "fee", + "interchain_account_id", + "memo", + "msgs", + "timeout" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "fee": { + "description": "**fee** is an ibc fee for the transaction.", + "allOf": [ + { + "$ref": "#/definitions/IbcFee" + } + ] + }, + "interchain_account_id": { + "description": "*interchain_account_id** is an identifier of your interchain account from which you want to execute msgs.", + "type": "string" + }, + "memo": { + "description": "*memo** is a memo you want to attach to your interchain transaction.It behaves like a memo in usual Cosmos transaction.", + "type": "string" + }, + "msgs": { + "description": "*msgs** is a list of protobuf encoded Cosmos-SDK messages you want to execute on remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/ProtobufAny" + } + }, + "timeout": { + "description": "*timeout** is a timeout in seconds after which the packet times out.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery registers an interchain query.", + "type": "object", + "required": [ + "register_interchain_query" + ], + "properties": { + "register_interchain_query": { + "type": "object", + "required": [ + "connection_id", + "keys", + "query_type", + "transactions_filter", + "update_period" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "keys": { + "description": "*keys** is the KV-storage keys for which we want to get values from remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "query_type": { + "description": "*query_type** is a query type identifier ('tx' or 'kv' for now).", + "type": "string" + }, + "transactions_filter": { + "description": "*transactions_filter** is the filter for transaction search ICQ.", + "type": "string" + }, + "update_period": { + "description": "*update_period** is used to say how often the query must be updated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery updates an interchain query.", + "type": "object", + "required": [ + "update_interchain_query" + ], + "properties": { + "update_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "new_keys": { + "description": "*new_keys** is the new query keys to retrive.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "new_transactions_filter": { + "description": "*new_transactions_filter** is a new transactions filter of the query.", + "type": [ + "string", + "null" + ] + }, + "new_update_period": { + "description": "*new_update_period** is a new update period of the query.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "query_id": { + "description": "*query_id** is the ID of the query we want to update.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RemoveInterchainQuery removes as interchain query.", + "type": "object", + "required": [ + "remove_interchain_query" + ], + "properties": { + "remove_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "description": "*query_id** is ID of the query we want to remove.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "IbcTransfer sends a fungible token packet over IBC.", + "type": "object", + "required": [ + "ibc_transfer" + ], + "properties": { + "ibc_transfer": { + "type": "object", + "required": [ + "fee", + "receiver", + "sender", + "source_channel", + "source_port", + "timeout_height", + "timeout_timestamp", + "token" + ], + "properties": { + "fee": { + "$ref": "#/definitions/IbcFee" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "source_channel": { + "type": "string" + }, + "source_port": { + "type": "string" + }, + "timeout_height": { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + "timeout_timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitAdminProposal sends a proposal to neutron's Admin module. This type of messages can be only executed by Neutron DAO.", + "type": "object", + "required": [ + "submit_admin_proposal" + ], + "properties": { + "submit_admin_proposal": { + "type": "object", + "required": [ + "admin_proposal" + ], + "properties": { + "admin_proposal": { + "$ref": "#/definitions/AdminProposal" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines the struct for parameter change request.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "description": "*key** is a name of parameter. Unique for subspace.", + "type": "string" + }, + "subspace": { + "description": "*subspace** is a key of module to which the parameter to change belongs. Unique for each module.", + "type": "string" + }, + "value": { + "description": "*value** is a new value for given parameter. Non unique.", + "type": "string" + } + } + }, + "ParamChangeProposal": { + "description": "ParamChangeProposal defines the struct for single parameter change proposal.", + "type": "object", + "required": [ + "description", + "param_changes", + "title" + ], + "properties": { + "description": { + "description": "*descriptionr** is a text description of proposal. Non unique.", + "type": "string" + }, + "param_changes": { + "description": "*param_changes** is a vector of params to be changed. Non unique.", + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + }, + "title": { + "description": "*title** is a text title of proposal. Non unique.", + "type": "string" + } + } + }, + "PercentageThreshold": { + "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `yes_votes >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", + "oneOf": [ + { + "description": "The majority of voters must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "majority" + ], + "properties": { + "majority": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "percent" + ], + "properties": { + "percent": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + ] + }, + "ProtobufAny": { + "description": "Type for wrapping any protobuf message", + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "description": "*type_url** describes the type of the serialized message", + "type": "string" + }, + "value": { + "description": "*value** must be a valid serialized protocol buffer of the above specified type", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "SingleChoiceProposal": { + "type": "object", + "required": [ + "allow_revoting", + "description", + "expiration", + "msgs", + "proposer", + "start_height", + "status", + "threshold", + "title", + "total_power", + "votes" + ], + "properties": { + "allow_revoting": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "expiration": { + "description": "The the time at which this proposal will expire and close for additional votes.", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "min_voting_period": { + "description": "The minimum amount of time this proposal must remain open for voting. The proposal may not pass unless this is expired or None.", + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "msgs": { + "description": "The messages that will be executed should this proposal pass.", + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_NeutronMsg" + } + }, + "proposer": { + "description": "The address that created this proposal.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "start_height": { + "description": "The block height at which this proposal was created. Voting power queries should query for voting power at this block height.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "$ref": "#/definitions/Status" + }, + "threshold": { + "description": "The threshold at which this proposal will pass.", + "allOf": [ + { + "$ref": "#/definitions/Threshold" + } + ] + }, + "title": { + "type": "string" + }, + "total_power": { + "description": "The total amount of voting power at the time of this proposal's creation.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "votes": { + "$ref": "#/definitions/Votes" + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Status": { + "oneOf": [ + { + "description": "The proposal is open for voting.", + "type": "string", + "enum": [ + "open" + ] + }, + { + "description": "The proposal has been rejected.", + "type": "string", + "enum": [ + "rejected" + ] + }, + { + "description": "The proposal has been passed but has not been executed.", + "type": "string", + "enum": [ + "passed" + ] + }, + { + "description": "The proposal has been passed and executed.", + "type": "string", + "enum": [ + "executed" + ] + }, + { + "description": "The proposal has failed or expired and has been closed. A proposal deposit refund has been issued if applicable.", + "type": "string", + "enum": [ + "closed" + ] + }, + { + "description": "The proposal's execution failed.", + "type": "string", + "enum": [ + "execution_failed" + ] + } + ] + }, + "Threshold": { + "description": "The ways a proposal may reach its passing / failing threshold.", + "oneOf": [ + { + "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/PercentageThreshold" + }, + "threshold": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "threshold" + ], + "properties": { + "threshold": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "Votes": { + "type": "object", + "required": [ + "abstain", + "no", + "yes" + ], + "properties": { + "abstain": { + "$ref": "#/definitions/Uint128" + }, + "no": { + "$ref": "#/definitions/Uint128" + }, + "yes": { + "$ref": "#/definitions/Uint128" + } + } + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/query_msg.json b/contracts/proposal/cwd-proposal-single/schema/query_msg.json new file mode 100644 index 00000000..35fbfca9 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/query_msg.json @@ -0,0 +1,251 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Gets the governance module's config. Returns `state::Config`.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Gets information about a proposal. Returns `proposals::Proposal`.", + "type": "object", + "required": [ + "proposal" + ], + "properties": { + "proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Lists all the proposals that have been cast in this module. Returns `query::ProposalListResponse`.", + "type": "object", + "required": [ + "list_proposals" + ], + "properties": { + "list_proposals": { + "type": "object", + "properties": { + "limit": { + "description": "The maximum number of proposals to return as part of this query. If no limit is set a max of 30 proposals will be returned.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "start_after": { + "description": "The proposal ID to start listing proposals after. For example, if this is set to 2 proposals with IDs 3 and higher will be returned.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Lists all of the proposals that have been cast in this module in decending order of proposal ID. Returns `query::ProposalListResponse`.", + "type": "object", + "required": [ + "reverse_proposals" + ], + "properties": { + "reverse_proposals": { + "type": "object", + "properties": { + "limit": { + "description": "The maximum number of proposals to return as part of this query. If no limit is set a max of 30 proposals will be returned.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "start_before": { + "description": "The proposal ID to start listing proposals before. For example, if this is set to 6 proposals with IDs 5 and lower will be returned.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns the number of proposals that have been created in this module.", + "type": "object", + "required": [ + "proposal_count" + ], + "properties": { + "proposal_count": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Returns a voters position on a propsal. Returns `query::VoteResponse`.", + "type": "object", + "required": [ + "get_vote" + ], + "properties": { + "get_vote": { + "type": "object", + "required": [ + "proposal_id", + "voter" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "voter": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Lists all of the votes that have been cast on a proposal. Returns `VoteListResponse`.", + "type": "object", + "required": [ + "list_votes" + ], + "properties": { + "list_votes": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "limit": { + "description": "The maximum number of votes to return in response to this query. If no limit is specified a max of 30 are returned.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "proposal_id": { + "description": "The proposal to list the votes of.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_after": { + "description": "The voter to start listing votes after. Ordering is done alphabetically.", + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Gets the current proposal creation policy for this module. Returns `voting::pre_propose::ProposalCreationPolicy`.", + "type": "object", + "required": [ + "proposal_creation_policy" + ], + "properties": { + "proposal_creation_policy": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Lists all of the consumers of proposal hooks for this module.", + "type": "object", + "required": [ + "proposal_hooks" + ], + "properties": { + "proposal_hooks": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Lists all of the consumers of vote hooks for this module. Returns cwd_hooks::HooksResponse.", + "type": "object", + "required": [ + "vote_hooks" + ], + "properties": { + "vote_hooks": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "dao" + ], + "properties": { + "dao": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/proposal/cwd-proposal-single/schema/reverse_proposals_response.json b/contracts/proposal/cwd-proposal-single/schema/reverse_proposals_response.json new file mode 100644 index 00000000..8f6871c3 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/reverse_proposals_response.json @@ -0,0 +1,1495 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ReverseProposalsResponse", + "description": "A list of proposals returned by `ListProposals` and `ReverseProposals`.", + "type": "object", + "required": [ + "proposals" + ], + "properties": { + "proposals": { + "type": "array", + "items": { + "$ref": "#/definitions/ProposalResponse" + } + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AdminProposal": { + "description": "AdminProposal defines the struct for various proposals which Neutron's Admin Module may accept. Currently only parameter change proposals are implemented, new types of admin proposals may be implemented in future.", + "type": "object", + "properties": { + "param_change_proposal": { + "description": "*param_change_proposal** is a parameter change proposal field.", + "anyOf": [ + { + "$ref": "#/definitions/ParamChangeProposal" + }, + { + "type": "null" + } + ] + } + } + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_NeutronMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/NeutronMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "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" + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "GovMsg": { + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "$ref": "#/definitions/VoteOption" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcFee": { + "type": "object", + "required": [ + "ack_fee", + "recv_fee", + "timeout_fee" + ], + "properties": { + "ack_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "recv_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "timeout_fee": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "exisiting channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (eg. after reseting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "KVKey": { + "description": "Describes a KV key for which you want to get value from the storage on remote chain", + "type": "object", + "required": [ + "key", + "path" + ], + "properties": { + "key": { + "description": "*key** is a key you want to read from the storage", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "path": { + "description": "*path** is a path to the storage (storage prefix) where you want to read value by key (usually name of cosmos-sdk module: 'staking', 'bank', etc.)", + "type": "string" + } + } + }, + "NeutronMsg": { + "description": "A number of Custom messages that can call into the Neutron bindings.", + "oneOf": [ + { + "description": "RegisterInterchainAccount registers an interchain account on remote chain.", + "type": "object", + "required": [ + "register_interchain_account" + ], + "properties": { + "register_interchain_account": { + "type": "object", + "required": [ + "connection_id", + "interchain_account_id" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "interchain_account_id": { + "description": "**interchain_account_id** is an identifier of your new interchain account. Can be any string. This identifier allows contracts to have multiple interchain accounts on remote chains.", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitTx starts the process of executing any Cosmos-SDK *msgs* on remote chain.", + "type": "object", + "required": [ + "submit_tx" + ], + "properties": { + "submit_tx": { + "type": "object", + "required": [ + "connection_id", + "fee", + "interchain_account_id", + "memo", + "msgs", + "timeout" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "fee": { + "description": "**fee** is an ibc fee for the transaction.", + "allOf": [ + { + "$ref": "#/definitions/IbcFee" + } + ] + }, + "interchain_account_id": { + "description": "*interchain_account_id** is an identifier of your interchain account from which you want to execute msgs.", + "type": "string" + }, + "memo": { + "description": "*memo** is a memo you want to attach to your interchain transaction.It behaves like a memo in usual Cosmos transaction.", + "type": "string" + }, + "msgs": { + "description": "*msgs** is a list of protobuf encoded Cosmos-SDK messages you want to execute on remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/ProtobufAny" + } + }, + "timeout": { + "description": "*timeout** is a timeout in seconds after which the packet times out.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery registers an interchain query.", + "type": "object", + "required": [ + "register_interchain_query" + ], + "properties": { + "register_interchain_query": { + "type": "object", + "required": [ + "connection_id", + "keys", + "query_type", + "transactions_filter", + "update_period" + ], + "properties": { + "connection_id": { + "description": "*connection_id** is an IBC connection identifier between Neutron and remote chain.", + "type": "string" + }, + "keys": { + "description": "*keys** is the KV-storage keys for which we want to get values from remote chain.", + "type": "array", + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "query_type": { + "description": "*query_type** is a query type identifier ('tx' or 'kv' for now).", + "type": "string" + }, + "transactions_filter": { + "description": "*transactions_filter** is the filter for transaction search ICQ.", + "type": "string" + }, + "update_period": { + "description": "*update_period** is used to say how often the query must be updated.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RegisterInterchainQuery updates an interchain query.", + "type": "object", + "required": [ + "update_interchain_query" + ], + "properties": { + "update_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "new_keys": { + "description": "*new_keys** is the new query keys to retrive.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/KVKey" + } + }, + "new_transactions_filter": { + "description": "*new_transactions_filter** is a new transactions filter of the query.", + "type": [ + "string", + "null" + ] + }, + "new_update_period": { + "description": "*new_update_period** is a new update period of the query.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "query_id": { + "description": "*query_id** is the ID of the query we want to update.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "RemoveInterchainQuery removes as interchain query.", + "type": "object", + "required": [ + "remove_interchain_query" + ], + "properties": { + "remove_interchain_query": { + "type": "object", + "required": [ + "query_id" + ], + "properties": { + "query_id": { + "description": "*query_id** is ID of the query we want to remove.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "IbcTransfer sends a fungible token packet over IBC.", + "type": "object", + "required": [ + "ibc_transfer" + ], + "properties": { + "ibc_transfer": { + "type": "object", + "required": [ + "fee", + "receiver", + "sender", + "source_channel", + "source_port", + "timeout_height", + "timeout_timestamp", + "token" + ], + "properties": { + "fee": { + "$ref": "#/definitions/IbcFee" + }, + "receiver": { + "type": "string" + }, + "sender": { + "type": "string" + }, + "source_channel": { + "type": "string" + }, + "source_port": { + "type": "string" + }, + "timeout_height": { + "$ref": "#/definitions/RequestPacketTimeoutHeight" + }, + "timeout_timestamp": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token": { + "$ref": "#/definitions/Coin" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "SubmitAdminProposal sends a proposal to neutron's Admin module. This type of messages can be only executed by Neutron DAO.", + "type": "object", + "required": [ + "submit_admin_proposal" + ], + "properties": { + "submit_admin_proposal": { + "type": "object", + "required": [ + "admin_proposal" + ], + "properties": { + "admin_proposal": { + "$ref": "#/definitions/AdminProposal" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "ParamChange": { + "description": "ParamChange defines the struct for parameter change request.", + "type": "object", + "required": [ + "key", + "subspace", + "value" + ], + "properties": { + "key": { + "description": "*key** is a name of parameter. Unique for subspace.", + "type": "string" + }, + "subspace": { + "description": "*subspace** is a key of module to which the parameter to change belongs. Unique for each module.", + "type": "string" + }, + "value": { + "description": "*value** is a new value for given parameter. Non unique.", + "type": "string" + } + } + }, + "ParamChangeProposal": { + "description": "ParamChangeProposal defines the struct for single parameter change proposal.", + "type": "object", + "required": [ + "description", + "param_changes", + "title" + ], + "properties": { + "description": { + "description": "*descriptionr** is a text description of proposal. Non unique.", + "type": "string" + }, + "param_changes": { + "description": "*param_changes** is a vector of params to be changed. Non unique.", + "type": "array", + "items": { + "$ref": "#/definitions/ParamChange" + } + }, + "title": { + "description": "*title** is a text title of proposal. Non unique.", + "type": "string" + } + } + }, + "PercentageThreshold": { + "description": "A percentage of voting power that must vote yes for a proposal to pass. An example of why this is needed:\n\nIf a user specifies a 60% passing threshold, and there are 10 voters they likely expect that proposal to pass when there are 6 yes votes. This implies that the condition for passing should be `yes_votes >= total_votes * threshold`.\n\nWith this in mind, how should a user specify that they would like proposals to pass if the majority of voters choose yes? Selecting a 50% passing threshold with those rules doesn't properly cover that case as 5 voters voting yes out of 10 would pass the proposal. Selecting 50.0001% or or some variation of that also does not work as a very small yes vote which technically makes the majority yes may not reach that threshold.\n\nTo handle these cases we provide both a majority and percent option for all percentages. If majority is selected passing will be determined by `yes > total_votes * 0.5`. If percent is selected passing is determined by `yes >= total_votes * percent`.\n\nIn both of these cases a proposal with only abstain votes must fail. This requires a special case passing logic.", + "oneOf": [ + { + "description": "The majority of voters must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "majority" + ], + "properties": { + "majority": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "A percentage of voting power >= percent must vote yes for the proposal to pass.", + "type": "object", + "required": [ + "percent" + ], + "properties": { + "percent": { + "$ref": "#/definitions/Decimal" + } + }, + "additionalProperties": false + } + ] + }, + "ProposalResponse": { + "description": "Information about a proposal returned by proposal queries.", + "type": "object", + "required": [ + "id", + "proposal" + ], + "properties": { + "id": { + "description": "The ID of the proposal being returned.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal": { + "$ref": "#/definitions/SingleChoiceProposal" + } + } + }, + "ProtobufAny": { + "description": "Type for wrapping any protobuf message", + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "description": "*type_url** describes the type of the serialized message", + "type": "string" + }, + "value": { + "description": "*value** must be a valid serialized protocol buffer of the above specified type", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + }, + "RequestPacketTimeoutHeight": { + "type": "object", + "properties": { + "revision_height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "revision_number": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + }, + "SingleChoiceProposal": { + "type": "object", + "required": [ + "allow_revoting", + "description", + "expiration", + "msgs", + "proposer", + "start_height", + "status", + "threshold", + "title", + "total_power", + "votes" + ], + "properties": { + "allow_revoting": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "expiration": { + "description": "The the time at which this proposal will expire and close for additional votes.", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "min_voting_period": { + "description": "The minimum amount of time this proposal must remain open for voting. The proposal may not pass unless this is expired or None.", + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "msgs": { + "description": "The messages that will be executed should this proposal pass.", + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_NeutronMsg" + } + }, + "proposer": { + "description": "The address that created this proposal.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "start_height": { + "description": "The block height at which this proposal was created. Voting power queries should query for voting power at this block height.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "$ref": "#/definitions/Status" + }, + "threshold": { + "description": "The threshold at which this proposal will pass.", + "allOf": [ + { + "$ref": "#/definitions/Threshold" + } + ] + }, + "title": { + "type": "string" + }, + "total_power": { + "description": "The total amount of voting power at the time of this proposal's creation.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "votes": { + "$ref": "#/definitions/Votes" + } + } + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Status": { + "oneOf": [ + { + "description": "The proposal is open for voting.", + "type": "string", + "enum": [ + "open" + ] + }, + { + "description": "The proposal has been rejected.", + "type": "string", + "enum": [ + "rejected" + ] + }, + { + "description": "The proposal has been passed but has not been executed.", + "type": "string", + "enum": [ + "passed" + ] + }, + { + "description": "The proposal has been passed and executed.", + "type": "string", + "enum": [ + "executed" + ] + }, + { + "description": "The proposal has failed or expired and has been closed. A proposal deposit refund has been issued if applicable.", + "type": "string", + "enum": [ + "closed" + ] + }, + { + "description": "The proposal's execution failed.", + "type": "string", + "enum": [ + "execution_failed" + ] + } + ] + }, + "Threshold": { + "description": "The ways a proposal may reach its passing / failing threshold.", + "oneOf": [ + { + "description": "Declares a percentage of the total weight that must cast Yes votes in order for a proposal to pass. See `ThresholdResponse::AbsolutePercentage` in the cw3 spec for details.", + "type": "object", + "required": [ + "absolute_percentage" + ], + "properties": { + "absolute_percentage": { + "type": "object", + "required": [ + "percentage" + ], + "properties": { + "percentage": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Declares a `quorum` of the total votes that must participate in the election in order for the vote to be considered at all. See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for details.", + "type": "object", + "required": [ + "threshold_quorum" + ], + "properties": { + "threshold_quorum": { + "type": "object", + "required": [ + "quorum", + "threshold" + ], + "properties": { + "quorum": { + "$ref": "#/definitions/PercentageThreshold" + }, + "threshold": { + "$ref": "#/definitions/PercentageThreshold" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "An absolute number of votes needed for something to cross the threshold. Useful for multisig style voting.", + "type": "object", + "required": [ + "absolute_count" + ], + "properties": { + "absolute_count": { + "type": "object", + "required": [ + "threshold" + ], + "properties": { + "threshold": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "Votes": { + "type": "object", + "required": [ + "abstain", + "no", + "yes" + ], + "properties": { + "abstain": { + "$ref": "#/definitions/Uint128" + }, + "no": { + "$ref": "#/definitions/Uint128" + }, + "yes": { + "$ref": "#/definitions/Uint128" + } + } + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.16.0-alpha1/x/wasm/internal/types/tx.proto#L47-L61). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readbale label for the contract", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/vote_hooks_response.json b/contracts/proposal/cwd-proposal-single/schema/vote_hooks_response.json new file mode 100644 index 00000000..b185fd8a --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/vote_hooks_response.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VoteHooksResponse", + "type": "object", + "required": [ + "hooks" + ], + "properties": { + "hooks": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/schema/vote_response.json b/contracts/proposal/cwd-proposal-single/schema/vote_response.json new file mode 100644 index 00000000..20e47d10 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/schema/vote_response.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VoteResponse", + "description": "Information about a vote.", + "type": "object", + "properties": { + "vote": { + "description": "None if no such vote, Some otherwise.", + "anyOf": [ + { + "$ref": "#/definitions/VoteInfo" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Vote": { + "oneOf": [ + { + "description": "Marks support for the proposal.", + "type": "string", + "enum": [ + "yes" + ] + }, + { + "description": "Marks opposition to the proposal.", + "type": "string", + "enum": [ + "no" + ] + }, + { + "description": "Marks participation but does not count towards the ratio of support / opposed.", + "type": "string", + "enum": [ + "abstain" + ] + } + ] + }, + "VoteInfo": { + "description": "Information about a vote that was cast.", + "type": "object", + "required": [ + "power", + "vote", + "voter" + ], + "properties": { + "power": { + "description": "The voting power behind the vote.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "vote": { + "description": "Position on the vote.", + "allOf": [ + { + "$ref": "#/definitions/Vote" + } + ] + }, + "voter": { + "description": "The address that voted.", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + } + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/src/contract.rs b/contracts/proposal/cwd-proposal-single/src/contract.rs new file mode 100644 index 00000000..f7a67e06 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/src/contract.rs @@ -0,0 +1,842 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Reply, Response, + StdResult, Storage, SubMsg, WasmMsg, +}; +use cw2::set_contract_version; +use cw_storage_plus::Bound; +use cw_utils::{parse_reply_instantiate_data, Duration}; +use cwd_hooks::Hooks; +use cwd_pre_propose_single::contract::ExecuteMsg as PreProposeMsg; +use cwd_proposal_hooks::{new_proposal_hooks, proposal_status_changed_hooks}; +use cwd_vote_hooks::new_vote_hooks; +use cwd_voting::pre_propose::{PreProposeInfo, ProposalCreationPolicy}; +use cwd_voting::proposal::{DEFAULT_LIMIT, MAX_PROPOSAL_SIZE}; +use cwd_voting::reply::{ + failed_pre_propose_module_hook_id, mask_proposal_execution_proposal_id, TaggedReplyId, +}; +use cwd_voting::status::Status; +use cwd_voting::threshold::Threshold; +use cwd_voting::voting::{get_total_power, get_voting_power, validate_voting_period, Vote, Votes}; +use neutron_bindings::bindings::msg::NeutronMsg; + +use crate::msg::MigrateMsg; +use crate::proposal::SingleChoiceProposal; +use crate::state::{Config, CREATION_POLICY}; + +use crate::{ + error::ContractError, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + proposal::advance_proposal_id, + query::ProposalListResponse, + query::{ProposalResponse, VoteInfo, VoteListResponse, VoteResponse}, + state::{Ballot, BALLOTS, CONFIG, PROPOSALS, PROPOSAL_COUNT, PROPOSAL_HOOKS, VOTE_HOOKS}, +}; + +pub(crate) const CONTRACT_NAME: &str = "crates.io:cwd-proposal-single"; +pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + msg.threshold.validate()?; + + let dao = info.sender; + + let (min_voting_period, max_voting_period) = + validate_voting_period(msg.min_voting_period, msg.max_voting_period)?; + + let (initial_policy, pre_propose_messages) = msg + .pre_propose_info + .into_initial_policy_and_messages(dao.clone())?; + + let config = Config { + threshold: msg.threshold, + max_voting_period, + min_voting_period, + dao: dao.clone(), + allow_revoting: msg.allow_revoting, + close_proposal_on_execution_failure: msg.close_proposal_on_execution_failure, + }; + + // Initialize proposal count to zero so that queries return zero + // instead of None. + PROPOSAL_COUNT.save(deps.storage, &0)?; + CONFIG.save(deps.storage, &config)?; + CREATION_POLICY.save(deps.storage, &initial_policy)?; + + Ok(Response::default() + .add_submessages(pre_propose_messages) + .add_attribute("action", "instantiate") + .add_attribute("dao", dao)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Propose { + title, + description, + msgs, + proposer, + } => execute_propose(deps, env, info.sender, title, description, msgs, proposer), + ExecuteMsg::Vote { proposal_id, vote } => execute_vote(deps, env, info, proposal_id, vote), + ExecuteMsg::Execute { proposal_id } => execute_execute(deps, env, info, proposal_id), + ExecuteMsg::Close { proposal_id } => execute_close(deps, env, info, proposal_id), + ExecuteMsg::UpdateConfig { + threshold, + max_voting_period, + min_voting_period, + allow_revoting, + dao, + close_proposal_on_execution_failure, + } => execute_update_config( + deps, + info, + threshold, + max_voting_period, + min_voting_period, + allow_revoting, + dao, + close_proposal_on_execution_failure, + ), + ExecuteMsg::UpdatePreProposeInfo { info: new_info } => { + execute_update_proposal_creation_policy(deps, info, new_info) + } + ExecuteMsg::AddProposalHook { address } => { + execute_add_proposal_hook(deps, env, info, address) + } + ExecuteMsg::RemoveProposalHook { address } => { + execute_remove_proposal_hook(deps, env, info, address) + } + ExecuteMsg::AddVoteHook { address } => execute_add_vote_hook(deps, env, info, address), + ExecuteMsg::RemoveVoteHook { address } => { + execute_remove_vote_hook(deps, env, info, address) + } + } +} + +pub fn execute_propose( + deps: DepsMut, + env: Env, + sender: Addr, + title: String, + description: String, + msgs: Vec>, + proposer: Option, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; + + // Check that the sender is permitted to create proposals. + if !proposal_creation_policy.is_permitted(&sender) { + return Err(ContractError::Unauthorized {}); + } + + // Determine the appropriate proposer. If this is coming from our + // pre-propose module, it must be specified. Otherwise, the + // proposer should not be specified. + let proposer = match (proposer, &proposal_creation_policy) { + (None, ProposalCreationPolicy::Anyone {}) => sender.clone(), + // `is_permitted` above checks that an allowed module is + // actually sending the propose message. + (Some(proposer), ProposalCreationPolicy::Module { .. }) => { + deps.api.addr_validate(&proposer)? + } + _ => return Err(ContractError::InvalidProposer {}), + }; + + let expiration = config.max_voting_period.after(&env.block); + + let total_power = get_total_power(deps.as_ref(), config.dao, Some(env.block.height))?; + + let proposal = { + // Limit mutability to this block. + let mut proposal = SingleChoiceProposal { + title, + description, + proposer: proposer.clone(), + start_height: env.block.height, + min_voting_period: config.min_voting_period.map(|min| min.after(&env.block)), + expiration, + threshold: config.threshold, + total_power, + msgs, + status: Status::Open, + votes: Votes::zero(), + allow_revoting: config.allow_revoting, + }; + // Update the proposal's status. Addresses case where proposal + // expires on the same block as it is created. + proposal.update_status(&env.block); + proposal + }; + let id = advance_proposal_id(deps.storage)?; + + // TODO: discuss and probably adapt to Neutron reality. + // + // Limit the size of proposals. + // + // The Juno mainnet has a larger limit for data that can be + // uploaded as part of an execute message than it does for data + // that can be queried as part of a query. This means that without + // this check it is possible to create a proposal that can not be + // queried. + // + // The size selected was determined by uploading versions of this + // contract to the Juno mainnet until queries worked within a + // reasonable margin of error. + // + // `to_vec` is the method used by cosmwasm to convert a struct + // into it's byte representation in storage. + let proposal_size = cosmwasm_std::to_vec(&proposal)?.len() as u64; + if proposal_size > MAX_PROPOSAL_SIZE { + return Err(ContractError::ProposalTooLarge { + size: proposal_size, + max: MAX_PROPOSAL_SIZE, + }); + } + + PROPOSALS.save(deps.storage, id, &proposal)?; + + let hooks = new_proposal_hooks(PROPOSAL_HOOKS, deps.storage, id, proposer.as_str())?; + + // Add prepropose / deposit module hook which will save deposit info. This + // needs to be called after execute_propose because we don't know the + // proposal ID beforehand. + let hooks = match proposal_creation_policy { + ProposalCreationPolicy::Anyone {} => hooks, + ProposalCreationPolicy::Module { addr } => { + let msg = to_binary(&PreProposeMsg::ProposalCreatedHook { + proposal_id: id, + proposer: proposer.into_string(), + })?; + let mut hooks = hooks; + hooks.push(SubMsg::reply_on_error( + WasmMsg::Execute { + contract_addr: addr.into_string(), + msg, + funds: vec![], + }, + failed_pre_propose_module_hook_id(), + )); + hooks + } + }; + + Ok(Response::default() + .add_submessages(hooks) + .add_attribute("action", "propose") + .add_attribute("sender", sender) + .add_attribute("proposal_id", id.to_string()) + .add_attribute("status", proposal.status.to_string())) +} + +pub fn execute_execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + proposal_id: u64, +) -> Result { + let config = CONFIG.load(deps.storage)?; + + let mut prop = PROPOSALS + .may_load(deps.storage, proposal_id)? + .ok_or(ContractError::NoSuchProposal { id: proposal_id })?; + + // Check here that the proposal is passed. Allow it to be executed + // even if it is expired so long as it passed during its voting + // period. + let old_status = prop.status; + prop.update_status(&env.block); + if prop.status != Status::Passed { + return Err(ContractError::NotPassed {}); + } + + prop.status = Status::Executed; + + PROPOSALS.save(deps.storage, proposal_id, &prop)?; + + let response = { + if !prop.msgs.is_empty() { + let execute_message = WasmMsg::Execute { + contract_addr: config.dao.to_string(), + msg: to_binary(&cwd_core::msg::ExecuteMsg::ExecuteProposalHook { + msgs: prop.msgs, + })?, + funds: vec![], + }; + match config.close_proposal_on_execution_failure { + true => { + let masked_proposal_id = mask_proposal_execution_proposal_id(proposal_id); + Response::default() + .add_submessage(SubMsg::reply_on_error(execute_message, masked_proposal_id)) + } + false => Response::default().add_message(execute_message), + } + } else { + Response::default() + } + }; + + let hooks = proposal_status_changed_hooks( + PROPOSAL_HOOKS, + deps.storage, + proposal_id, + old_status.to_string(), + prop.status.to_string(), + )?; + + // Add prepropose / deposit module hook which will handle deposit refunds. + let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; + let hooks = match proposal_creation_policy { + ProposalCreationPolicy::Anyone {} => hooks, + ProposalCreationPolicy::Module { addr } => { + let msg = to_binary(&PreProposeMsg::ProposalCompletedHook { + proposal_id, + new_status: prop.status, + })?; + let mut hooks = hooks; + hooks.push(SubMsg::reply_on_error( + WasmMsg::Execute { + contract_addr: addr.into_string(), + msg, + funds: vec![], + }, + failed_pre_propose_module_hook_id(), + )); + hooks + } + }; + + Ok(response + .add_submessages(hooks) + .add_attribute("action", "execute") + .add_attribute("sender", info.sender) + .add_attribute("proposal_id", proposal_id.to_string()) + .add_attribute("dao", config.dao)) +} + +pub fn execute_vote( + deps: DepsMut, + env: Env, + info: MessageInfo, + proposal_id: u64, + vote: Vote, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let mut prop = PROPOSALS + .may_load(deps.storage, proposal_id)? + .ok_or(ContractError::NoSuchProposal { id: proposal_id })?; + if prop.current_status(&env.block) != Status::Open { + return Err(ContractError::NotOpen { id: proposal_id }); + } + + let vote_power = get_voting_power( + deps.as_ref(), + info.sender.clone(), + config.dao, + Some(prop.start_height), + )?; + if vote_power.is_zero() { + return Err(ContractError::NotRegistered {}); + } + + BALLOTS.update( + deps.storage, + (proposal_id, info.sender.clone()), + |bal| match bal { + Some(current_ballot) => { + if prop.allow_revoting { + if current_ballot.vote == vote { + // Don't allow casting the same vote more than + // once. This seems liable to be confusing + // behavior. + Err(ContractError::AlreadyCast {}) + } else { + // Remove the old vote if this is a re-vote. + prop.votes + .remove_vote(current_ballot.vote, current_ballot.power); + Ok(Ballot { + power: vote_power, + vote, + }) + } + } else { + Err(ContractError::AlreadyVoted {}) + } + } + None => Ok(Ballot { + power: vote_power, + vote, + }), + }, + )?; + + let old_status = prop.status; + + prop.votes.add_vote(vote, vote_power); + prop.update_status(&env.block); + + PROPOSALS.save(deps.storage, proposal_id, &prop)?; + + let new_status = prop.status; + let change_hooks = proposal_status_changed_hooks( + PROPOSAL_HOOKS, + deps.storage, + proposal_id, + old_status.to_string(), + new_status.to_string(), + )?; + + let vote_hooks = new_vote_hooks( + VOTE_HOOKS, + deps.storage, + proposal_id, + info.sender.to_string(), + vote.to_string(), + )?; + + Ok(Response::default() + .add_submessages(change_hooks) + .add_submessages(vote_hooks) + .add_attribute("action", "vote") + .add_attribute("sender", info.sender) + .add_attribute("proposal_id", proposal_id.to_string()) + .add_attribute("position", vote.to_string()) + .add_attribute("status", prop.status.to_string())) +} + +pub fn execute_close( + deps: DepsMut, + env: Env, + info: MessageInfo, + proposal_id: u64, +) -> Result { + let mut prop = PROPOSALS.load(deps.storage, proposal_id)?; + + // Update status to ensure that proposals which were open and have + // expired are moved to "rejected." + prop.update_status(&env.block); + if prop.status != Status::Rejected { + return Err(ContractError::WrongCloseStatus {}); + } + + let old_status = prop.status; + + prop.status = Status::Closed; + PROPOSALS.save(deps.storage, proposal_id, &prop)?; + + let hooks = proposal_status_changed_hooks( + PROPOSAL_HOOKS, + deps.storage, + proposal_id, + old_status.to_string(), + prop.status.to_string(), + )?; + + // Add prepropose / deposit module hook which will handle deposit refunds. + let proposal_creation_policy = CREATION_POLICY.load(deps.storage)?; + let hooks = match proposal_creation_policy { + ProposalCreationPolicy::Anyone {} => hooks, + ProposalCreationPolicy::Module { addr } => { + let msg = to_binary(&PreProposeMsg::ProposalCompletedHook { + proposal_id, + new_status: prop.status, + })?; + let mut hooks = hooks; + hooks.push(SubMsg::reply_on_error( + WasmMsg::Execute { + contract_addr: addr.into_string(), + msg, + funds: vec![], + }, + failed_pre_propose_module_hook_id(), + )); + hooks + } + }; + + Ok(Response::default() + .add_submessages(hooks) + .add_attribute("action", "close") + .add_attribute("sender", info.sender) + .add_attribute("proposal_id", proposal_id.to_string())) +} + +#[allow(clippy::too_many_arguments)] +pub fn execute_update_config( + deps: DepsMut, + info: MessageInfo, + threshold: Threshold, + max_voting_period: Duration, + min_voting_period: Option, + allow_revoting: bool, + dao: String, + close_proposal_on_execution_failure: bool, +) -> Result { + let config = CONFIG.load(deps.storage)?; + + // Only the DAO may call this method. + if info.sender != config.dao { + return Err(ContractError::Unauthorized {}); + } + + threshold.validate()?; + let dao = deps.api.addr_validate(&dao)?; + + let (min_voting_period, max_voting_period) = + validate_voting_period(min_voting_period, max_voting_period)?; + + CONFIG.save( + deps.storage, + &Config { + threshold, + max_voting_period, + min_voting_period, + allow_revoting, + dao, + close_proposal_on_execution_failure, + }, + )?; + + Ok(Response::default() + .add_attribute("action", "update_config") + .add_attribute("sender", info.sender)) +} + +pub fn execute_update_proposal_creation_policy( + deps: DepsMut, + info: MessageInfo, + new_info: PreProposeInfo, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if config.dao != info.sender { + return Err(ContractError::Unauthorized {}); + } + + let (initial_policy, messages) = new_info.into_initial_policy_and_messages(config.dao)?; + CREATION_POLICY.save(deps.storage, &initial_policy)?; + + Ok(Response::default() + .add_submessages(messages) + .add_attribute("action", "update_proposal_creation_policy") + .add_attribute("sender", info.sender) + .add_attribute("new_policy", format!("{:?}", initial_policy))) +} + +pub fn add_hook( + hooks: Hooks, + storage: &mut dyn Storage, + validated_address: Addr, +) -> Result<(), ContractError> { + hooks + .add_hook(storage, validated_address) + .map_err(ContractError::HookError)?; + Ok(()) +} + +pub fn remove_hook( + hooks: Hooks, + storage: &mut dyn Storage, + validate_address: Addr, +) -> Result<(), ContractError> { + hooks + .remove_hook(storage, validate_address) + .map_err(ContractError::HookError)?; + Ok(()) +} + +pub fn execute_add_proposal_hook( + deps: DepsMut, + _env: Env, + info: MessageInfo, + address: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if config.dao != info.sender { + // Only DAO can add hooks + return Err(ContractError::Unauthorized {}); + } + + let validated_address = deps.api.addr_validate(&address)?; + + add_hook(PROPOSAL_HOOKS, deps.storage, validated_address)?; + + Ok(Response::default() + .add_attribute("action", "add_proposal_hook") + .add_attribute("address", address)) +} + +pub fn execute_remove_proposal_hook( + deps: DepsMut, + _env: Env, + info: MessageInfo, + address: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if config.dao != info.sender { + // Only DAO can remove hooks + return Err(ContractError::Unauthorized {}); + } + + let validated_address = deps.api.addr_validate(&address)?; + + remove_hook(PROPOSAL_HOOKS, deps.storage, validated_address)?; + + Ok(Response::default() + .add_attribute("action", "remove_proposal_hook") + .add_attribute("address", address)) +} + +pub fn execute_add_vote_hook( + deps: DepsMut, + _env: Env, + info: MessageInfo, + address: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if config.dao != info.sender { + // Only DAO can add hooks + return Err(ContractError::Unauthorized {}); + } + + let validated_address = deps.api.addr_validate(&address)?; + + add_hook(VOTE_HOOKS, deps.storage, validated_address)?; + + Ok(Response::default() + .add_attribute("action", "add_vote_hook") + .add_attribute("address", address)) +} + +pub fn execute_remove_vote_hook( + deps: DepsMut, + _env: Env, + info: MessageInfo, + address: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if config.dao != info.sender { + // Only DAO can remove hooks + return Err(ContractError::Unauthorized {}); + } + + let validated_address = deps.api.addr_validate(&address)?; + + remove_hook(VOTE_HOOKS, deps.storage, validated_address)?; + + Ok(Response::default() + .add_attribute("action", "remove_vote_hook") + .add_attribute("address", address)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => query_config(deps), + QueryMsg::Dao {} => query_dao(deps), + QueryMsg::Proposal { proposal_id } => query_proposal(deps, env, proposal_id), + QueryMsg::ListProposals { start_after, limit } => { + query_list_proposals(deps, env, start_after, limit) + } + QueryMsg::ProposalCount {} => query_proposal_count(deps), + QueryMsg::GetVote { proposal_id, voter } => query_vote(deps, proposal_id, voter), + QueryMsg::ListVotes { + proposal_id, + start_after, + limit, + } => query_list_votes(deps, proposal_id, start_after, limit), + QueryMsg::Info {} => query_info(deps), + QueryMsg::ReverseProposals { + start_before, + limit, + } => query_reverse_proposals(deps, env, start_before, limit), + QueryMsg::ProposalCreationPolicy {} => query_creation_policy(deps), + QueryMsg::ProposalHooks {} => to_binary(&PROPOSAL_HOOKS.query_hooks(deps)?), + QueryMsg::VoteHooks {} => to_binary(&VOTE_HOOKS.query_hooks(deps)?), + } +} + +pub fn query_config(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + to_binary(&config) +} + +pub fn query_dao(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + to_binary(&config.dao) +} + +pub fn query_proposal(deps: Deps, env: Env, id: u64) -> StdResult { + let proposal = PROPOSALS.load(deps.storage, id)?; + to_binary(&proposal.into_response(&env.block, id)) +} + +pub fn query_creation_policy(deps: Deps) -> StdResult { + let policy = CREATION_POLICY.load(deps.storage)?; + to_binary(&policy) +} + +pub fn query_list_proposals( + deps: Deps, + env: Env, + start_after: Option, + limit: Option, +) -> StdResult { + let min = start_after.map(Bound::exclusive); + let limit = limit.unwrap_or(DEFAULT_LIMIT); + let props: Vec = PROPOSALS + .range(deps.storage, min, None, cosmwasm_std::Order::Ascending) + .take(limit as usize) + .collect::, _>>()? + .into_iter() + .map(|(id, proposal)| proposal.into_response(&env.block, id)) + .collect(); + + to_binary(&ProposalListResponse { proposals: props }) +} + +pub fn query_reverse_proposals( + deps: Deps, + env: Env, + start_before: Option, + limit: Option, +) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT); + let max = start_before.map(Bound::exclusive); + let props: Vec = PROPOSALS + .range(deps.storage, None, max, cosmwasm_std::Order::Descending) + .take(limit as usize) + .collect::, _>>()? + .into_iter() + .map(|(id, proposal)| proposal.into_response(&env.block, id)) + .collect(); + + to_binary(&ProposalListResponse { proposals: props }) +} + +pub fn query_proposal_count(deps: Deps) -> StdResult { + let proposal_count = PROPOSAL_COUNT.load(deps.storage)?; + to_binary(&proposal_count) +} + +pub fn query_vote(deps: Deps, proposal_id: u64, voter: String) -> StdResult { + let voter = deps.api.addr_validate(&voter)?; + let ballot = BALLOTS.may_load(deps.storage, (proposal_id, voter.clone()))?; + let vote = ballot.map(|ballot| VoteInfo { + voter, + vote: ballot.vote, + power: ballot.power, + }); + to_binary(&VoteResponse { vote }) +} + +pub fn query_list_votes( + deps: Deps, + proposal_id: u64, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT); + let start_after = start_after + .map(|addr| deps.api.addr_validate(&addr)) + .transpose()?; + let min = start_after.map(Bound::::exclusive); + + let votes = BALLOTS + .prefix(proposal_id) + .range(deps.storage, min, None, cosmwasm_std::Order::Ascending) + .take(limit as usize) + .map(|item| { + let (voter, ballot) = item?; + Ok(VoteInfo { + voter, + vote: ballot.vote, + power: ballot.power, + }) + }) + .collect::>>()?; + + to_binary(&VoteListResponse { votes }) +} + +pub fn query_info(deps: Deps) -> StdResult { + let info = cw2::get_contract_version(deps.storage)?; + to_binary(&cwd_interface::voting::InfoResponse { info }) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { + let repl = TaggedReplyId::new(msg.id)?; + match repl { + TaggedReplyId::FailedProposalExecution(proposal_id) => { + PROPOSALS.update(deps.storage, proposal_id, |prop| match prop { + Some(mut prop) => { + prop.status = Status::ExecutionFailed; + + Ok(prop) + } + None => Err(ContractError::NoSuchProposal { id: proposal_id }), + })?; + + Ok(Response::new().add_attribute("proposal_execution_failed", proposal_id.to_string())) + } + TaggedReplyId::FailedProposalHook(idx) => { + let addr = PROPOSAL_HOOKS.remove_hook_by_index(deps.storage, idx)?; + Ok(Response::new().add_attribute("removed_proposal_hook", format!("{addr}:{idx}"))) + } + TaggedReplyId::FailedVoteHook(idx) => { + let addr = VOTE_HOOKS.remove_hook_by_index(deps.storage, idx)?; + Ok(Response::new().add_attribute("removed_vote_hook", format!("{addr}:{idx}"))) + } + TaggedReplyId::PreProposeModuleInstantiation => { + let res = parse_reply_instantiate_data(msg)?; + let module = deps.api.addr_validate(&res.contract_address)?; + CREATION_POLICY.save( + deps.storage, + &ProposalCreationPolicy::Module { addr: module }, + )?; + + Ok(Response::new().add_attribute("update_pre_propose_module", res.contract_address)) + } + TaggedReplyId::FailedPreProposeModuleHook => { + let addr = match CREATION_POLICY.load(deps.storage)? { + ProposalCreationPolicy::Anyone {} => { + // Something is off if we're getting this + // reply and we don't have a pre-propose + // module installed. This should be + // unreachable. + return Err(ContractError::InvalidReplyID { + id: failed_pre_propose_module_hook_id(), + }); + } + ProposalCreationPolicy::Module { addr } => { + // If we are here, our pre-propose module has + // errored while receiving a proposal + // hook. Rest in peace pre-propose module. + CREATION_POLICY.save(deps.storage, &ProposalCreationPolicy::Anyone {})?; + addr + } + }; + Ok(Response::new().add_attribute("failed_prepropose_hook", format!("{addr}"))) + } + } +} diff --git a/contracts/proposal/cwd-proposal-single/src/error.rs b/contracts/proposal/cwd-proposal-single/src/error.rs new file mode 100644 index 00000000..61a8045f --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/src/error.rs @@ -0,0 +1,63 @@ +use std::u64; + +use cosmwasm_std::StdError; +use cw_utils::ParseReplyError; +use cwd_hooks::HookError; +use cwd_voting::reply::error::TagError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error(transparent)] + Std(#[from] StdError), + + #[error(transparent)] + ParseReplyError(#[from] ParseReplyError), + + #[error(transparent)] + HookError(#[from] HookError), + + #[error("unauthorized")] + Unauthorized {}, + + #[error(transparent)] + ThresholdError(#[from] cwd_voting::threshold::ThresholdError), + + #[error(transparent)] + VotingError(#[from] cwd_voting::error::VotingError), + + #[error("no such proposal ({id})")] + NoSuchProposal { id: u64 }, + + #[error("proposal is ({size}) bytes, must be <= ({max}) bytes")] + ProposalTooLarge { size: u64, max: u64 }, + + #[error("proposal is not open ({id})")] + NotOpen { id: u64 }, + + #[error("not registered to vote (no voting power) at time of proposal creation")] + NotRegistered {}, + + #[error("already voted. this proposal does not support revoting")] + AlreadyVoted {}, + + #[error("already cast a vote with that option. change your vote to revote")] + AlreadyCast {}, + + #[error("proposal is not in 'passed' state")] + NotPassed {}, + + #[error("only rejected proposals may be closed")] + WrongCloseStatus {}, + + #[error( + "pre-propose modules must specify a proposer. lacking one, no proposer should be specified" + )] + InvalidProposer {}, + + #[error(transparent)] + Tag(#[from] TagError), + + #[error("received a reply failure with an invalid ID: ({id})")] + InvalidReplyID { id: u64 }, +} diff --git a/contracts/proposal/cwd-proposal-single/src/lib.rs b/contracts/proposal/cwd-proposal-single/src/lib.rs new file mode 100644 index 00000000..37f16781 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/src/lib.rs @@ -0,0 +1,53 @@ +//! # cw-proposal-single +//! +//! A proposal module for a DAO DAO DAO which supports simple "yes", "no", +//! "abstain" voting. Proposals may have associated messages which will be +//! executed by the core module upon the proposal being passed and +//! executed. +//! +//! For more information about how these modules fit together see +//! [this](https://!github.com/DA0-DA0/dao-contracts/wiki/DAO-DAO-v1-Contracts-Design) +//! wiki page. +//! +//! For information about how this module counts votes and handles passing +//! thresholds see +//! [this](https://!github.com/DA0-DA0/dao-contracts/wiki/A-brief-overview-of-DAO-DAO-voting#proposal-status) +//! wiki page. +//! +//! ## Proposal deposits +//! +//! This contract may optionally be configured to require a deposit for +//! proposal creation. Currently, any cw20 token may be used. +//! +//! As a convienence one may specify that the module should use the same +//! token as the DAO's voting module using the `VotingModuleToken` variant +//! when specifying information about the deposit. For this to work the +//! voting module associated with the DAO must support the `TokenContract` +//! query. This query may be derived via the `#[token_query]` +//! [macro](../../packages/cw-core-macros/src/lib.rs). +//! +//! ## Hooks +//! +//! This module supports hooks for voting and proposal status changes. One +//! may register a contract to receive these hooks with the `AddVoteHook` +//! and `AddProposalHook` methods. Upon registration the contract will +//! receive messages whenever a vote is cast and a proposal's status +//! changes (for example, when the proposal passes). +//! +//! The format for these hook messages can be located in the +//! `proposal-hooks` and `vote-hooks` packages located in +//! `packages/proposal-hooks` and `packages/vote-hooks` respectively. +//! +//! To stop an invalid hook receiver from locking the proposal module +//! receivers will be removed from the hook list if they error when +//! handling a hook. + +pub mod contract; +mod error; +pub mod msg; +pub mod proposal; +pub mod query; + +pub mod state; + +pub use crate::error::ContractError; diff --git a/contracts/proposal/cwd-proposal-single/src/msg.rs b/contracts/proposal/cwd-proposal-single/src/msg.rs new file mode 100644 index 00000000..f3f730c3 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/src/msg.rs @@ -0,0 +1,197 @@ +use cosmwasm_std::CosmosMsg; +use cw_utils::Duration; +use neutron_bindings::bindings::msg::NeutronMsg; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cwd_macros::{info_query, proposal_module_query}; +use cwd_voting::{pre_propose::PreProposeInfo, threshold::Threshold, voting::Vote}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InstantiateMsg { + /// The threshold a proposal must reach to complete. + pub threshold: Threshold, + /// The default maximum amount of time a proposal may be voted on + /// before expiring. + pub max_voting_period: Duration, + /// The minimum amount of time a proposal must be open before + /// passing. A proposal may fail before this amount of time has + /// elapsed, but it will not pass. This can be useful for + /// preventing governance attacks wherein an attacker aquires a + /// large number of tokens and forces a proposal through. + pub min_voting_period: Option, + /// Allows changing votes before the proposal expires. If this is + /// enabled proposals will not be able to complete early as final + /// vote information is not known until the time of proposal + /// expiration. + pub allow_revoting: bool, + /// Information about what addresses may create proposals. + pub pre_propose_info: PreProposeInfo, + /// If set to true proposals will be closed if their execution + /// fails. Otherwise, proposals will remain open after execution + /// failure. For example, with this enabled a proposal to send 5 + /// tokens out of a DAO's treasury with 4 tokens would be closed when + /// it is executed. With this disabled, that same proposal would + /// remain open until the DAO's treasury was large enough for it to be + /// executed. + pub close_proposal_on_execution_failure: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + /// Creates a proposal in the module. + Propose { + /// The title of the proposal. + title: String, + /// A description of the proposal. + description: String, + /// The messages that should be executed in response to this + /// proposal passing. + msgs: Vec>, + /// The address creating the proposal. If no pre-propose + /// module is attached to this module this must always be None + /// as the proposer is the sender of the propose message. If a + /// pre-propose module is attached, this must be Some and will + /// set the proposer of the proposal it creates. + proposer: Option, + }, + /// Votes on a proposal. Voting power is determined by the DAO's + /// voting power module. + Vote { + /// The ID of the proposal to vote on. + proposal_id: u64, + /// The senders position on the proposal. + vote: Vote, + }, + /// Causes the messages associated with a passed proposal to be + /// executed by the DAO. + Execute { + /// The ID of the proposal to execute. + proposal_id: u64, + }, + /// Closes a proposal that has failed (either not passed or timed + /// out). If applicable this will cause the proposal deposit + /// associated wth said proposal to be returned. + Close { + /// The ID of the proposal to close. + proposal_id: u64, + }, + /// Updates the governance module's config. + UpdateConfig { + /// The new proposal passing threshold. This will only apply + /// to proposals created after the config update. + threshold: Threshold, + /// The default maximum amount of time a proposal may be voted + /// on before expiring. This will only apply to proposals + /// created after the config update. + max_voting_period: Duration, + /// The minimum amount of time a proposal must be open before + /// passing. A proposal may fail before this amount of time has + /// elapsed, but it will not pass. This can be useful for + /// preventing governance attacks wherein an attacker aquires a + /// large number of tokens and forces a proposal through. + min_voting_period: Option, + /// Allows changing votes before the proposal expires. If this is + /// enabled proposals will not be able to complete early as final + /// vote information is not known until the time of proposal + /// expiration. + allow_revoting: bool, + /// The address if tge DAO that this governance module is + /// associated with. + dao: String, + /// If set to true proposals will be closed if their execution + /// fails. Otherwise, proposals will remain open after execution + /// failure. For example, with this enabled a proposal to send 5 + /// tokens out of a DAO's treasury with 4 tokens would be closed when + /// it is executed. With this disabled, that same proposal would + /// remain open until the DAO's treasury was large enough for it to be + /// executed. + close_proposal_on_execution_failure: bool, + }, + /// Update's the proposal creation policy used for this + /// module. Only the DAO may call this method. + UpdatePreProposeInfo { info: PreProposeInfo }, + /// Adds an address as a consumer of proposal hooks. Consumers of + /// proposal hooks have hook messages executed on them whenever + /// the status of a proposal changes or a proposal is created. If + /// a consumer contract errors when handling a hook message it + /// will be removed from the list of consumers. + AddProposalHook { address: String }, + /// Removes a consumer of proposal hooks. + RemoveProposalHook { address: String }, + /// Adds an address as a consumer of vote hooks. Consumers of vote + /// hooks have hook messages executed on them whenever the a vote + /// is cast. If a consumer contract errors when handling a hook + /// message it will be removed from the list of consumers. + AddVoteHook { address: String }, + /// Removed a consumer of vote hooks. + RemoveVoteHook { address: String }, +} + +#[proposal_module_query] +#[info_query] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + /// Gets the governance module's config. Returns `state::Config`. + Config {}, + /// Gets information about a proposal. Returns + /// `proposals::Proposal`. + Proposal { proposal_id: u64 }, + /// Lists all the proposals that have been cast in this + /// module. Returns `query::ProposalListResponse`. + ListProposals { + /// The proposal ID to start listing proposals after. For + /// example, if this is set to 2 proposals with IDs 3 and + /// higher will be returned. + start_after: Option, + /// The maximum number of proposals to return as part of this + /// query. If no limit is set a max of 30 proposals will be + /// returned. + limit: Option, + }, + /// Lists all of the proposals that have been cast in this module + /// in decending order of proposal ID. Returns + /// `query::ProposalListResponse`. + ReverseProposals { + /// The proposal ID to start listing proposals before. For + /// example, if this is set to 6 proposals with IDs 5 and + /// lower will be returned. + start_before: Option, + /// The maximum number of proposals to return as part of this + /// query. If no limit is set a max of 30 proposals will be + /// returned. + limit: Option, + }, + /// Returns the number of proposals that have been created in this + /// module. + ProposalCount {}, + /// Returns a voters position on a propsal. Returns + /// `query::VoteResponse`. + GetVote { proposal_id: u64, voter: String }, + /// Lists all of the votes that have been cast on a + /// proposal. Returns `VoteListResponse`. + ListVotes { + /// The proposal to list the votes of. + proposal_id: u64, + /// The voter to start listing votes after. Ordering is done + /// alphabetically. + start_after: Option, + /// The maximum number of votes to return in response to this + /// query. If no limit is specified a max of 30 are returned. + limit: Option, + }, + /// Gets the current proposal creation policy for this + /// module. Returns `voting::pre_propose::ProposalCreationPolicy`. + ProposalCreationPolicy {}, + /// Lists all of the consumers of proposal hooks for this module. + ProposalHooks {}, + /// Lists all of the consumers of vote hooks for this + /// module. Returns cwd_hooks::HooksResponse. + VoteHooks {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum MigrateMsg {} diff --git a/contracts/proposal/cwd-proposal-single/src/proposal.rs b/contracts/proposal/cwd-proposal-single/src/proposal.rs new file mode 100644 index 00000000..f90606c2 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/src/proposal.rs @@ -0,0 +1,1088 @@ +use crate::query::ProposalResponse; +use crate::state::PROPOSAL_COUNT; +use cosmwasm_std::{Addr, BlockInfo, CosmosMsg, Decimal, StdResult, Storage, Uint128}; +use cw_utils::Expiration; +use cwd_voting::status::Status; +use cwd_voting::threshold::{PercentageThreshold, Threshold}; +use cwd_voting::voting::{does_vote_count_fail, does_vote_count_pass, Votes}; +use neutron_bindings::bindings::msg::NeutronMsg; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct SingleChoiceProposal { + pub title: String, + pub description: String, + /// The address that created this proposal. + pub proposer: Addr, + /// The block height at which this proposal was created. Voting + /// power queries should query for voting power at this block + /// height. + pub start_height: u64, + /// The minimum amount of time this proposal must remain open for + /// voting. The proposal may not pass unless this is expired or + /// None. + pub min_voting_period: Option, + /// The the time at which this proposal will expire and close for + /// additional votes. + pub expiration: Expiration, + /// The threshold at which this proposal will pass. + pub threshold: Threshold, + /// The total amount of voting power at the time of this + /// proposal's creation. + pub total_power: Uint128, + /// The messages that will be executed should this proposal pass. + pub msgs: Vec>, + pub status: Status, + pub votes: Votes, + pub allow_revoting: bool, +} + +pub fn advance_proposal_id(store: &mut dyn Storage) -> StdResult { + let id: u64 = PROPOSAL_COUNT.may_load(store)?.unwrap_or_default() + 1; + PROPOSAL_COUNT.save(store, &id)?; + Ok(id) +} + +impl SingleChoiceProposal { + /// Consumes the proposal and returns a version which may be used + /// in a query response. The difference being that proposal + /// statuses are only updated on vote, execute, and close + /// events. It is possible though that since a vote has occured + /// the proposal expiring has changed its status. This method + /// recomputes the status so that queries get accurate + /// information. + pub fn into_response(mut self, block: &BlockInfo, id: u64) -> ProposalResponse { + self.update_status(block); + ProposalResponse { id, proposal: self } + } + + /// Gets the current status of the proposal. + pub fn current_status(&self, block: &BlockInfo) -> Status { + if self.status == Status::Open && self.is_passed(block) { + Status::Passed + } else if self.status == Status::Open + && (self.expiration.is_expired(block) || self.is_rejected(block)) + { + Status::Rejected + } else { + self.status + } + } + + /// Sets a proposals status to its current status. + pub fn update_status(&mut self, block: &BlockInfo) { + let new_status = self.current_status(block); + self.status = new_status + } + + /// Returns true iff this proposal is sure to pass (even before + /// expiration if no future sequence of possible votes can cause + /// it to fail). + pub fn is_passed(&self, block: &BlockInfo) -> bool { + // If re-voting is allowed nothing is known until the proposal + // has expired. + if self.allow_revoting && !self.expiration.is_expired(block) { + return false; + } + // If the min voting period is set and not expired the + // proposal can not yet be passed. This gives DAO members some + // time to remove liquidity / scheme on a recovery plan if a + // single actor accumulates enough tokens to unilaterally pass + // proposals. + if let Some(min) = self.min_voting_period { + if !min.is_expired(block) { + return false; + } + } + + match self.threshold { + Threshold::AbsolutePercentage { percentage } => { + let options = self.total_power - self.votes.abstain; + does_vote_count_pass(self.votes.yes, options, percentage) + } + Threshold::ThresholdQuorum { threshold, quorum } => { + if !does_vote_count_pass(self.votes.total(), self.total_power, quorum) { + return false; + } + + if self.expiration.is_expired(block) { + // If the quorum is met and the proposal is + // expired the number of votes needed to pass a + // proposal is compared to the number of votes on + // the proposal. + let options = self.votes.total() - self.votes.abstain; + does_vote_count_pass(self.votes.yes, options, threshold) + } else { + let options = self.total_power - self.votes.abstain; + does_vote_count_pass(self.votes.yes, options, threshold) + } + } + Threshold::AbsoluteCount { threshold } => self.votes.yes >= threshold, + } + } + + /// As above for the passed check, used to check if a proposal is + /// already rejected. + pub fn is_rejected(&self, block: &BlockInfo) -> bool { + // If re-voting is allowed and the proposal is not expired no + // information is known. + if self.allow_revoting && !self.expiration.is_expired(block) { + return false; + } + + match self.threshold { + Threshold::AbsolutePercentage { + percentage: percentage_needed, + } => { + let options = self.total_power - self.votes.abstain; + + // If there is a 100% passing threshold.. + if percentage_needed == PercentageThreshold::Percent(Decimal::percent(100)) { + if options == Uint128::zero() { + // and there are no possible votes (zero + // voting power or all abstain), then this + // proposal has been rejected. + return true; + } else { + // and there are possible votes, then this is + // rejected if there is a single no vote. + // + // We need this check becuase otherwise when + // we invert the threshold (`Decimal::one() - + // threshold`) we get a 0% requirement for no + // votes. Zero no votes do indeed meet a 0% + // threshold. + return self.votes.no >= Uint128::new(1); + } + } + + does_vote_count_fail(self.votes.no, options, percentage_needed) + } + Threshold::ThresholdQuorum { threshold, quorum } => { + match ( + does_vote_count_pass(self.votes.total(), self.total_power, quorum), + self.expiration.is_expired(block), + ) { + // Has met quorum and is expired. + (true, true) => { + // => consider only votes cast and see if no + // votes meet threshold. + let options = self.votes.total() - self.votes.abstain; + + // If there is a 100% passing threshold.. + if threshold == PercentageThreshold::Percent(Decimal::percent(100)) { + if options == Uint128::zero() { + // and there are no possible votes (zero + // voting power or all abstain), then this + // proposal has been rejected. + return true; + } else { + // and there are possible votes, then this is + // rejected if there is a single no vote. + // + // We need this check becuase + // otherwise when we invert the + // threshold (`Decimal::one() - + // threshold`) we get a 0% requirement + // for no votes. Zero no votes do + // indeed meet a 0% threshold. + return self.votes.no >= Uint128::new(1); + } + } + does_vote_count_fail(self.votes.no, options, threshold) + } + // Has met quorum and is not expired. + // | Hasn't met quorum and is not expired. + (true, false) | (false, false) => { + // => consider all possible votes and see if + // no votes meet threshold. + let options = self.total_power - self.votes.abstain; + + // If there is a 100% passing threshold.. + if threshold == PercentageThreshold::Percent(Decimal::percent(100)) { + if options == Uint128::zero() { + // and there are no possible votes (zero + // voting power or all abstain), then this + // proposal has been rejected. + return true; + } else { + // and there are possible votes, then this is + // rejected if there is a single no vote. + // + // We need this check becuase otherwise + // when we invert the threshold + // (`Decimal::one() - threshold`) we + // get a 0% requirement for no + // votes. Zero no votes do indeed meet + // a 0% threshold. + return self.votes.no >= Uint128::new(1); + } + } + + does_vote_count_fail(self.votes.no, options, threshold) + } + // Hasn't met quorum requirement and voting has closed => rejected. + (false, true) => true, + } + } + Threshold::AbsoluteCount { threshold } => { + // If all the outstanding votes voting yes would not + // cause this proposal to pass then it is rejected. + let outstanding_votes = self.total_power - self.votes.total(); + self.votes.yes + outstanding_votes < threshold + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use cosmwasm_std::{testing::mock_env, Decimal}; + + fn setup_prop( + threshold: Threshold, + votes: Votes, + total_power: Uint128, + is_expired: bool, + min_voting_period_elapsed: bool, + allow_revoting: bool, + ) -> (SingleChoiceProposal, BlockInfo) { + let block = mock_env().block; + let expiration = match is_expired { + true => Expiration::AtHeight(block.height - 5), + false => Expiration::AtHeight(block.height + 100), + }; + let min_voting_period = match min_voting_period_elapsed { + true => Expiration::AtHeight(block.height - 5), + false => Expiration::AtHeight(block.height + 5), + }; + + let prop = SingleChoiceProposal { + title: "Demo".to_string(), + description: "Info".to_string(), + proposer: Addr::unchecked("test"), + start_height: 100, + expiration, + min_voting_period: Some(min_voting_period), + allow_revoting, + msgs: vec![], + status: Status::Open, + threshold, + total_power, + votes, + }; + (prop, block) + } + + fn check_is_passed( + threshold: Threshold, + votes: Votes, + total_power: Uint128, + is_expired: bool, + min_voting_period_elapsed: bool, + allow_revoting: bool, + ) -> bool { + let (prop, block) = setup_prop( + threshold, + votes, + total_power, + is_expired, + min_voting_period_elapsed, + allow_revoting, + ); + prop.is_passed(&block) + } + + fn check_is_rejected( + threshold: Threshold, + votes: Votes, + total_power: Uint128, + is_expired: bool, + min_voting_period_elapsed: bool, + allow_revoting: bool, + ) -> bool { + let (prop, block) = setup_prop( + threshold, + votes, + total_power, + is_expired, + min_voting_period_elapsed, + allow_revoting, + ); + prop.is_rejected(&block) + } + + #[test] + fn test_pass_majority_percentage() { + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }; + let votes = Votes { + yes: Uint128::new(7), + no: Uint128::new(4), + abstain: Uint128::new(2), + }; + + // 15 total votes. 7 yes and 2 abstain. Majority threshold. This + // should pass. + assert!(check_is_passed( + threshold.clone(), + votes.clone(), + Uint128::new(15), + false, + true, + false, + )); + // Proposal being expired should not effect those results. + assert!(check_is_passed( + threshold.clone(), + votes.clone(), + Uint128::new(15), + true, + true, + false + )); + + // More votes == higher threshold => not passed. + assert!(!check_is_passed( + threshold, + votes, + Uint128::new(17), + false, + true, + false + )); + } + + #[test] + fn test_min_voting_period_no_early_pass() { + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }; + let votes = Votes { + yes: Uint128::new(7), + no: Uint128::new(4), + abstain: Uint128::new(2), + }; + + // Does not pass if min voting period is not expired. + assert!(!check_is_passed( + threshold.clone(), + votes.clone(), + Uint128::new(15), + false, + false, + false, + )); + // Should not be rejected either. + assert!(!check_is_rejected( + threshold.clone(), + votes.clone(), + Uint128::new(15), + false, + false, + false, + )); + + // Min voting period being expired makes this pass. + assert!(check_is_passed( + threshold, + votes, + Uint128::new(15), + false, + true, + false + )); + } + + #[test] + fn test_min_voting_period_early_rejection() { + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }; + let votes = Votes { + yes: Uint128::new(4), + no: Uint128::new(7), + abstain: Uint128::new(2), + }; + + // Proposal has not passed. + assert!(!check_is_passed( + threshold.clone(), + votes.clone(), + Uint128::new(15), + false, + false, + false, + )); + // Should be rejected despite the min voting period not being + // passed. + assert!(check_is_rejected( + threshold, + votes, + Uint128::new(15), + false, + false, + false, + )); + } + + #[test] + fn test_revoting_majority_no_pass() { + // Revoting being allowed means that proposals may not be + // passed or rejected before they expire. + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }; + let votes = Votes { + yes: Uint128::new(7), + no: Uint128::new(4), + abstain: Uint128::new(2), + }; + + // 15 total votes. 7 yes and 2 abstain. Majority threshold. This + // should pass but revoting is enabled. + assert!(!check_is_passed( + threshold.clone(), + votes.clone(), + Uint128::new(15), + false, + true, + true, + )); + // Proposal being expired should cause the proposal to be + // passed as votes may no longer be cast. + assert!(check_is_passed( + threshold, + votes, + Uint128::new(15), + true, + true, + true + )); + } + + #[test] + fn test_revoting_majority_rejection() { + // Revoting being allowed means that proposals may not be + // passed or rejected before they expire. + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }; + let votes = Votes { + yes: Uint128::new(4), + no: Uint128::new(7), + abstain: Uint128::new(2), + }; + + // Not expired, revoting allowed => no rejection. + assert!(!check_is_rejected( + threshold.clone(), + votes.clone(), + Uint128::new(15), + false, + true, + true + )); + + // Expired, revoting allowed => rejection. + assert!(check_is_rejected( + threshold, + votes, + Uint128::new(15), + true, + true, + true + )); + } + + /// Simple checks for absolute count passing and failing + /// conditions. + #[test] + fn test_absolute_count_threshold() { + let threshold = Threshold::AbsoluteCount { + threshold: Uint128::new(10), + }; + + assert!(check_is_passed( + threshold.clone(), + Votes { + yes: Uint128::new(10), + no: Uint128::zero(), + abstain: Uint128::zero(), + }, + Uint128::new(100), + false, + true, + false + )); + + assert!(check_is_rejected( + threshold.clone(), + Votes { + yes: Uint128::new(9), + no: Uint128::new(1), + abstain: Uint128::zero() + }, + Uint128::new(10), + false, + true, + false + )); + + assert!(!check_is_rejected( + threshold.clone(), + Votes { + yes: Uint128::new(9), + no: Uint128::new(1), + abstain: Uint128::zero() + }, + Uint128::new(11), + false, + true, + false + )); + + assert!(!check_is_passed( + threshold, + Votes { + yes: Uint128::new(9), + no: Uint128::new(1), + abstain: Uint128::zero() + }, + Uint128::new(11), + false, + true, + false + )); + } + + /// Tests that revoting works as expected with an absolute count + /// style threshold. + #[test] + fn test_absolute_count_threshold_revoting() { + let threshold = Threshold::AbsoluteCount { + threshold: Uint128::new(10), + }; + + assert!(!check_is_passed( + threshold.clone(), + Votes { + yes: Uint128::new(10), + no: Uint128::zero(), + abstain: Uint128::zero(), + }, + Uint128::new(100), + false, + true, + true + )); + assert!(check_is_passed( + threshold.clone(), + Votes { + yes: Uint128::new(10), + no: Uint128::zero(), + abstain: Uint128::zero(), + }, + Uint128::new(100), + true, + true, + true + )); + + assert!(!check_is_rejected( + threshold.clone(), + Votes { + yes: Uint128::new(9), + no: Uint128::new(1), + abstain: Uint128::zero() + }, + Uint128::new(10), + false, + true, + true + )); + assert!(check_is_rejected( + threshold, + Votes { + yes: Uint128::new(9), + no: Uint128::new(1), + abstain: Uint128::zero() + }, + Uint128::new(10), + true, + true, + true + )); + } + + #[test] + fn test_tricky_pass() { + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::from_ratio(7u32, 13u32)), + }; + let votes = Votes { + yes: Uint128::new(7), + no: Uint128::new(6), + abstain: Uint128::zero(), + }; + assert!(check_is_passed( + threshold, + votes, + Uint128::new(13), + false, + true, + false + )) + } + + #[test] + fn test_weird_failure_rounding() { + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Percent(Decimal::from_ratio(6u32, 13u32)), + }; + let votes = Votes { + yes: Uint128::new(6), + no: Uint128::new(7), + abstain: Uint128::zero(), + }; + assert!(check_is_passed( + threshold.clone(), + votes.clone(), + Uint128::new(13), + false, + true, + false + )); + assert!(!check_is_rejected( + threshold, + votes, + Uint128::new(13), + false, + true, + false + )); + } + + #[test] + fn test_tricky_pass_majority() { + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }; + let votes = Votes { + yes: Uint128::new(7), + no: Uint128::new(6), + abstain: Uint128::zero(), + }; + assert!(check_is_passed( + threshold.clone(), + votes.clone(), + Uint128::new(13), + false, + true, + false + )); + assert!(!check_is_passed( + threshold, + votes, + Uint128::new(14), + false, + true, + false + )) + } + + #[test] + fn test_reject_majority_percentage() { + let percent = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }; + + // 4 YES, 7 NO, 2 ABSTAIN + let votes = Votes { + yes: Uint128::new(4), + no: Uint128::new(7), + abstain: Uint128::new(2), + }; + + // 15 total voting power + // 7 / (15 - 2) > 50% + // Expiry does not matter + assert!(check_is_rejected( + percent.clone(), + votes.clone(), + Uint128::new(15), + false, + true, + false, + )); + assert!(check_is_rejected( + percent.clone(), + votes.clone(), + Uint128::new(15), + true, + true, + false + )); + + // 17 total voting power + // 7 / (17 - 2) < 50% + assert!(!check_is_rejected( + percent.clone(), + votes.clone(), + Uint128::new(17), + false, + true, + false + )); + assert!(!check_is_rejected( + percent.clone(), + votes.clone(), + Uint128::new(17), + true, + true, + false + )); + + // Rejected if total was lower + assert!(check_is_rejected( + percent.clone(), + votes.clone(), + Uint128::new(14), + false, + true, + false + )); + assert!(check_is_rejected( + percent, + votes, + Uint128::new(14), + true, + true, + false + )); + } + + #[test] + fn proposal_passed_quorum() { + let quorum = Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Percent(Decimal::percent(50)), + quorum: PercentageThreshold::Percent(Decimal::percent(40)), + }; + // all non-yes votes are counted for quorum + let passing = Votes { + yes: Uint128::new(7), + no: Uint128::new(3), + abstain: Uint128::new(2), + }; + // abstain votes are not counted for threshold => yes / (yes + no + veto) + let passes_ignoring_abstain = Votes { + yes: Uint128::new(6), + no: Uint128::new(6), + abstain: Uint128::new(5), + }; + // fails any way you look at it + let failing = Votes { + yes: Uint128::new(6), + no: Uint128::new(7), + abstain: Uint128::new(2), + }; + + // first, expired (voting period over) + // over quorum (40% of 30 = 12), over threshold (7/12 > 50%) + assert!(check_is_passed( + quorum.clone(), + passing.clone(), + Uint128::new(30), + true, + true, + false, + )); + // under quorum it is not passing (40% of 33 = 13.2 > 13) + assert!(!check_is_passed( + quorum.clone(), + passing.clone(), + Uint128::new(33), + true, + true, + false + )); + // over quorum, threshold passes if we ignore abstain + // 17 total votes w/ abstain => 40% quorum of 40 total + // 6 yes / (6 yes + 4 no + 2 votes) => 50% threshold + assert!(check_is_passed( + quorum.clone(), + passes_ignoring_abstain.clone(), + Uint128::new(40), + true, + true, + false, + )); + // over quorum, but under threshold fails also + assert!(!check_is_passed( + quorum.clone(), + failing, + Uint128::new(20), + true, + true, + false + )); + + // now, check with open voting period + // would pass if closed, but fail here, as remaining votes no -> fail + assert!(!check_is_passed( + quorum.clone(), + passing.clone(), + Uint128::new(30), + false, + true, + false + )); + assert!(!check_is_passed( + quorum.clone(), + passes_ignoring_abstain.clone(), + Uint128::new(40), + false, + true, + false + )); + // if we have threshold * total_weight as yes votes this must pass + assert!(check_is_passed( + quorum.clone(), + passing.clone(), + Uint128::new(14), + false, + true, + false + )); + // all votes have been cast, some abstain + assert!(check_is_passed( + quorum.clone(), + passes_ignoring_abstain, + Uint128::new(17), + false, + true, + false + )); + // 3 votes uncast, if they all vote no, we have 7 yes, 7 no+veto, 2 abstain (out of 16) + assert!(check_is_passed( + quorum, + passing, + Uint128::new(16), + false, + true, + false + )); + } + + #[test] + fn proposal_rejected_quorum() { + let quorum = Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Majority {}, + quorum: PercentageThreshold::Percent(Decimal::percent(40)), + }; + // all non-yes votes are counted for quorum + let rejecting = Votes { + yes: Uint128::new(3), + no: Uint128::new(8), + abstain: Uint128::new(2), + }; + // abstain votes are not counted for threshold => yes / (yes + no) + let rejected_ignoring_abstain = Votes { + yes: Uint128::new(4), + no: Uint128::new(8), + abstain: Uint128::new(5), + }; + // fails any way you look at it + let failing = Votes { + yes: Uint128::new(5), + no: Uint128::new(8), + abstain: Uint128::new(2), + }; + + // first, expired (voting period over) + // over quorum (40% of 30 = 12), over threshold (7/11 > 50%) + assert!(check_is_rejected( + quorum.clone(), + rejecting.clone(), + Uint128::new(30), + true, + true, + false + )); + // Total power of 33. 13 total votes. 8 no votes, 3 yes, 2 + // abstain. 39.3% turnout. Expired. As it is expired we see if + // the 8 no votes excede the 50% failing threshold, which they + // do. + assert!(check_is_rejected( + quorum.clone(), + rejecting.clone(), + Uint128::new(33), + true, + true, + false + )); + + // over quorum, threshold passes if we ignore abstain + // 17 total votes w/ abstain => 40% quorum of 40 total + // 6 no / (6 no + 4 yes + 2 votes) => 50% threshold + assert!(check_is_rejected( + quorum.clone(), + rejected_ignoring_abstain.clone(), + Uint128::new(40), + true, + true, + false + )); + + // Over quorum, but under threshold fails if the proposal is + // not expired. If the proposal is expired though it passes as + // the total vote count used is the number of votes, and not + // the total number of votes avaliable. + assert!(check_is_rejected( + quorum.clone(), + failing.clone(), + Uint128::new(20), + true, + true, + false + )); + assert!(!check_is_rejected( + quorum.clone(), + failing, + Uint128::new(20), + false, + true, + false + )); + + // Voting is still open so assume rest of votes are yes + // threshold not reached + assert!(!check_is_rejected( + quorum.clone(), + rejecting.clone(), + Uint128::new(30), + false, + true, + false + )); + assert!(!check_is_rejected( + quorum.clone(), + rejected_ignoring_abstain.clone(), + Uint128::new(40), + false, + true, + false + )); + // if we have threshold * total_weight as no votes this must reject + assert!(check_is_rejected( + quorum.clone(), + rejecting.clone(), + Uint128::new(14), + false, + true, + false + )); + // all votes have been cast, some abstain + assert!(check_is_rejected( + quorum.clone(), + rejected_ignoring_abstain, + Uint128::new(17), + false, + true, + false + )); + // 3 votes uncast, if they all vote yes, we have 7 no, 7 yes+veto, 2 abstain (out of 16) + assert!(check_is_rejected( + quorum, + rejecting, + Uint128::new(16), + false, + true, + false + )); + } + + #[test] + fn quorum_edge_cases() { + // When we pass absolute threshold (everyone else voting no, + // we pass), but still don't hit quorum. + let quorum = Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Percent(Decimal::percent(60)), + quorum: PercentageThreshold::Percent(Decimal::percent(80)), + }; + + // Try 9 yes, 1 no (out of 15) -> 90% voter threshold, 60% + // absolute threshold, still no quorum doesn't matter if + // expired or not. + let missing_voters = Votes { + yes: Uint128::new(9), + no: Uint128::new(1), + abstain: Uint128::new(0), + }; + assert!(!check_is_passed( + quorum.clone(), + missing_voters.clone(), + Uint128::new(15), + false, + true, + false + )); + assert!(!check_is_passed( + quorum.clone(), + missing_voters, + Uint128::new(15), + true, + true, + false + )); + + // 1 less yes, 3 vetos and this passes only when expired. + let wait_til_expired = Votes { + yes: Uint128::new(8), + no: Uint128::new(4), + abstain: Uint128::new(0), + }; + assert!(!check_is_passed( + quorum.clone(), + wait_til_expired.clone(), + Uint128::new(15), + false, + true, + false + )); + assert!(check_is_passed( + quorum.clone(), + wait_til_expired, + Uint128::new(15), + true, + true, + false + )); + + // 9 yes and 3 nos passes early + let passes_early = Votes { + yes: Uint128::new(9), + no: Uint128::new(3), + abstain: Uint128::new(0), + }; + assert!(check_is_passed( + quorum.clone(), + passes_early.clone(), + Uint128::new(15), + false, + true, + false + )); + assert!(check_is_passed( + quorum, + passes_early, + Uint128::new(15), + true, + true, + false + )); + } +} diff --git a/contracts/proposal/cwd-proposal-single/src/query.rs b/contracts/proposal/cwd-proposal-single/src/query.rs new file mode 100644 index 00000000..91aecbee --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/src/query.rs @@ -0,0 +1,46 @@ +use cosmwasm_std::{Addr, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cwd_voting::voting::Vote; + +use crate::proposal::SingleChoiceProposal; + +/// Information about a proposal returned by proposal queries. +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct ProposalResponse { + /// The ID of the proposal being returned. + pub id: u64, + pub proposal: SingleChoiceProposal, +} + +/// Information about a vote that was cast. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct VoteInfo { + /// The address that voted. + pub voter: Addr, + /// Position on the vote. + pub vote: Vote, + /// The voting power behind the vote. + pub power: Uint128, +} + +/// Information about a vote. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct VoteResponse { + /// None if no such vote, Some otherwise. + pub vote: Option, +} + +/// Information about the votes for a proposal. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct VoteListResponse { + pub votes: Vec, +} + +/// A list of proposals returned by `ListProposals` and +/// `ReverseProposals`. +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct ProposalListResponse { + pub proposals: Vec, +} diff --git a/contracts/proposal/cwd-proposal-single/src/state.rs b/contracts/proposal/cwd-proposal-single/src/state.rs new file mode 100644 index 00000000..4805b029 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/src/state.rs @@ -0,0 +1,66 @@ +use cosmwasm_std::{Addr, Uint128}; +use cw_storage_plus::{Item, Map}; +use cw_utils::Duration; + +use cwd_hooks::Hooks; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cwd_voting::{pre_propose::ProposalCreationPolicy, threshold::Threshold, voting::Vote}; + +use crate::proposal::SingleChoiceProposal; + +/// A vote cast for a proposal. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Ballot { + /// The amount of voting power behind the vote. + pub power: Uint128, + /// The position. + pub vote: Vote, +} +/// The governance module's configuration. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Config { + /// The threshold a proposal must reach to complete. + pub threshold: Threshold, + /// The default maximum amount of time a proposal may be voted on + /// before expiring. + pub max_voting_period: Duration, + /// The minimum amount of time a proposal must be open before + /// passing. A proposal may fail before this amount of time has + /// elapsed, but it will not pass. This can be useful for + /// preventing governance attacks wherein an attacker aquires a + /// large number of tokens and forces a proposal through. + pub min_voting_period: Option, + /// Allows changing votes before the proposal expires. If this is + /// enabled proposals will not be able to complete early as final + /// vote information is not known until the time of proposal + /// expiration. + pub allow_revoting: bool, + /// The address of the DAO that this governance module is + /// associated with. + pub dao: Addr, + /// If set to true proposals will be closed if their execution + /// fails. Otherwise, proposals will remain open after execution + /// failure. For example, with this enabled a proposal to send 5 + /// tokens out of a DAO's treasury with 4 tokens would be closed when + /// it is executed. With this disabled, that same proposal would + /// remain open until the DAO's treasury was large enough for it to be + /// executed. + pub close_proposal_on_execution_failure: bool, +} + +/// The current top level config for the module. The "config" key was +/// previously used to store configs for v1 DAOs. +pub const CONFIG: Item = Item::new("config_v2"); +/// The number of proposals that have been created. +pub const PROPOSAL_COUNT: Item = Item::new("proposal_count"); +pub const PROPOSALS: Map = Map::new("proposals_v2"); +pub const BALLOTS: Map<(u64, Addr), Ballot> = Map::new("ballots"); +/// Consumers of proposal state change hooks. +pub const PROPOSAL_HOOKS: Hooks = Hooks::new("proposal_hooks"); +/// Consumers of vote hooks. +pub const VOTE_HOOKS: Hooks = Hooks::new("vote_hooks"); +/// The address of the pre-propose module associated with this +/// proposal module (if any). +pub const CREATION_POLICY: Item = Item::new("creation_policy"); diff --git a/contracts/proposal/cwd-proposal-single/src/tests.rs b/contracts/proposal/cwd-proposal-single/src/tests.rs new file mode 100644 index 00000000..7afb4be8 --- /dev/null +++ b/contracts/proposal/cwd-proposal-single/src/tests.rs @@ -0,0 +1,612 @@ +use std::u128; + +use cosmwasm_std::{ + coins, + testing::{mock_dependencies, mock_env}, + to_binary, Addr, Coin, CosmosMsg, Decimal, Empty, Order, Timestamp, Uint128, WasmMsg, +}; +use cw20::Cw20Coin; +use cwd_voting_cw20_staked::msg::ActiveThreshold; + +use cwd_core::state::ProposalModule; +use cwd_interface::{Admin, ModuleInstantiateInfo}; +use cw_multi_test::{next_block, App, BankSudo, Contract, ContractWrapper, Executor, SudoMsg}; +use cw_pre_propose_base_proposal_single as cppbps; +use cw_storage_plus::{Item, Map}; +use cw_utils::Duration; +use cw_utils::Expiration; + +use cwd_hooks::HooksResponse; + +use cw_denom::{CheckedDenom, UncheckedDenom}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use testing::{ShouldExecute, TestSingleChoiceVote}; +use cwd_voting::{ + deposit::{CheckedDepositInfo, DepositRefundPolicy, DepositToken, UncheckedDepositInfo}, + pre_propose::{PreProposeInfo, ProposalCreationPolicy}, + status::Status, + threshold::{PercentageThreshold, Threshold}, + voting::{Vote, Votes}, +}; + +use crate::{ + contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}, + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, + proposal::SingleChoiceProposal, + query::{ProposalListResponse, ProposalResponse, VoteInfo, VoteResponse}, + state::Config, + ContractError, +}; + +const CREATOR_ADDR: &str = "creator"; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +struct V1Proposal { + pub title: String, + pub description: String, + pub proposer: Addr, + pub start_height: u64, + pub min_voting_period: Option, + pub expiration: Expiration, + pub threshold: Threshold, + pub total_power: Uint128, + pub msgs: Vec>, + pub status: Status, + pub votes: Votes, + pub allow_revoting: bool, + pub deposit_info: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct V1Config { + pub threshold: Threshold, + pub max_voting_period: Duration, + pub min_voting_period: Option, + pub only_members_execute: bool, + pub allow_revoting: bool, + pub dao: Addr, + pub deposit_info: Option, +} + +#[test] +fn test_migrate_mock() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let current_block = &env.block; + let max_voting_period = cw_utils::Duration::Height(6); + + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }; + + // Write to storage in old data format + let v1_map: Map = Map::new("proposals"); + let v1_proposal = V1Proposal { + title: "A simple text proposal".to_string(), + description: "This is a simple text proposal".to_string(), + proposer: Addr::unchecked(CREATOR_ADDR), + start_height: env.block.height, + expiration: max_voting_period.after(current_block), + min_voting_period: None, + threshold: threshold.clone(), + allow_revoting: false, + total_power: Uint128::new(100_000_000), + msgs: vec![], + status: Status::Open, + votes: Votes::zero(), + deposit_info: None, + }; + v1_map.save(&mut deps.storage, 0, &v1_proposal).unwrap(); + + let v1_item: Item = Item::new("config"); + let v1_config = V1Config { + threshold: threshold.clone(), + max_voting_period, + min_voting_period: None, + only_members_execute: true, + allow_revoting: false, + dao: Addr::unchecked("simple happy desert"), + deposit_info: None, + }; + v1_item.save(&mut deps.storage, &v1_config).unwrap(); + + let msg = MigrateMsg::FromV1 { + close_proposal_on_execution_failure: true, + }; + migrate(deps.as_mut(), env.clone(), msg).unwrap(); + + // Verify migration. + let new_map: Map = Map::new("proposals_v2"); + let proposals: Vec<(u64, SingleChoiceProposal)> = new_map + .range(&deps.storage, None, None, Order::Ascending) + .collect::, _>>() + .unwrap(); + + let migrated_proposal = &proposals[0]; + assert_eq!(migrated_proposal.0, 0); + + let new_item: Item = Item::new("config_v2"); + let migrated_config = new_item.load(&deps.storage).unwrap(); + // assert_eq!( + // migrated_config, + // Config { + // threshold, + // max_voting_period, + // min_voting_period: None, + // only_members_execute: true, + // allow_revoting: false, + // dao: Addr::unchecked("simple happy desert"), + // deposit_info: None, + // close_proposal_on_execution_failure: true, + // open_proposal_submission: false, + // } + // ); + todo!("(zeke) hmmmmmmmmm") +} + +#[test] +fn test_close_failed_proposal() { + let mut app = App::default(); + let govmod_id = app.store_code(proposal_contract()); + + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }; + let max_voting_period = cw_utils::Duration::Height(6); + let instantiate = InstantiateMsg { + threshold, + max_voting_period, + min_voting_period: None, + only_members_execute: false, + allow_revoting: false, + pre_propose_info: get_pre_propose_info(&mut app, None, false), + close_proposal_on_execution_failure: true, + }; + + let governance_addr = + instantiate_with_staking_active_threshold(&mut app, govmod_id, instantiate, None, None); + let governance_modules: Vec = app + .wrap() + .query_wasm_smart( + governance_addr, + &cwd_core::msg::QueryMsg::ProposalModules { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(governance_modules.len(), 1); + let govmod_single = governance_modules.into_iter().next().unwrap().address; + + let govmod_config: Config = app + .wrap() + .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) + .unwrap(); + let dao = govmod_config.dao; + let voting_module: Addr = app + .wrap() + .query_wasm_smart(dao, &cwd_core::msg::QueryMsg::VotingModule {}) + .unwrap(); + let staking_contract: Addr = app + .wrap() + .query_wasm_smart( + voting_module.clone(), + &cwd_voting_cw20_staked::msg::QueryMsg::StakingContract {}, + ) + .unwrap(); + let token_contract: Addr = app + .wrap() + .query_wasm_smart( + voting_module, + &cwd_interface::voting::Query::TokenContract {}, + ) + .unwrap(); + + // Stake some tokens so we can propose + let msg = cw20::Cw20ExecuteMsg::Send { + contract: staking_contract.to_string(), + amount: Uint128::new(2000), + msg: to_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), + }; + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + token_contract.clone(), + &msg, + &[], + ) + .unwrap(); + app.update_block(next_block); + + let msg = cw20::Cw20ExecuteMsg::Burn { + amount: Uint128::new(2000), + }; + let binary_msg = to_binary(&msg).unwrap(); + + // Overburn tokens + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + govmod_single.clone(), + &ExecuteMsg::Propose { + title: "A simple burn tokens proposal".to_string(), + description: "Burning more tokens, than dao treasury have".to_string(), + msgs: vec![WasmMsg::Execute { + contract_addr: token_contract.to_string(), + msg: binary_msg.clone(), + funds: vec![], + } + .into()], + }, + &[], + ) + .unwrap(); + + // Vote on proposal + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + govmod_single.clone(), + &ExecuteMsg::Vote { + proposal_id: 1, + vote: Vote::Yes, + }, + &[], + ) + .unwrap(); + + let timestamp = Timestamp::from_seconds(300_000_000); + app.update_block(|block| block.time = timestamp); + + // Execute proposal + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + govmod_single.clone(), + &ExecuteMsg::Execute { proposal_id: 1 }, + &[], + ) + .unwrap(); + + let failed: ProposalResponse = app + .wrap() + .query_wasm_smart( + govmod_single.clone(), + &QueryMsg::Proposal { proposal_id: 1 }, + ) + .unwrap(); + assert_eq!(failed.proposal.status, Status::ExecutionFailed); + + // With disabled feature + // Disable feature first + { + let original: Config = app + .wrap() + .query_wasm_smart(govmod_single.clone(), &QueryMsg::Config {}) + .unwrap(); + + let pre_propose_info = get_pre_propose_info(&mut app, None, false); + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + govmod_single.clone(), + &ExecuteMsg::Propose { + title: "Disable closing failed proposals".to_string(), + description: "We want to re-execute failed proposals".to_string(), + msgs: vec![WasmMsg::Execute { + contract_addr: govmod_single.to_string(), + msg: to_binary(&ExecuteMsg::UpdateConfig { + threshold: original.threshold, + max_voting_period: original.max_voting_period, + min_voting_period: original.min_voting_period, + only_members_execute: original.only_members_execute, + allow_revoting: original.allow_revoting, + dao: original.dao.to_string(), + pre_propose_info, + close_proposal_on_execution_failure: false, + }) + .unwrap(), + funds: vec![], + } + .into()], + }, + &[], + ) + .unwrap(); + + // Vote on proposal + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + govmod_single.clone(), + &ExecuteMsg::Vote { + proposal_id: 2, + vote: Vote::Yes, + }, + &[], + ) + .unwrap(); + + // Execute proposal + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + govmod_single.clone(), + &ExecuteMsg::Execute { proposal_id: 2 }, + &[], + ) + .unwrap(); + } + + // Overburn tokens (again), this time without reverting + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + govmod_single.clone(), + &ExecuteMsg::Propose { + title: "A simple burn tokens proposal".to_string(), + description: "Burning more tokens, than dao treasury have".to_string(), + msgs: vec![WasmMsg::Execute { + contract_addr: token_contract.to_string(), + msg: binary_msg, + funds: vec![], + } + .into()], + }, + &[], + ) + .unwrap(); + + // Vote on proposal + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + govmod_single.clone(), + &ExecuteMsg::Vote { + proposal_id: 3, + vote: Vote::Yes, + }, + &[], + ) + .unwrap(); + + // Execute proposal + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + govmod_single.clone(), + &ExecuteMsg::Execute { proposal_id: 3 }, + &[], + ) + .expect_err("Should be sub overflow"); + + // Status should still be passed + let updated: ProposalResponse = app + .wrap() + .query_wasm_smart(govmod_single, &QueryMsg::Proposal { proposal_id: 3 }) + .unwrap(); + + // not reverted + assert_eq!(updated.proposal.status, Status::Passed); +} + +#[test] +fn test_no_double_refund_on_execute_fail_and_close() { + let mut app = App::default(); + let proposal_module_id = app.store_code(proposal_contract()); + + let threshold = Threshold::AbsolutePercentage { + percentage: PercentageThreshold::Majority {}, + }; + let max_voting_period = cw_utils::Duration::Height(6); + let instantiate = InstantiateMsg { + threshold, + max_voting_period, + min_voting_period: None, + only_members_execute: false, + allow_revoting: false, + pre_propose_info: get_pre_propose_info( + &mut app, + Some(UncheckedDepositInfo { + denom: DepositToken::VotingModuleToken {}, + amount: Uint128::new(1), + // Important to set to always here as we want to be sure + // that we don't get a second refund on close. Refunds on + // close only happen if Deposity Refund Policy is "Always". + refund_policy: DepositRefundPolicy::Always, + }), + false, + ), + close_proposal_on_execution_failure: true, + }; + + let core_addr = instantiate_with_staking_active_threshold( + &mut app, + proposal_module_id, + instantiate, + Some(vec![Cw20Coin { + address: CREATOR_ADDR.to_string(), + // One token for sending to the DAO treasury, one token + // for staking, one token for paying the proposal deposit. + amount: Uint128::new(3), + }]), + None, + ); + let proposal_modules: Vec = app + .wrap() + .query_wasm_smart( + core_addr, + &cwd_core::msg::QueryMsg::ProposalModules { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + assert_eq!(proposal_modules.len(), 1); + let proposal_single = proposal_modules.into_iter().next().unwrap().address; + + let proposal_config: Config = app + .wrap() + .query_wasm_smart(proposal_single.clone(), &QueryMsg::Config {}) + .unwrap(); + let dao = proposal_config.dao; + let voting_module: Addr = app + .wrap() + .query_wasm_smart(dao, &cwd_core::msg::QueryMsg::VotingModule {}) + .unwrap(); + let staking_contract: Addr = app + .wrap() + .query_wasm_smart( + voting_module.clone(), + &cwd_voting_cw20_staked::msg::QueryMsg::StakingContract {}, + ) + .unwrap(); + let token_contract: Addr = app + .wrap() + .query_wasm_smart( + voting_module, + &cwd_interface::voting::Query::TokenContract {}, + ) + .unwrap(); + + // Stake a token so we can propose. + let msg = cw20::Cw20ExecuteMsg::Send { + contract: staking_contract.to_string(), + amount: Uint128::new(1), + msg: to_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), + }; + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + token_contract.clone(), + &msg, + &[], + ) + .unwrap(); + app.update_block(next_block); + + // Send some tokens to the proposal module so it has the ability + // to double refund if the code is buggy. + let msg = cw20::Cw20ExecuteMsg::Transfer { + recipient: proposal_single.to_string(), + amount: Uint128::new(1), + }; + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + token_contract.clone(), + &msg, + &[], + ) + .unwrap(); + + let msg = cw20::Cw20ExecuteMsg::Burn { + amount: Uint128::new(2000), + }; + let binary_msg = to_binary(&msg).unwrap(); + + // Increase allowance to pay the proposal deposit. + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + token_contract.clone(), + &cw20_base::msg::ExecuteMsg::IncreaseAllowance { + spender: proposal_single.to_string(), + amount: Uint128::new(1), + expires: None, + }, + &[], + ) + .unwrap(); + + // proposal to overburn tokens + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + proposal_single.clone(), + &ExecuteMsg::Propose { + title: "A simple burn tokens proposal".to_string(), + description: "Burning more tokens, than dao treasury have".to_string(), + msgs: vec![WasmMsg::Execute { + contract_addr: token_contract.to_string(), + msg: binary_msg, + funds: vec![], + } + .into()], + }, + &[], + ) + .unwrap(); + + // Vote on proposal + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + proposal_single.clone(), + &ExecuteMsg::Vote { + proposal_id: 1, + vote: Vote::Yes, + }, + &[], + ) + .unwrap(); + + let timestamp = Timestamp::from_seconds(300_000_000); + app.update_block(|block| block.time = timestamp); + + // Execute proposal + app.execute_contract( + Addr::unchecked(CREATOR_ADDR), + proposal_single.clone(), + &ExecuteMsg::Execute { proposal_id: 1 }, + &[], + ) + .unwrap(); + + let failed: ProposalResponse = app + .wrap() + .query_wasm_smart( + proposal_single.clone(), + &QueryMsg::Proposal { proposal_id: 1 }, + ) + .unwrap(); + assert_eq!(failed.proposal.status, Status::ExecutionFailed); + + // Check that our deposit has been refunded. + let balance: cw20::BalanceResponse = app + .wrap() + .query_wasm_smart( + token_contract.to_string(), + &cw20::Cw20QueryMsg::Balance { + address: CREATOR_ADDR.to_string(), + }, + ) + .unwrap(); + + assert_eq!(balance.balance, Uint128::new(1)); + + // Close the proposal - this should fail as it was executed. + let err: ContractError = app + .execute_contract( + Addr::unchecked(CREATOR_ADDR), + proposal_single, + &ExecuteMsg::Close { proposal_id: 1 }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + + assert!(matches!(err, ContractError::WrongCloseStatus {})); + + // Check that our deposit was not refunded a second time on close. + let balance: cw20::BalanceResponse = app + .wrap() + .query_wasm_smart( + token_contract.to_string(), + &cw20::Cw20QueryMsg::Balance { + address: CREATOR_ADDR.to_string(), + }, + ) + .unwrap(); + + assert_eq!(balance.balance, Uint128::new(1)); +} + +#[test] +pub fn test_migrate_update_version() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + migrate(deps.as_mut(), mock_env(), MigrateMsg::FromCompatible {}).unwrap(); + let version = cw2::get_contract_version(&deps.storage).unwrap(); + assert_eq!(version.version, CONTRACT_VERSION); + assert_eq!(version.contract, CONTRACT_NAME); +} diff --git a/contracts/voting/.cargo/config b/contracts/voting/.cargo/config deleted file mode 100644 index 7c115322..00000000 --- a/contracts/voting/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib --features backtraces" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/voting/Cargo.toml b/contracts/voting/Cargo.toml deleted file mode 100644 index a66618e4..00000000 --- a/contracts/voting/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "neutron-dao" -version = "0.1.0" -authors = ["quasisamurai "] -edition = "2018" -license = "Apache-2.0" -repository = "https://github.com/neutron/neutron-dao" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cosmwasm-std = { version = "1.0", features = ["stargate"] } -cw2 = "0.13" -cw-storage-plus = "0.13" -schemars = "0.8.1" -serde = { version = "1.0.103", default-features = false, features = ["derive"] } - -[dev-dependencies] -cosmwasm-schema = { version = "1.0.0", default-features = false } diff --git a/contracts/voting/Makefile b/contracts/voting/Makefile deleted file mode 100644 index e2aa6a26..00000000 --- a/contracts/voting/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -CURRENT_DIR = $(shell pwd) -CURRENT_DIR_RELATIVE = $(notdir $(shell pwd)) - -clippy: - rustup component add clippy || true - cargo clippy --all-targets --all-features --workspace -- -D warnings - -test: clippy - cargo unit-test diff --git a/contracts/voting/README.md b/contracts/voting/README.md deleted file mode 100644 index ba79824e..00000000 --- a/contracts/voting/README.md +++ /dev/null @@ -1 +0,0 @@ -# Neutron voting \ No newline at end of file diff --git a/contracts/voting/examples/schema.rs b/contracts/voting/examples/schema.rs deleted file mode 100644 index 248bdf59..00000000 --- a/contracts/voting/examples/schema.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2022 Neutron -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; - -use neutron_dao::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema(&schema_for!(ExecuteMsg), &out_dir); - export_schema(&schema_for!(QueryMsg), &out_dir); -} diff --git a/contracts/voting/neutron-vault/.cargo/config b/contracts/voting/neutron-vault/.cargo/config new file mode 100644 index 00000000..336b618a --- /dev/null +++ b/contracts/voting/neutron-vault/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/voting/neutron-vault/.gitignore b/contracts/voting/neutron-vault/.gitignore new file mode 100644 index 00000000..dfdaaa6b --- /dev/null +++ b/contracts/voting/neutron-vault/.gitignore @@ -0,0 +1,15 @@ +# Build results +/target + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/contracts/voting/neutron-vault/Cargo.toml b/contracts/voting/neutron-vault/Cargo.toml new file mode 100644 index 00000000..8bf6dfd4 --- /dev/null +++ b/contracts/voting/neutron-vault/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "neutron-vault" +version = "0.2.0" +authors = ["Callum Anderson "] +edition = "2021" +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A DAO DAO voting module based on staked cw721 tokens." + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-std = { version = "1.0.0" } +cosmwasm-storage = { version = "1.0.0" } +cw-storage-plus = "0.13" +cw2 = "0.13" +cw-utils = "0.13" +cw-controllers = "0.13" +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +cwd-macros = { path = "../../../packages/cwd-macros" } +cwd-interface = { path = "../../../packages/cwd-interface" } +cw-paginate = { path = "../../../packages/cw-paginate" } + +[dev-dependencies] +cosmwasm-schema = { version = "1.0.0" } +cw-multi-test = "0.13" +anyhow = "1.0.57" diff --git a/contracts/voting/neutron-vault/README.md b/contracts/voting/neutron-vault/README.md new file mode 100644 index 00000000..bc25443c --- /dev/null +++ b/contracts/voting/neutron-vault/README.md @@ -0,0 +1,7 @@ +# CW Native Staked Balance Voting + +Simple native token voting contract which assumes the native denom +provided is not used for staking for securing the network e.g. IBC +denoms or secondary tokens (ION). Staked balances may be queried at an +arbitrary height. This contract implements the interface needed to be +a DAO DAO voting module. diff --git a/contracts/voting/neutron-vault/examples/schema.rs b/contracts/voting/neutron-vault/examples/schema.rs new file mode 100644 index 00000000..5d3a04db --- /dev/null +++ b/contracts/voting/neutron-vault/examples/schema.rs @@ -0,0 +1,35 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_std::Addr; +use cw_controllers::ClaimsResponse; +use cwd_interface::voting::{ + InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use neutron_vault::msg::{ExecuteMsg, InstantiateMsg, ListBondersResponse, MigrateMsg, QueryMsg}; +use neutron_vault::state::Config; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(MigrateMsg), &out_dir); + + export_schema(&schema_for!(InfoResponse), &out_dir); + export_schema(&schema_for!(TotalPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(VotingPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(IsActiveResponse), &out_dir); + export_schema(&schema_for!(ClaimsResponse), &out_dir); + export_schema(&schema_for!(ListBondersResponse), &out_dir); + + // Auto TS code generation expects the query return type as QueryNameResponse + // Here we map query resonses to the correct name + export_schema_with_title(&schema_for!(Addr), &out_dir, "DaoResponse"); + export_schema_with_title(&schema_for!(Config), &out_dir, "GetConfigResponse"); +} diff --git a/contracts/voting/neutron-vault/schema/claims_response.json b/contracts/voting/neutron-vault/schema/claims_response.json new file mode 100644 index 00000000..08211a3c --- /dev/null +++ b/contracts/voting/neutron-vault/schema/claims_response.json @@ -0,0 +1,95 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ClaimsResponse", + "type": "object", + "required": [ + "claims" + ], + "properties": { + "claims": { + "type": "array", + "items": { + "$ref": "#/definitions/Claim" + } + } + }, + "definitions": { + "Claim": { + "type": "object", + "required": [ + "amount", + "release_at" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "release_at": { + "$ref": "#/definitions/Expiration" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/voting/neutron-vault/schema/dao_response.json b/contracts/voting/neutron-vault/schema/dao_response.json new file mode 100644 index 00000000..9518ba3b --- /dev/null +++ b/contracts/voting/neutron-vault/schema/dao_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/voting/neutron-vault/schema/execute_msg.json b/contracts/voting/neutron-vault/schema/execute_msg.json new file mode 100644 index 00000000..bbb06adf --- /dev/null +++ b/contracts/voting/neutron-vault/schema/execute_msg.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "bond" + ], + "properties": { + "bond": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond" + ], + "properties": { + "unbond": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "manager": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/voting/neutron-vault/schema/get_config_response.json b/contracts/voting/neutron-vault/schema/get_config_response.json new file mode 100644 index 00000000..d3bc1acf --- /dev/null +++ b/contracts/voting/neutron-vault/schema/get_config_response.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetConfigResponse", + "type": "object", + "required": [ + "denom", + "description" + ], + "properties": { + "denom": { + "type": "string" + }, + "description": { + "type": "string" + }, + "manager": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "owner": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/voting/neutron-vault/schema/info_response.json b/contracts/voting/neutron-vault/schema/info_response.json new file mode 100644 index 00000000..a0516764 --- /dev/null +++ b/contracts/voting/neutron-vault/schema/info_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + } + } +} diff --git a/contracts/voting/neutron-vault/schema/instantiate_msg.json b/contracts/voting/neutron-vault/schema/instantiate_msg.json new file mode 100644 index 00000000..3637954e --- /dev/null +++ b/contracts/voting/neutron-vault/schema/instantiate_msg.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "denom", + "description" + ], + "properties": { + "denom": { + "type": "string" + }, + "description": { + "type": "string" + }, + "manager": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/voting/schema/instantiate_msg.json b/contracts/voting/neutron-vault/schema/is_active_response.json similarity index 50% rename from contracts/voting/schema/instantiate_msg.json rename to contracts/voting/neutron-vault/schema/is_active_response.json index 69238555..5275d891 100644 --- a/contracts/voting/schema/instantiate_msg.json +++ b/contracts/voting/neutron-vault/schema/is_active_response.json @@ -1,14 +1,13 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", + "title": "IsActiveResponse", "type": "object", "required": [ - "owner" + "active" ], "properties": { - "owner": { - "description": "The contract's owner", - "type": "string" + "active": { + "type": "boolean" } } } diff --git a/contracts/voting/neutron-vault/schema/list_bonders_response.json b/contracts/voting/neutron-vault/schema/list_bonders_response.json new file mode 100644 index 00000000..c4200c3f --- /dev/null +++ b/contracts/voting/neutron-vault/schema/list_bonders_response.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListBondersResponse", + "type": "object", + "required": [ + "bonders" + ], + "properties": { + "bonders": { + "type": "array", + "items": { + "$ref": "#/definitions/BonderBalanceResponse" + } + } + }, + "definitions": { + "BonderBalanceResponse": { + "type": "object", + "required": [ + "address", + "balance" + ], + "properties": { + "address": { + "type": "string" + }, + "balance": { + "$ref": "#/definitions/Uint128" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/voting/neutron-vault/schema/migrate_msg.json b/contracts/voting/neutron-vault/schema/migrate_msg.json new file mode 100644 index 00000000..87b18ea7 --- /dev/null +++ b/contracts/voting/neutron-vault/schema/migrate_msg.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object" +} diff --git a/contracts/voting/neutron-vault/schema/query_msg.json b/contracts/voting/neutron-vault/schema/query_msg.json new file mode 100644 index 00000000..3f634ef2 --- /dev/null +++ b/contracts/voting/neutron-vault/schema/query_msg.json @@ -0,0 +1,132 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "dao" + ], + "properties": { + "dao": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_config" + ], + "properties": { + "get_config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "list_bonders" + ], + "properties": { + "list_bonders": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_power_at_height" + ], + "properties": { + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_power_at_height" + ], + "properties": { + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/voting/neutron-vault/schema/total_power_at_height_response.json b/contracts/voting/neutron-vault/schema/total_power_at_height_response.json new file mode 100644 index 00000000..8018462b --- /dev/null +++ b/contracts/voting/neutron-vault/schema/total_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/voting/neutron-vault/schema/voting_power_at_height_response.json b/contracts/voting/neutron-vault/schema/voting_power_at_height_response.json new file mode 100644 index 00000000..15e986bf --- /dev/null +++ b/contracts/voting/neutron-vault/schema/voting_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/voting/neutron-vault/src/contract.rs b/contracts/voting/neutron-vault/src/contract.rs new file mode 100644 index 00000000..353e6165 --- /dev/null +++ b/contracts/voting/neutron-vault/src/contract.rs @@ -0,0 +1,296 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + coins, to_binary, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, + StdResult, Uint128, +}; +use cw2::set_contract_version; +use cw_utils::must_pay; +use cwd_interface::voting::{TotalPowerAtHeightResponse, VotingPowerAtHeightResponse}; +use cwd_interface::Admin; + +use crate::error::ContractError; +use crate::msg::{ + BonderBalanceResponse, ExecuteMsg, InstantiateMsg, ListBondersResponse, MigrateMsg, QueryMsg, +}; +use crate::state::{Config, BONDED_BALANCES, BONDED_TOTAL, CONFIG, DAO, DESCRIPTION}; + +pub(crate) const CONTRACT_NAME: &str = "crates.io:neutron-voting-registry"; +pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let owner = msg + .owner + .as_ref() + .map(|owner| match owner { + Admin::Address { addr } => deps.api.addr_validate(addr), + Admin::CoreModule {} => Ok(info.sender.clone()), + }) + .transpose()?; + let manager = msg + .manager + .map(|manager| deps.api.addr_validate(&manager)) + .transpose()?; + + let config = Config { + description: msg.description, + owner, + manager, + denom: msg.denom, + }; + + CONFIG.save(deps.storage, &config)?; + DAO.save(deps.storage, &info.sender)?; + + Ok(Response::new() + .add_attribute("action", "instantiate") + .add_attribute("description", config.description) + .add_attribute( + "owner", + config + .owner + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + ) + .add_attribute( + "manager", + config + .manager + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + )) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Bond {} => execute_bond(deps, env, info), + ExecuteMsg::Unbond { amount } => execute_unbond(deps, env, info, amount), + ExecuteMsg::UpdateConfig { + owner, + manager, + description, + } => execute_update_config(deps, info, owner, manager, description), + } +} + +pub fn execute_bond(deps: DepsMut, env: Env, info: MessageInfo) -> Result { + let config = CONFIG.load(deps.storage)?; + let amount = must_pay(&info, &config.denom)?; + + BONDED_BALANCES.update( + deps.storage, + &info.sender, + env.block.height, + |balance| -> StdResult { Ok(balance.unwrap_or_default().checked_add(amount)?) }, + )?; + BONDED_TOTAL.update( + deps.storage, + env.block.height, + |total| -> StdResult { Ok(total.unwrap_or_default().checked_add(amount)?) }, + )?; + + Ok(Response::new() + .add_attribute("action", "bond") + .add_attribute("amount", amount.to_string()) + .add_attribute("from", info.sender)) +} + +pub fn execute_unbond( + deps: DepsMut, + env: Env, + info: MessageInfo, + amount: Uint128, +) -> Result { + let config = CONFIG.load(deps.storage)?; + + BONDED_BALANCES.update( + deps.storage, + &info.sender, + env.block.height, + |balance| -> Result { + balance + .unwrap_or_default() + .checked_sub(amount) + .map_err(|_e| ContractError::InvalidUnbondAmount {}) + }, + )?; + BONDED_TOTAL.update( + deps.storage, + env.block.height, + |total| -> Result { + total + .unwrap_or_default() + .checked_sub(amount) + .map_err(|_e| ContractError::InvalidUnbondAmount {}) + }, + )?; + + let msg = CosmosMsg::Bank(BankMsg::Send { + to_address: info.sender.to_string(), + amount: coins(amount.u128(), config.denom), + }); + Ok(Response::new() + .add_message(msg) + .add_attribute("action", "unbond") + .add_attribute("from", info.sender) + .add_attribute("amount", amount) + .add_attribute("claim_duration", "None")) +} + +pub fn execute_update_config( + deps: DepsMut, + info: MessageInfo, + new_owner: Option, + new_manager: Option, + new_description: Option, +) -> Result { + let mut config: Config = CONFIG.load(deps.storage)?; + if Some(info.sender.clone()) != config.owner && Some(info.sender.clone()) != config.manager { + return Err(ContractError::Unauthorized {}); + } + + let new_owner = new_owner + .map(|new_owner| deps.api.addr_validate(&new_owner)) + .transpose()?; + let new_manager = new_manager + .map(|new_manager| deps.api.addr_validate(&new_manager)) + .transpose()?; + + if Some(info.sender) != config.owner && new_owner != config.owner { + return Err(ContractError::OnlyOwnerCanChangeOwner {}); + }; + + config.owner = new_owner; + config.manager = new_manager; + if let Some(description) = new_description { + config.description = description; + } + + CONFIG.save(deps.storage, &config)?; + Ok(Response::new() + .add_attribute("action", "update_config") + .add_attribute("description", config.description) + .add_attribute( + "owner", + config + .owner + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + ) + .add_attribute( + "manager", + config + .manager + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + )) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::VotingPowerAtHeight { address, height } => { + to_binary(&query_voting_power_at_height(deps, env, address, height)?) + } + QueryMsg::TotalPowerAtHeight { height } => { + to_binary(&query_total_power_at_height(deps, env, height)?) + } + QueryMsg::Info {} => query_info(deps), + QueryMsg::Dao {} => query_dao(deps), + QueryMsg::Description {} => query_description(deps), + QueryMsg::GetConfig {} => to_binary(&CONFIG.load(deps.storage)?), + QueryMsg::ListBonders { start_after, limit } => { + query_list_bonders(deps, start_after, limit) + } + } +} + +pub fn query_voting_power_at_height( + deps: Deps, + env: Env, + address: String, + height: Option, +) -> StdResult { + let height = height.unwrap_or(env.block.height); + let address = deps.api.addr_validate(&address)?; + let power = BONDED_BALANCES + .may_load_at_height(deps.storage, &address, height)? + .unwrap_or_default(); + Ok(VotingPowerAtHeightResponse { power, height }) +} + +pub fn query_total_power_at_height( + deps: Deps, + env: Env, + height: Option, +) -> StdResult { + let height = height.unwrap_or(env.block.height); + let power = BONDED_TOTAL + .may_load_at_height(deps.storage, height)? + .unwrap_or_default(); + Ok(TotalPowerAtHeightResponse { power, height }) +} + +pub fn query_info(deps: Deps) -> StdResult { + let info = cw2::get_contract_version(deps.storage)?; + to_binary(&cwd_interface::voting::InfoResponse { info }) +} + +pub fn query_dao(deps: Deps) -> StdResult { + let dao = DAO.load(deps.storage)?; + to_binary(&dao) +} + +pub fn query_description(deps: Deps) -> StdResult { + let description = DESCRIPTION.load(deps.storage)?; + to_binary(&description) +} + +pub fn query_list_bonders( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + let start_at = start_after + .map(|addr| deps.api.addr_validate(&addr)) + .transpose()?; + + let bonders = cw_paginate::paginate_snapshot_map( + deps, + &BONDED_BALANCES, + start_at.as_ref(), + limit, + cosmwasm_std::Order::Ascending, + )?; + + let bonders = bonders + .into_iter() + .map(|(address, balance)| BonderBalanceResponse { + address: address.into_string(), + balance, + }) + .collect(); + + to_binary(&ListBondersResponse { bonders }) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::default()) +} diff --git a/contracts/voting/neutron-vault/src/error.rs b/contracts/voting/neutron-vault/src/error.rs new file mode 100644 index 00000000..70d1aa76 --- /dev/null +++ b/contracts/voting/neutron-vault/src/error.rs @@ -0,0 +1,24 @@ +use cosmwasm_std::StdError; +use cw_utils::PaymentError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Nothing to claim")] + NothingToClaim {}, + + #[error("Only owner can change owner")] + OnlyOwnerCanChangeOwner {}, + + #[error("Can only unbond less than or equal to the amount you have bonded")] + InvalidUnbondAmount {}, +} diff --git a/contracts/voting/neutron-vault/src/lib.rs b/contracts/voting/neutron-vault/src/lib.rs new file mode 100644 index 00000000..8e58a2f1 --- /dev/null +++ b/contracts/voting/neutron-vault/src/lib.rs @@ -0,0 +1,9 @@ +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +#[cfg(test)] +mod tests; + +pub use crate::error::ContractError; diff --git a/contracts/voting/neutron-vault/src/msg.rs b/contracts/voting/neutron-vault/src/msg.rs new file mode 100644 index 00000000..4afe79b3 --- /dev/null +++ b/contracts/voting/neutron-vault/src/msg.rs @@ -0,0 +1,61 @@ +use cosmwasm_std::Uint128; +use cwd_interface::Admin; +use cwd_macros::{info_query, voting_query}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +pub struct InstantiateMsg { + // Description contains information that characterizes the vault. + pub description: String, + // Owner can update all configs including changing the owner. This will generally be a DAO. + pub owner: Option, + // Manager can update all configs except changing the owner. This will generally be an operations multisig for a DAO. + pub manager: Option, + // Token denom e.g. untrn, or some ibc denom + pub denom: String, +} + +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + Bond {}, + Unbond { + amount: Uint128, + }, + UpdateConfig { + owner: Option, + manager: Option, + description: Option, + }, +} + +#[voting_query] +#[info_query] +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + Dao {}, + Description {}, + GetConfig {}, + ListBonders { + start_after: Option, + limit: Option, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MigrateMsg {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct ListBondersResponse { + pub bonders: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct BonderBalanceResponse { + pub address: String, + pub balance: Uint128, +} diff --git a/contracts/voting/neutron-vault/src/state.rs b/contracts/voting/neutron-vault/src/state.rs new file mode 100644 index 00000000..733e3bdb --- /dev/null +++ b/contracts/voting/neutron-vault/src/state.rs @@ -0,0 +1,29 @@ +use cosmwasm_std::{Addr, Uint128}; +use cw_storage_plus::{Item, SnapshotItem, SnapshotMap, Strategy}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct Config { + pub description: String, + pub owner: Option, + pub manager: Option, + pub denom: String, +} + +pub const CONFIG: Item = Item::new("config"); +pub const DAO: Item = Item::new("dao"); +pub const DESCRIPTION: Item = Item::new("description"); +pub const BONDED_BALANCES: SnapshotMap<&Addr, Uint128> = SnapshotMap::new( + "bonded_balances", + "bonded_balance__checkpoints", + "bonded_balance__changelog", + Strategy::EveryBlock, +); + +pub const BONDED_TOTAL: SnapshotItem = SnapshotItem::new( + "total_bonded", + "total_bonded__checkpoints", + "total_bonded__changelog", + Strategy::EveryBlock, +); diff --git a/contracts/voting/neutron-vault/src/tests.rs b/contracts/voting/neutron-vault/src/tests.rs new file mode 100644 index 00000000..d9aa78f2 --- /dev/null +++ b/contracts/voting/neutron-vault/src/tests.rs @@ -0,0 +1,759 @@ +use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; +use crate::msg::{ + BonderBalanceResponse, ExecuteMsg, InstantiateMsg, ListBondersResponse, MigrateMsg, QueryMsg, +}; +use crate::state::Config; +use cosmwasm_std::testing::{mock_dependencies, mock_env}; +use cosmwasm_std::{coins, Addr, Coin, Empty, Uint128}; +use cw_multi_test::{ + custom_app, next_block, App, AppResponse, Contract, ContractWrapper, Executor, +}; +use cwd_interface::voting::{ + InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use cwd_interface::Admin; + +const DAO_ADDR: &str = "dao"; +const DESCRIPTION: &str = "description"; +const NEW_DESCRIPTION: &str = "new description"; +const ADDR1: &str = "addr1"; +const ADDR2: &str = "addr2"; +const DENOM: &str = "ujuno"; +const INVALID_DENOM: &str = "uinvalid"; +const INIT_BALANCE: Uint128 = Uint128::new(10000); + +fn vault_contract() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) +} + +fn mock_app() -> App { + custom_app(|r, _a, s| { + r.bank + .init_balance( + s, + &Addr::unchecked(DAO_ADDR), + vec![ + Coin { + denom: DENOM.to_string(), + amount: INIT_BALANCE, + }, + Coin { + denom: INVALID_DENOM.to_string(), + amount: INIT_BALANCE, + }, + ], + ) + .unwrap(); + r.bank + .init_balance( + s, + &Addr::unchecked(ADDR1), + vec![ + Coin { + denom: DENOM.to_string(), + amount: INIT_BALANCE, + }, + Coin { + denom: INVALID_DENOM.to_string(), + amount: INIT_BALANCE, + }, + ], + ) + .unwrap(); + r.bank + .init_balance( + s, + &Addr::unchecked(ADDR2), + vec![ + Coin { + denom: DENOM.to_string(), + amount: INIT_BALANCE, + }, + Coin { + denom: INVALID_DENOM.to_string(), + amount: INIT_BALANCE, + }, + ], + ) + .unwrap(); + }) +} + +fn instantiate_vault(app: &mut App, id: u64, msg: InstantiateMsg) -> Addr { + app.instantiate_contract(id, Addr::unchecked(DAO_ADDR), &msg, &[], "vault", None) + .unwrap() +} + +fn bond_tokens( + app: &mut App, + contract_addr: Addr, + sender: &str, + amount: u128, + denom: &str, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::Bond {}, + &coins(amount, denom), + ) +} + +fn unbond_tokens( + app: &mut App, + contract_addr: Addr, + sender: &str, + amount: u128, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::Unbond { + amount: Uint128::new(amount), + }, + &[], + ) +} + +fn update_config( + app: &mut App, + contract_addr: Addr, + sender: &str, + owner: Option, + manager: Option, + description: Option, +) -> anyhow::Result { + app.execute_contract( + Addr::unchecked(sender), + contract_addr, + &ExecuteMsg::UpdateConfig { + owner, + manager, + description, + }, + &[], + ) +} + +fn get_voting_power_at_height( + app: &mut App, + contract_addr: Addr, + address: String, + height: Option, +) -> VotingPowerAtHeightResponse { + app.wrap() + .query_wasm_smart( + contract_addr, + &QueryMsg::VotingPowerAtHeight { address, height }, + ) + .unwrap() +} + +fn get_total_power_at_height( + app: &mut App, + contract_addr: Addr, + height: Option, +) -> TotalPowerAtHeightResponse { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::TotalPowerAtHeight { height }) + .unwrap() +} + +fn get_config(app: &mut App, contract_addr: Addr) -> Config { + app.wrap() + .query_wasm_smart(contract_addr, &QueryMsg::GetConfig {}) + .unwrap() +} + +fn get_balance(app: &mut App, address: &str, denom: &str) -> Uint128 { + app.wrap().query_balance(address, denom).unwrap().amount +} + +#[test] +fn test_instantiate() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + // Populated fields + let _addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::Address { + addr: DAO_ADDR.to_string(), + }), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // Non populated fields + let _addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: None, + manager: None, + denom: DENOM.to_string(), + }, + ); +} + +#[test] +fn test_instantiate_dao_owner() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + // Populated fields + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + let config = get_config(&mut app, addr); + + assert_eq!(config.owner, Some(Addr::unchecked(DAO_ADDR))) +} + +#[test] +#[should_panic(expected = "Must send reserve token 'ujuno'")] +fn test_bond_invalid_denom() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // Try and bond an invalid denom + bond_tokens(&mut app, addr, ADDR1, 100, INVALID_DENOM).unwrap(); +} + +#[test] +fn test_bond_valid_denom() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // Try and bond an valid denom + bond_tokens(&mut app, addr, ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); +} + +#[test] +#[should_panic(expected = "Can only unbond less than or equal to the amount you have bonded")] +fn test_unbond_none_bonded() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + unbond_tokens(&mut app, addr, ADDR1, 100).unwrap(); +} + +#[test] +#[should_panic(expected = "Can only unbond less than or equal to the amount you have bonded")] +fn test_unbond_invalid_balance() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // bond some tokens + bond_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + + // Try and unbond too many + unbond_tokens(&mut app, addr, ADDR1, 200).unwrap(); +} + +#[test] +fn test_unbond() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + assert_eq!(get_balance(&mut app, ADDR1, DENOM), INIT_BALANCE); + // bond some tokens + bond_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + assert_eq!(get_balance(&mut app, ADDR1, DENOM), Uint128::new(9900)); + + // Unbond some + unbond_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); + assert_eq!(get_balance(&mut app, ADDR1, DENOM), Uint128::new(9975)); + + // Unbond the rest + unbond_tokens(&mut app, addr, ADDR1, 25).unwrap(); + assert_eq!(get_balance(&mut app, ADDR1, DENOM), INIT_BALANCE); +} + +#[test] +#[should_panic(expected = "Unauthorized")] +fn test_update_config_invalid_sender() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // From ADDR2, so not owner or manager + update_config( + &mut app, + addr, + ADDR2, + Some(ADDR1.to_string()), + Some(DAO_ADDR.to_string()), + Some(NEW_DESCRIPTION.to_string()), + ) + .unwrap(); +} + +#[test] +#[should_panic(expected = "Only owner can change owner")] +fn test_update_config_non_owner_changes_owner() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // ADDR1 is the manager so cannot change the owner + update_config(&mut app, addr, ADDR1, Some(ADDR2.to_string()), None, None).unwrap(); +} + +#[test] +fn test_update_config_as_owner() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // Swap owner and manager, change description + update_config( + &mut app, + addr.clone(), + DAO_ADDR, + Some(ADDR1.to_string()), + Some(DAO_ADDR.to_string()), + Some(NEW_DESCRIPTION.to_string()), + ) + .unwrap(); + + let config = get_config(&mut app, addr); + assert_eq!( + Config { + description: NEW_DESCRIPTION.to_string(), + owner: Some(Addr::unchecked(ADDR1)), + manager: Some(Addr::unchecked(DAO_ADDR)), + denom: DENOM.to_string(), + }, + config + ); +} + +#[test] +fn test_update_config_as_manager() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // Change description and manager as manager cannot change owner + update_config( + &mut app, + addr.clone(), + ADDR1, + Some(DAO_ADDR.to_string()), + Some(ADDR2.to_string()), + Some(NEW_DESCRIPTION.to_string()), + ) + .unwrap(); + + let config = get_config(&mut app, addr); + assert_eq!( + Config { + description: NEW_DESCRIPTION.to_string(), + owner: Some(Addr::unchecked(DAO_ADDR)), + manager: Some(Addr::unchecked(ADDR2)), + denom: DENOM.to_string(), + }, + config + ); +} + +#[test] +#[should_panic(expected = "Empty attribute value. Key: description")] +fn test_update_config_invalid_description() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // Change duration and manager as manager cannot change owner + update_config( + &mut app, + addr, + ADDR1, + Some(DAO_ADDR.to_string()), + Some(ADDR2.to_string()), + Some(String::from("")), + ) + .unwrap(); +} + +#[test] +fn test_query_dao() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + let msg = QueryMsg::Dao {}; + let dao: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!(dao, Addr::unchecked(DAO_ADDR)); +} + +#[test] +fn test_query_info() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + let msg = QueryMsg::Info {}; + let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); + assert_eq!(resp.info.contract, "crates.io:neutron-voting-registry"); +} + +#[test] +fn test_query_get_config() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + let config = get_config(&mut app, addr); + assert_eq!( + config, + Config { + description: DESCRIPTION.to_string(), + owner: Some(Addr::unchecked(DAO_ADDR)), + manager: Some(Addr::unchecked(ADDR1)), + denom: DENOM.to_string(), + } + ) +} + +#[test] +fn test_voting_power_queries() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // Total power is 0 + let resp = get_total_power_at_height(&mut app, addr.clone(), None); + assert!(resp.power.is_zero()); + + // ADDR1 has no power, none bonded + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); + assert!(resp.power.is_zero()); + + // ADDR1 bonds + bond_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + app.update_block(next_block); + + // Total power is 100 + let resp = get_total_power_at_height(&mut app, addr.clone(), None); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR1 has 100 power + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR2 still has 0 power + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); + assert!(resp.power.is_zero()); + + // ADDR2 bonds + bond_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); + app.update_block(next_block); + let prev_height = app.block_info().height - 1; + + // Query the previous height, total 100, ADDR1 100, ADDR2 0 + // Total power is 100 + let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR1 has 100 power + let resp = + get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR2 still has 0 power + let resp = + get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); + assert!(resp.power.is_zero()); + + // For current height, total 150, ADDR1 100, ADDR2 50 + // Total power is 150 + let resp = get_total_power_at_height(&mut app, addr.clone(), None); + assert_eq!(resp.power, Uint128::new(150)); + + // ADDR1 has 100 power + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR2 now has 50 power + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); + assert_eq!(resp.power, Uint128::new(50)); + + // ADDR1 unbonds half + unbond_tokens(&mut app, addr.clone(), ADDR1, 50).unwrap(); + app.update_block(next_block); + let prev_height = app.block_info().height - 1; + + // Query the previous height, total 150, ADDR1 100, ADDR2 50 + // Total power is 100 + let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); + assert_eq!(resp.power, Uint128::new(150)); + + // ADDR1 has 100 power + let resp = + get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR2 still has 0 power + let resp = + get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); + assert_eq!(resp.power, Uint128::new(50)); + + // For current height, total 100, ADDR1 50, ADDR2 50 + // Total power is 100 + let resp = get_total_power_at_height(&mut app, addr.clone(), None); + assert_eq!(resp.power, Uint128::new(100)); + + // ADDR1 has 50 power + let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); + assert_eq!(resp.power, Uint128::new(50)); + + // ADDR2 now has 50 power + let resp = get_voting_power_at_height(&mut app, addr, ADDR2.to_string(), None); + assert_eq!(resp.power, Uint128::new(50)); +} + +#[test] +fn test_query_list_bonders() { + let mut app = mock_app(); + let vault_id = app.store_code(vault_contract()); + let addr = instantiate_vault( + &mut app, + vault_id, + InstantiateMsg { + description: DESCRIPTION.to_string(), + owner: Some(Admin::CoreModule {}), + manager: Some(ADDR1.to_string()), + denom: DENOM.to_string(), + }, + ); + + // ADDR1 bonds + bond_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); + + // ADDR2 bonds + bond_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); + + // check entire result set + let bonders: ListBondersResponse = app + .wrap() + .query_wasm_smart( + addr.clone(), + &QueryMsg::ListBonders { + start_after: None, + limit: None, + }, + ) + .unwrap(); + + let test_res = ListBondersResponse { + bonders: vec![ + BonderBalanceResponse { + address: ADDR1.to_string(), + balance: Uint128::new(100), + }, + BonderBalanceResponse { + address: ADDR2.to_string(), + balance: Uint128::new(50), + }, + ], + }; + + assert_eq!(bonders, test_res); + + // skipped 1, check result + let bonders: ListBondersResponse = app + .wrap() + .query_wasm_smart( + addr.clone(), + &QueryMsg::ListBonders { + start_after: Some(ADDR1.to_string()), + limit: None, + }, + ) + .unwrap(); + + let test_res = ListBondersResponse { + bonders: vec![BonderBalanceResponse { + address: ADDR2.to_string(), + balance: Uint128::new(50), + }], + }; + + assert_eq!(bonders, test_res); + + // skipped 2, check result. should be nothing + let bonders: ListBondersResponse = app + .wrap() + .query_wasm_smart( + addr, + &QueryMsg::ListBonders { + start_after: Some(ADDR2.to_string()), + limit: None, + }, + ) + .unwrap(); + + assert_eq!(bonders, ListBondersResponse { bonders: vec![] }); +} + +#[test] +pub fn test_migrate_update_version() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); + migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); + let version = cw2::get_contract_version(&deps.storage).unwrap(); + assert_eq!(version.version, CONTRACT_VERSION); + assert_eq!(version.contract, CONTRACT_NAME); +} diff --git a/contracts/voting/neutron-voting-registry/.cargo/config b/contracts/voting/neutron-voting-registry/.cargo/config new file mode 100644 index 00000000..336b618a --- /dev/null +++ b/contracts/voting/neutron-voting-registry/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/voting/neutron-voting-registry/.gitignore b/contracts/voting/neutron-voting-registry/.gitignore new file mode 100644 index 00000000..dfdaaa6b --- /dev/null +++ b/contracts/voting/neutron-voting-registry/.gitignore @@ -0,0 +1,15 @@ +# Build results +/target + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/contracts/voting/neutron-voting-registry/Cargo.toml b/contracts/voting/neutron-voting-registry/Cargo.toml new file mode 100644 index 00000000..41423f06 --- /dev/null +++ b/contracts/voting/neutron-voting-registry/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "neutron-voting-registry" +version = "0.2.0" +authors = ["Callum Anderson "] +edition = "2021" +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A DAO DAO voting module based on staked cw721 tokens." + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-std = { version = "1.0.0" } +cosmwasm-storage = { version = "1.0.0" } +cw-storage-plus = "0.13" +cw2 = "0.13" +cw-utils = "0.13" +cw-controllers = "0.13" +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +neutron-vault = { path = "../neutron-vault" } +cwd-macros = { path = "../../../packages/cwd-macros" } +cwd-interface = { path = "../../../packages/cwd-interface" } +cw-paginate = { path = "../../../packages/cw-paginate" } + +[dev-dependencies] +cosmwasm-schema = { version = "1.0.0" } +cw-multi-test = "0.13" +anyhow = "1.0.57" diff --git a/contracts/voting/neutron-voting-registry/README.md b/contracts/voting/neutron-voting-registry/README.md new file mode 100644 index 00000000..bc25443c --- /dev/null +++ b/contracts/voting/neutron-voting-registry/README.md @@ -0,0 +1,7 @@ +# CW Native Staked Balance Voting + +Simple native token voting contract which assumes the native denom +provided is not used for staking for securing the network e.g. IBC +denoms or secondary tokens (ION). Staked balances may be queried at an +arbitrary height. This contract implements the interface needed to be +a DAO DAO voting module. diff --git a/contracts/voting/neutron-voting-registry/examples/schema.rs b/contracts/voting/neutron-voting-registry/examples/schema.rs new file mode 100644 index 00000000..2d0112ff --- /dev/null +++ b/contracts/voting/neutron-voting-registry/examples/schema.rs @@ -0,0 +1,34 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_std::Addr; +use cw_controllers::ClaimsResponse; +use cwd_interface::voting::{ + InfoResponse, IsActiveResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; +use neutron_voting_registry::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use neutron_voting_registry::state::Config; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema(&schema_for!(ExecuteMsg), &out_dir); + export_schema(&schema_for!(QueryMsg), &out_dir); + export_schema(&schema_for!(MigrateMsg), &out_dir); + + export_schema(&schema_for!(InfoResponse), &out_dir); + export_schema(&schema_for!(TotalPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(VotingPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(IsActiveResponse), &out_dir); + export_schema(&schema_for!(ClaimsResponse), &out_dir); + + // Auto TS code generation expects the query return type as QueryNameResponse + // Here we map query resonses to the correct name + export_schema_with_title(&schema_for!(Addr), &out_dir, "DaoResponse"); + export_schema_with_title(&schema_for!(Config), &out_dir, "GetConfigResponse"); +} diff --git a/contracts/voting/neutron-voting-registry/schema/claims_response.json b/contracts/voting/neutron-voting-registry/schema/claims_response.json new file mode 100644 index 00000000..08211a3c --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/claims_response.json @@ -0,0 +1,95 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ClaimsResponse", + "type": "object", + "required": [ + "claims" + ], + "properties": { + "claims": { + "type": "array", + "items": { + "$ref": "#/definitions/Claim" + } + } + }, + "definitions": { + "Claim": { + "type": "object", + "required": [ + "amount", + "release_at" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "release_at": { + "$ref": "#/definitions/Expiration" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/voting/neutron-voting-registry/schema/dao_response.json b/contracts/voting/neutron-voting-registry/schema/dao_response.json new file mode 100644 index 00000000..9518ba3b --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/dao_response.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DaoResponse", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/contracts/voting/neutron-voting-registry/schema/execute_msg.json b/contracts/voting/neutron-voting-registry/schema/execute_msg.json new file mode 100644 index 00000000..9586090f --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/execute_msg.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "add_voting_vault" + ], + "properties": { + "add_voting_vault": { + "type": "object", + "required": [ + "new_voting_vault_contract" + ], + "properties": { + "new_voting_vault_contract": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "remove_voting_vault" + ], + "properties": { + "remove_voting_vault": { + "type": "object", + "required": [ + "old_voting_vault_contract" + ], + "properties": { + "old_voting_vault_contract": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "manager": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/voting/neutron-voting-registry/schema/get_config_response.json b/contracts/voting/neutron-voting-registry/schema/get_config_response.json new file mode 100644 index 00000000..4278fdac --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/get_config_response.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetConfigResponse", + "type": "object", + "required": [ + "voting_vaults" + ], + "properties": { + "manager": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "owner": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "voting_vaults": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/contracts/voting/neutron-voting-registry/schema/info_response.json b/contracts/voting/neutron-voting-registry/schema/info_response.json new file mode 100644 index 00000000..a0516764 --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/info_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InfoResponse", + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "$ref": "#/definitions/ContractVersion" + } + }, + "definitions": { + "ContractVersion": { + "type": "object", + "required": [ + "contract", + "version" + ], + "properties": { + "contract": { + "description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing", + "type": "string" + }, + "version": { + "description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)", + "type": "string" + } + } + } + } +} diff --git a/contracts/voting/neutron-voting-registry/schema/instantiate_msg.json b/contracts/voting/neutron-voting-registry/schema/instantiate_msg.json new file mode 100644 index 00000000..405ea49f --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/instantiate_msg.json @@ -0,0 +1,70 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "voting_vault" + ], + "properties": { + "manager": { + "type": [ + "string", + "null" + ] + }, + "owner": { + "anyOf": [ + { + "$ref": "#/definitions/Admin" + }, + { + "type": "null" + } + ] + }, + "voting_vault": { + "type": "string" + } + }, + "definitions": { + "Admin": { + "description": "Information about the CosmWasm level admin of a contract. Used in conjunction with `ModuleInstantiateInfo` to instantiate modules.", + "oneOf": [ + { + "description": "Set the admin to a specified address.", + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "object", + "required": [ + "addr" + ], + "properties": { + "addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets the admin as the core module address.", + "type": "object", + "required": [ + "core_module" + ], + "properties": { + "core_module": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/voting/neutron-voting-registry/schema/is_active_response.json b/contracts/voting/neutron-voting-registry/schema/is_active_response.json new file mode 100644 index 00000000..5275d891 --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/is_active_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsActiveResponse", + "type": "object", + "required": [ + "active" + ], + "properties": { + "active": { + "type": "boolean" + } + } +} diff --git a/contracts/voting/neutron-voting-registry/schema/migrate_msg.json b/contracts/voting/neutron-voting-registry/schema/migrate_msg.json new file mode 100644 index 00000000..87b18ea7 --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/migrate_msg.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object" +} diff --git a/contracts/voting/neutron-voting-registry/schema/query_msg.json b/contracts/voting/neutron-voting-registry/schema/query_msg.json new file mode 100644 index 00000000..e84167b9 --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/query_msg.json @@ -0,0 +1,104 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "dao" + ], + "properties": { + "dao": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_config" + ], + "properties": { + "get_config": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_vaults" + ], + "properties": { + "voting_vaults": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "voting_power_at_height" + ], + "properties": { + "voting_power_at_height": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "total_power_at_height" + ], + "properties": { + "total_power_at_height": { + "type": "object", + "properties": { + "height": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "info" + ], + "properties": { + "info": { + "type": "object" + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/voting/neutron-voting-registry/schema/total_power_at_height_response.json b/contracts/voting/neutron-voting-registry/schema/total_power_at_height_response.json new file mode 100644 index 00000000..8018462b --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/total_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TotalPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/voting/neutron-voting-registry/schema/voting_power_at_height_response.json b/contracts/voting/neutron-voting-registry/schema/voting_power_at_height_response.json new file mode 100644 index 00000000..15e986bf --- /dev/null +++ b/contracts/voting/neutron-voting-registry/schema/voting_power_at_height_response.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotingPowerAtHeightResponse", + "type": "object", + "required": [ + "height", + "power" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "power": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/voting/neutron-voting-registry/src/contract.rs b/contracts/voting/neutron-voting-registry/src/contract.rs new file mode 100644 index 00000000..c5b594b4 --- /dev/null +++ b/contracts/voting/neutron-voting-registry/src/contract.rs @@ -0,0 +1,274 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::{ + entry_point, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, +}; +use cw2::set_contract_version; +// use cw_controllers::ClaimsResponse; +use cwd_interface::voting::{TotalPowerAtHeightResponse, VotingPowerAtHeightResponse}; +use cwd_interface::{voting, Admin}; +use neutron_vault::msg::QueryMsg as VaultQueryMsg; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use crate::state::{Config, CONFIG, DAO}; + +pub(crate) const CONTRACT_NAME: &str = "crates.io:neutron-voting-registry"; +pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let owner = msg + .owner + .as_ref() + .map(|owner| match owner { + Admin::Address { addr } => deps.api.addr_validate(addr), + Admin::CoreModule {} => Ok(info.sender.clone()), + }) + .transpose()?; + let manager = msg + .manager + .map(|manager| deps.api.addr_validate(&manager)) + .transpose()?; + + let voting_vault = deps.api.addr_validate(&msg.voting_vault)?; + + let config = Config { + owner, + manager, + voting_vaults: vec![voting_vault], + }; + + CONFIG.save(deps.storage, &config)?; + DAO.save(deps.storage, &info.sender)?; + + Ok(Response::new() + .add_attribute("action", "instantiate") + .add_attribute( + "owner", + config + .owner + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + ) + .add_attribute( + "manager", + config + .manager + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + )) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::AddVotingVault { + new_voting_vault_contract, + } => execute_add_voting_vault(deps, env, info, new_voting_vault_contract), + ExecuteMsg::RemoveVotingVault { + old_voting_vault_contract, + } => execute_remove_voting_vault(deps, env, info, old_voting_vault_contract), + ExecuteMsg::UpdateConfig { owner, manager } => { + execute_update_config(deps, info, owner, manager) + } + } +} + +pub fn execute_add_voting_vault( + deps: DepsMut, + _env: Env, + info: MessageInfo, + new_voting_vault_contact: String, +) -> Result { + let mut config: Config = CONFIG.load(deps.storage)?; + + if Some(info.sender) != config.owner { + return Err(ContractError::Unauthorized {}); + } + + let new_voting_vault = deps.api.addr_validate(&new_voting_vault_contact)?; + if !config.voting_vaults.contains(&new_voting_vault) { + config.voting_vaults.push(new_voting_vault); + CONFIG.save(deps.storage, &config)?; + } else { + return Err(ContractError::VotingVaultAlreadyExists {}); + } + + Ok(Response::new()) +} + +pub fn execute_remove_voting_vault( + deps: DepsMut, + _env: Env, + info: MessageInfo, + old_voting_vault_contact: String, +) -> Result { + let mut config: Config = CONFIG.load(deps.storage)?; + + if Some(info.sender) != config.owner { + return Err(ContractError::Unauthorized {}); + } + + if config.voting_vaults.len() == 1 { + return Err(ContractError::RemoveLastVault {}); + } + + let new_voting_vault = deps.api.addr_validate(&old_voting_vault_contact)?; + if config.voting_vaults.contains(&new_voting_vault) { + config + .voting_vaults + .retain(|value| *value != old_voting_vault_contact); + CONFIG.save(deps.storage, &config)?; + } + + Ok(Response::new()) +} + +pub fn execute_update_config( + deps: DepsMut, + info: MessageInfo, + new_owner: Option, + new_manager: Option, +) -> Result { + let mut config: Config = CONFIG.load(deps.storage)?; + if Some(info.sender.clone()) != config.owner && Some(info.sender.clone()) != config.manager { + return Err(ContractError::Unauthorized {}); + } + + let new_owner = new_owner + .map(|new_owner| deps.api.addr_validate(&new_owner)) + .transpose()?; + let new_manager = new_manager + .map(|new_manager| deps.api.addr_validate(&new_manager)) + .transpose()?; + + if Some(info.sender) != config.owner && new_owner != config.owner { + return Err(ContractError::OnlyOwnerCanChangeOwner {}); + }; + + config.owner = new_owner; + config.manager = new_manager; + + CONFIG.save(deps.storage, &config)?; + Ok(Response::new() + .add_attribute("action", "update_config") + .add_attribute( + "owner", + config + .owner + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + ) + .add_attribute( + "manager", + config + .manager + .map(|a| a.to_string()) + .unwrap_or_else(|| "None".to_string()), + )) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::VotingPowerAtHeight { address, height } => { + to_binary(&query_voting_power_at_height(deps, env, address, height)?) + } + QueryMsg::TotalPowerAtHeight { height } => { + to_binary(&query_total_power_at_height(deps, env, height)?) + } + QueryMsg::Info {} => query_info(deps), + QueryMsg::Dao {} => query_dao(deps), + QueryMsg::GetConfig {} => to_binary(&CONFIG.load(deps.storage)?), + QueryMsg::VotingVaults {} => query_voting_vaults(deps), + } +} + +pub fn query_voting_vaults(deps: Deps) -> StdResult { + let mut voting_vaults: Vec<(Addr, String)> = vec![]; + for vault in CONFIG.load(deps.storage)?.voting_vaults.iter() { + let vault_description: String = deps + .querier + .query_wasm_smart(vault, &VaultQueryMsg::Description {})?; + voting_vaults.push((vault.clone(), vault_description)); + } + + to_binary(&voting_vaults) +} + +pub fn query_voting_power_at_height( + deps: Deps, + _env: Env, + address: String, + height: Option, +) -> StdResult { + let voting_vaults = CONFIG.load(deps.storage)?.voting_vaults; + let mut total_power = VotingPowerAtHeightResponse { + power: Default::default(), + height: Default::default(), + }; + for vault in voting_vaults.iter() { + let total_power_single_vault: VotingPowerAtHeightResponse = deps.querier.query_wasm_smart( + vault, + &voting::Query::VotingPowerAtHeight { + height, + address: address.clone(), + }, + )?; + total_power.power += total_power_single_vault.power; + total_power.height = total_power_single_vault.height; + } + + Ok(total_power) +} + +pub fn query_total_power_at_height( + deps: Deps, + _env: Env, + height: Option, +) -> StdResult { + let voting_vaults = CONFIG.load(deps.storage)?.voting_vaults; + + let mut total_power: TotalPowerAtHeightResponse = TotalPowerAtHeightResponse { + power: Default::default(), + height: Default::default(), + }; + for vault in voting_vaults.iter() { + let total_power_single_vault: TotalPowerAtHeightResponse = deps + .querier + .query_wasm_smart(vault, &voting::Query::TotalPowerAtHeight { height })?; + total_power.power += total_power_single_vault.power; + total_power.height = total_power_single_vault.height; + } + + Ok(total_power) +} + +pub fn query_info(deps: Deps) -> StdResult { + let info = cw2::get_contract_version(deps.storage)?; + to_binary(&voting::InfoResponse { info }) +} + +pub fn query_dao(deps: Deps) -> StdResult { + let dao = DAO.load(deps.storage)?; + to_binary(&dao) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + // Set contract to version to latest + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::default()) +} diff --git a/contracts/voting/neutron-voting-registry/src/error.rs b/contracts/voting/neutron-voting-registry/src/error.rs new file mode 100644 index 00000000..ea4ca3f1 --- /dev/null +++ b/contracts/voting/neutron-voting-registry/src/error.rs @@ -0,0 +1,24 @@ +use cosmwasm_std::StdError; +use cw_utils::PaymentError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Only owner can change owner")] + OnlyOwnerCanChangeOwner {}, + + #[error("Voting vault already exists")] + VotingVaultAlreadyExists {}, + + #[error("Removing last vault is forbidden")] + RemoveLastVault {}, +} diff --git a/contracts/voting/neutron-voting-registry/src/lib.rs b/contracts/voting/neutron-voting-registry/src/lib.rs new file mode 100644 index 00000000..8e58a2f1 --- /dev/null +++ b/contracts/voting/neutron-voting-registry/src/lib.rs @@ -0,0 +1,9 @@ +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +#[cfg(test)] +mod tests; + +pub use crate::error::ContractError; diff --git a/contracts/voting/neutron-voting-registry/src/msg.rs b/contracts/voting/neutron-voting-registry/src/msg.rs new file mode 100644 index 00000000..6030a617 --- /dev/null +++ b/contracts/voting/neutron-voting-registry/src/msg.rs @@ -0,0 +1,42 @@ +use cwd_interface::Admin; +use cwd_macros::{info_query, voting_query}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +pub struct InstantiateMsg { + // Owner can update all configs including changing the owner. This will generally be a DAO. + pub owner: Option, + // Manager can update all configs except changing the owner. This will generally be an operations multisig for a DAO. + pub manager: Option, + // Address of voting vault contract + pub voting_vault: String, +} + +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + AddVotingVault { + new_voting_vault_contract: String, + }, + RemoveVotingVault { + old_voting_vault_contract: String, + }, + UpdateConfig { + owner: Option, + manager: Option, + }, +} + +#[voting_query] +#[info_query] +#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + Dao {}, + GetConfig {}, + VotingVaults {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MigrateMsg {} diff --git a/contracts/voting/neutron-voting-registry/src/state.rs b/contracts/voting/neutron-voting-registry/src/state.rs new file mode 100644 index 00000000..273adf6f --- /dev/null +++ b/contracts/voting/neutron-voting-registry/src/state.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct Config { + pub owner: Option, + pub manager: Option, + pub voting_vaults: Vec, +} + +pub const CONFIG: Item = Item::new("config"); +pub const DAO: Item = Item::new("dao"); diff --git a/contracts/voting/neutron-voting-registry/src/tests.rs b/contracts/voting/neutron-voting-registry/src/tests.rs new file mode 100644 index 00000000..7eab9ab1 --- /dev/null +++ b/contracts/voting/neutron-voting-registry/src/tests.rs @@ -0,0 +1,987 @@ +// use crate::contract::{migrate, CONTRACT_NAME, CONTRACT_VERSION}; +// use crate::msg::{ +// ExecuteMsg, InstantiateMsg, ListStakersResponse, MigrateMsg, QueryMsg, StakerBalanceResponse, +// }; +// use crate::state::Config; +// use cosmwasm_std::testing::{mock_dependencies, mock_env}; +// use cosmwasm_std::{coins, Addr, Coin, Empty, Uint128}; +// use cw_controllers::ClaimsResponse; +// use cw_multi_test::{ +// custom_app, next_block, App, AppResponse, Contract, ContractWrapper, Executor, +// }; +// use cw_utils::Duration; +// use cwd_interface::voting::{ +// InfoResponse, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +// }; +// use cwd_interface::Admin; +// +// const DAO_ADDR: &str = "dao"; +// const ADDR1: &str = "addr1"; +// const ADDR2: &str = "addr2"; +// const DENOM: &str = "ujuno"; +// const INVALID_DENOM: &str = "uinvalid"; +// +// fn staking_contract() -> Box> { +// let contract = ContractWrapper::new( +// crate::contract::execute, +// crate::contract::instantiate, +// crate::contract::query, +// ); +// Box::new(contract) +// } +// +// fn mock_app() -> App { +// custom_app(|r, _a, s| { +// r.bank +// .init_balance( +// s, +// &Addr::unchecked(DAO_ADDR), +// vec![ +// Coin { +// denom: DENOM.to_string(), +// amount: Uint128::new(10000), +// }, +// Coin { +// denom: INVALID_DENOM.to_string(), +// amount: Uint128::new(10000), +// }, +// ], +// ) +// .unwrap(); +// r.bank +// .init_balance( +// s, +// &Addr::unchecked(ADDR1), +// vec![ +// Coin { +// denom: DENOM.to_string(), +// amount: Uint128::new(10000), +// }, +// Coin { +// denom: INVALID_DENOM.to_string(), +// amount: Uint128::new(10000), +// }, +// ], +// ) +// .unwrap(); +// r.bank +// .init_balance( +// s, +// &Addr::unchecked(ADDR2), +// vec![ +// Coin { +// denom: DENOM.to_string(), +// amount: Uint128::new(10000), +// }, +// Coin { +// denom: INVALID_DENOM.to_string(), +// amount: Uint128::new(10000), +// }, +// ], +// ) +// .unwrap(); +// }) +// } +// +// fn instantiate_staking(app: &mut App, staking_id: u64, msg: InstantiateMsg) -> Addr { +// app.instantiate_contract( +// staking_id, +// Addr::unchecked(DAO_ADDR), +// &msg, +// &[], +// "Staking", +// None, +// ) +// .unwrap() +// } +// +// fn stake_tokens( +// app: &mut App, +// staking_addr: Addr, +// sender: &str, +// amount: u128, +// denom: &str, +// ) -> anyhow::Result { +// app.execute_contract( +// Addr::unchecked(sender), +// staking_addr, +// &ExecuteMsg::Stake {}, +// &coins(amount, denom), +// ) +// } +// +// fn unstake_tokens( +// app: &mut App, +// staking_addr: Addr, +// sender: &str, +// amount: u128, +// ) -> anyhow::Result { +// app.execute_contract( +// Addr::unchecked(sender), +// staking_addr, +// &ExecuteMsg::Unstake { +// amount: Uint128::new(amount), +// }, +// &[], +// ) +// } +// +// fn claim(app: &mut App, staking_addr: Addr, sender: &str) -> anyhow::Result { +// app.execute_contract( +// Addr::unchecked(sender), +// staking_addr, +// &ExecuteMsg::Claim {}, +// &[], +// ) +// } +// +// fn update_config( +// app: &mut App, +// staking_addr: Addr, +// sender: &str, +// owner: Option, +// manager: Option, +// duration: Option, +// ) -> anyhow::Result { +// app.execute_contract( +// Addr::unchecked(sender), +// staking_addr, +// &ExecuteMsg::UpdateConfig { +// owner, +// manager, +// duration, +// }, +// &[], +// ) +// } +// +// fn get_voting_power_at_height( +// app: &mut App, +// staking_addr: Addr, +// address: String, +// height: Option, +// ) -> VotingPowerAtHeightResponse { +// app.wrap() +// .query_wasm_smart( +// staking_addr, +// &QueryMsg::VotingPowerAtHeight { address, height }, +// ) +// .unwrap() +// } +// +// fn get_total_power_at_height( +// app: &mut App, +// staking_addr: Addr, +// height: Option, +// ) -> TotalPowerAtHeightResponse { +// app.wrap() +// .query_wasm_smart(staking_addr, &QueryMsg::TotalPowerAtHeight { height }) +// .unwrap() +// } +// +// fn get_config(app: &mut App, staking_addr: Addr) -> Config { +// app.wrap() +// .query_wasm_smart(staking_addr, &QueryMsg::GetConfig {}) +// .unwrap() +// } +// +// fn get_claims(app: &mut App, staking_addr: Addr, address: String) -> ClaimsResponse { +// app.wrap() +// .query_wasm_smart(staking_addr, &QueryMsg::Claims { address }) +// .unwrap() +// } +// +// fn get_balance(app: &mut App, address: &str, denom: &str) -> Uint128 { +// app.wrap().query_balance(address, denom).unwrap().amount +// } +// +// #[test] +// fn test_instantiate() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// // Populated fields +// let _addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::Address { +// addr: DAO_ADDR.to_string(), +// }), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // Non populated fields +// let _addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: None, +// manager: None, +// denom: DENOM.to_string(), +// unstaking_duration: None, +// }, +// ); +// } +// +// #[test] +// fn test_instantiate_dao_owner() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// // Populated fields +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// let config = get_config(&mut app, addr); +// +// assert_eq!(config.owner, Some(Addr::unchecked(DAO_ADDR))) +// } +// +// #[test] +// #[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] +// fn test_instantiate_invalid_unstaking_duration() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// // Populated fields +// let _addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::Address { +// addr: DAO_ADDR.to_string(), +// }), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(0)), +// }, +// ); +// +// // Non populated fields +// let _addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: None, +// manager: None, +// denom: DENOM.to_string(), +// unstaking_duration: None, +// }, +// ); +// } +// +// #[test] +// #[should_panic(expected = "Must send reserve token 'ujuno'")] +// fn test_stake_invalid_denom() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // Try and stake an invalid denom +// stake_tokens(&mut app, addr, ADDR1, 100, INVALID_DENOM).unwrap(); +// } +// +// #[test] +// fn test_stake_valid_denom() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // Try and stake an valid denom +// stake_tokens(&mut app, addr, ADDR1, 100, DENOM).unwrap(); +// app.update_block(next_block); +// } +// +// #[test] +// #[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] +// fn test_unstake_none_staked() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// unstake_tokens(&mut app, addr, ADDR1, 100).unwrap(); +// } +// +// #[test] +// #[should_panic(expected = "Can only unstake less than or equal to the amount you have staked")] +// fn test_unstake_invalid_balance() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // Stake some tokens +// stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); +// app.update_block(next_block); +// +// // Try and unstake too many +// unstake_tokens(&mut app, addr, ADDR1, 200).unwrap(); +// } +// +// #[test] +// fn test_unstake() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // Stake some tokens +// stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); +// app.update_block(next_block); +// +// // Unstake some +// unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); +// +// // Query claims +// let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); +// assert_eq!(claims.claims.len(), 1); +// app.update_block(next_block); +// +// // Unstake the rest +// unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); +// +// // Query claims +// let claims = get_claims(&mut app, addr, ADDR1.to_string()); +// assert_eq!(claims.claims.len(), 2); +// } +// +// #[test] +// fn test_unstake_no_unstaking_duration() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: None, +// }, +// ); +// +// // Stake some tokens +// stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); +// app.update_block(next_block); +// +// // Unstake some tokens +// unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); +// +// app.update_block(next_block); +// +// let balance = get_balance(&mut app, ADDR1, DENOM); +// // 10000 (initial bal) - 100 (staked) + 75 (unstaked) = 9975 +// assert_eq!(balance, Uint128::new(9975)); +// +// // Unstake the rest +// unstake_tokens(&mut app, addr, ADDR1, 25).unwrap(); +// +// let balance = get_balance(&mut app, ADDR1, DENOM); +// // 10000 (initial bal) - 100 (staked) + 75 (unstaked 1) + 25 (unstaked 2) = 10000 +// assert_eq!(balance, Uint128::new(10000)) +// } +// +// #[test] +// #[should_panic(expected = "Nothing to claim")] +// fn test_claim_no_claims() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// claim(&mut app, addr, ADDR1).unwrap(); +// } +// +// #[test] +// #[should_panic(expected = "Nothing to claim")] +// fn test_claim_claim_not_reached() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // Stake some tokens +// stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); +// app.update_block(next_block); +// +// // Unstake them to create the claims +// unstake_tokens(&mut app, addr.clone(), ADDR1, 100).unwrap(); +// app.update_block(next_block); +// +// // We have a claim but it isnt reached yet so this will still fail +// claim(&mut app, addr, ADDR1).unwrap(); +// } +// +// #[test] +// fn test_claim() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // Stake some tokens +// stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); +// app.update_block(next_block); +// +// // Unstake some to create the claims +// unstake_tokens(&mut app, addr.clone(), ADDR1, 75).unwrap(); +// app.update_block(|b| { +// b.height += 5; +// b.time = b.time.plus_seconds(25); +// }); +// +// // Claim +// claim(&mut app, addr.clone(), ADDR1).unwrap(); +// +// // Query balance +// let balance = get_balance(&mut app, ADDR1, DENOM); +// // 10000 (initial bal) - 100 (staked) + 75 (unstaked) = 9975 +// assert_eq!(balance, Uint128::new(9975)); +// +// // Unstake the rest +// unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); +// app.update_block(|b| { +// b.height += 10; +// b.time = b.time.plus_seconds(50); +// }); +// +// // Claim +// claim(&mut app, addr, ADDR1).unwrap(); +// +// // Query balance +// let balance = get_balance(&mut app, ADDR1, DENOM); +// // 10000 (initial bal) - 100 (staked) + 75 (unstaked 1) + 25 (unstaked 2) = 10000 +// assert_eq!(balance, Uint128::new(10000)); +// } +// +// #[test] +// #[should_panic(expected = "Unauthorized")] +// fn test_update_config_invalid_sender() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // From ADDR2, so not owner or manager +// update_config( +// &mut app, +// addr, +// ADDR2, +// Some(ADDR1.to_string()), +// Some(DAO_ADDR.to_string()), +// Some(Duration::Height(10)), +// ) +// .unwrap(); +// } +// +// #[test] +// #[should_panic(expected = "Only owner can change owner")] +// fn test_update_config_non_owner_changes_owner() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // ADDR1 is the manager so cannot change the owner +// update_config(&mut app, addr, ADDR1, Some(ADDR2.to_string()), None, None).unwrap(); +// } +// +// #[test] +// fn test_update_config_as_owner() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // Swap owner and manager, change duration +// update_config( +// &mut app, +// addr.clone(), +// DAO_ADDR, +// Some(ADDR1.to_string()), +// Some(DAO_ADDR.to_string()), +// Some(Duration::Height(10)), +// ) +// .unwrap(); +// +// let config = get_config(&mut app, addr); +// assert_eq!( +// Config { +// owner: Some(Addr::unchecked(ADDR1)), +// manager: Some(Addr::unchecked(DAO_ADDR)), +// unstaking_duration: Some(Duration::Height(10)), +// denom: DENOM.to_string(), +// }, +// config +// ); +// } +// +// #[test] +// fn test_update_config_as_manager() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // Change duration and manager as manager cannot change owner +// update_config( +// &mut app, +// addr.clone(), +// ADDR1, +// Some(DAO_ADDR.to_string()), +// Some(ADDR2.to_string()), +// Some(Duration::Height(10)), +// ) +// .unwrap(); +// +// let config = get_config(&mut app, addr); +// assert_eq!( +// Config { +// owner: Some(Addr::unchecked(DAO_ADDR)), +// manager: Some(Addr::unchecked(ADDR2)), +// unstaking_duration: Some(Duration::Height(10)), +// denom: DENOM.to_string(), +// }, +// config +// ); +// } +// +// #[test] +// #[should_panic(expected = "Invalid unstaking duration, unstaking duration cannot be 0")] +// fn test_update_config_invalid_duration() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// // Change duration and manager as manager cannot change owner +// update_config( +// &mut app, +// addr, +// ADDR1, +// Some(DAO_ADDR.to_string()), +// Some(ADDR2.to_string()), +// Some(Duration::Height(0)), +// ) +// .unwrap(); +// } +// +// #[test] +// fn test_query_dao() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// let msg = QueryMsg::Dao {}; +// let dao: Addr = app.wrap().query_wasm_smart(addr, &msg).unwrap(); +// assert_eq!(dao, Addr::unchecked(DAO_ADDR)); +// } +// +// #[test] +// fn test_query_info() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// let msg = QueryMsg::Info {}; +// let resp: InfoResponse = app.wrap().query_wasm_smart(addr, &msg).unwrap(); +// assert_eq!(resp.info.contract, "crates.io:neutron-voting-registry"); +// } +// +// #[test] +// fn test_query_claims() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); +// assert_eq!(claims.claims.len(), 0); +// +// // Stake some tokens +// stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); +// app.update_block(next_block); +// +// // Unstake some tokens +// unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); +// app.update_block(next_block); +// +// let claims = get_claims(&mut app, addr.clone(), ADDR1.to_string()); +// assert_eq!(claims.claims.len(), 1); +// +// unstake_tokens(&mut app, addr.clone(), ADDR1, 25).unwrap(); +// app.update_block(next_block); +// +// let claims = get_claims(&mut app, addr, ADDR1.to_string()); +// assert_eq!(claims.claims.len(), 2); +// } +// +// #[test] +// fn test_query_get_config() { +// let mut app = mock_app(); +// let staking_id = app.store_code(staking_contract()); +// let addr = instantiate_staking( +// &mut app, +// staking_id, +// InstantiateMsg { +// owner: Some(Admin::CoreModule {}), +// manager: Some(ADDR1.to_string()), +// denom: DENOM.to_string(), +// unstaking_duration: Some(Duration::Height(5)), +// }, +// ); +// +// let config = get_config(&mut app, addr); +// assert_eq!( +// config, +// Config { +// owner: Some(Addr::unchecked(DAO_ADDR)), +// manager: Some(Addr::unchecked(ADDR1)), +// unstaking_duration: Some(Duration::Height(5)), +// denom: DENOM.to_string(), +// } +// ) +// } +// +// // TODO: test this +// // #[test] +// // fn test_voting_power_queries() { +// // let mut app = mock_app(); +// // let staking_id = app.store_code(staking_contract()); +// // let addr = instantiate_staking( +// // &mut app, +// // staking_id, +// // InstantiateMsg { +// // owner: Some(Admin::CoreModule {}), +// // manager: Some(ADDR1.to_string()), +// // denom: DENOM.to_string(), +// // unstaking_duration: Some(Duration::Height(5)), +// // }, +// // ); +// // +// // // Total power is 0 +// // let resp = get_total_power_at_height(&mut app, addr.clone(), None); +// // assert!(resp.power.is_zero()); +// // +// // // ADDR1 has no power, none staked +// // let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); +// // assert!(resp.power.is_zero()); +// // +// // // ADDR1 stakes +// // stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); +// // app.update_block(next_block); +// // +// // // Total power is 100 +// // let resp = get_total_power_at_height(&mut app, addr.clone(), None); +// // assert_eq!(resp.power, Uint128::new(100)); +// // +// // // ADDR1 has 100 power +// // let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); +// // assert_eq!(resp.power, Uint128::new(100)); +// // +// // // ADDR2 still has 0 power +// // let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); +// // assert!(resp.power.is_zero()); +// // +// // // ADDR2 stakes +// // stake_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); +// // app.update_block(next_block); +// // let prev_height = app.block_info().height - 1; +// // +// // // Query the previous height, total 100, ADDR1 100, ADDR2 0 +// // // Total power is 100 +// // let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); +// // assert_eq!(resp.power, Uint128::new(100)); +// // +// // // ADDR1 has 100 power +// // let resp = +// // get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); +// // assert_eq!(resp.power, Uint128::new(100)); +// // +// // // ADDR2 still has 0 power +// // let resp = +// // get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); +// // assert!(resp.power.is_zero()); +// // +// // // For current height, total 150, ADDR1 100, ADDR2 50 +// // // Total power is 150 +// // let resp = get_total_power_at_height(&mut app, addr.clone(), None); +// // assert_eq!(resp.power, Uint128::new(150)); +// // +// // // ADDR1 has 100 power +// // let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); +// // assert_eq!(resp.power, Uint128::new(100)); +// // +// // // ADDR2 now has 50 power +// // let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), None); +// // assert_eq!(resp.power, Uint128::new(50)); +// // +// // // ADDR1 unstakes half +// // unstake_tokens(&mut app, addr.clone(), ADDR1, 50).unwrap(); +// // app.update_block(next_block); +// // let prev_height = app.block_info().height - 1; +// // +// // // Query the previous height, total 150, ADDR1 100, ADDR2 50 +// // // Total power is 100 +// // let resp = get_total_power_at_height(&mut app, addr.clone(), Some(prev_height)); +// // assert_eq!(resp.power, Uint128::new(150)); +// // +// // // ADDR1 has 100 power +// // let resp = +// // get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), Some(prev_height)); +// // assert_eq!(resp.power, Uint128::new(100)); +// // +// // // ADDR2 still has 0 power +// // let resp = +// // get_voting_power_at_height(&mut app, addr.clone(), ADDR2.to_string(), Some(prev_height)); +// // assert_eq!(resp.power, Uint128::new(50)); +// // +// // // For current height, total 100, ADDR1 50, ADDR2 50 +// // // Total power is 100 +// // let resp = get_total_power_at_height(&mut app, addr.clone(), None); +// // assert_eq!(resp.power, Uint128::new(100)); +// // +// // // ADDR1 has 50 power +// // let resp = get_voting_power_at_height(&mut app, addr.clone(), ADDR1.to_string(), None); +// // assert_eq!(resp.power, Uint128::new(50)); +// // +// // // ADDR2 now has 50 power +// // let resp = get_voting_power_at_height(&mut app, addr, ADDR2.to_string(), None); +// // assert_eq!(resp.power, Uint128::new(50)); +// // } +// +// // TODO: test this +// // #[test] +// // fn test_query_list_stakers() { +// // let mut app = mock_app(); +// // let staking_id = app.store_code(staking_contract()); +// // let addr = instantiate_staking( +// // &mut app, +// // staking_id, +// // InstantiateMsg { +// // owner: Some(Admin::CoreModule {}), +// // manager: Some(ADDR1.to_string()), +// // staking: "".to_string() +// // }, +// // ); +// // +// // // ADDR1 stakes +// // stake_tokens(&mut app, addr.clone(), ADDR1, 100, DENOM).unwrap(); +// // +// // // ADDR2 stakes +// // stake_tokens(&mut app, addr.clone(), ADDR2, 50, DENOM).unwrap(); +// // +// // // check entire result set +// // let stakers: ListStakersResponse = app +// // .wrap() +// // .query_wasm_smart( +// // addr.clone(), +// // &QueryMsg::ListStakers { +// // start_after: None, +// // limit: None, +// // }, +// // ) +// // .unwrap(); +// // +// // let test_res = ListStakersResponse { +// // stakers: vec![ +// // StakerBalanceResponse { +// // address: ADDR1.to_string(), +// // balance: Uint128::new(100), +// // }, +// // StakerBalanceResponse { +// // address: ADDR2.to_string(), +// // balance: Uint128::new(50), +// // }, +// // ], +// // }; +// // +// // assert_eq!(stakers, test_res); +// // +// // // skipped 1, check result +// // let stakers: ListStakersResponse = app +// // .wrap() +// // .query_wasm_smart( +// // addr.clone(), +// // &QueryMsg::ListStakers { +// // start_after: Some(ADDR1.to_string()), +// // limit: None, +// // }, +// // ) +// // .unwrap(); +// // +// // let test_res = ListStakersResponse { +// // stakers: vec![StakerBalanceResponse { +// // address: ADDR2.to_string(), +// // balance: Uint128::new(50), +// // }], +// // }; +// // +// // assert_eq!(stakers, test_res); +// // +// // // skipped 2, check result. should be nothing +// // let stakers: ListStakersResponse = app +// // .wrap() +// // .query_wasm_smart( +// // addr, +// // &QueryMsg::ListStakers { +// // start_after: Some(ADDR2.to_string()), +// // limit: None, +// // }, +// // ) +// // .unwrap(); +// // +// // assert_eq!(stakers, ListStakersResponse { stakers: vec![] }); +// // } +// +// #[test] +// pub fn test_migrate_update_version() { +// let mut deps = mock_dependencies(); +// cw2::set_contract_version(&mut deps.storage, "my-contract", "old-version").unwrap(); +// migrate(deps.as_mut(), mock_env(), MigrateMsg {}).unwrap(); +// let version = cw2::get_contract_version(&deps.storage).unwrap(); +// assert_eq!(version.version, CONTRACT_VERSION); +// assert_eq!(version.contract, CONTRACT_NAME); +// } diff --git a/contracts/voting/schema/query_msg.json b/contracts/voting/schema/query_msg.json deleted file mode 100644 index 17b8d64c..00000000 --- a/contracts/voting/schema/query_msg.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "The contract's configurations; returns [`ConfigResponse`]", - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Amount of NTRN tokens of a voting recipient currently locked in the contract; returns [`VotingPowerResponse`]", - "type": "object", - "required": [ - "voting_power" - ], - "properties": { - "voting_power": { - "type": "object", - "required": [ - "user" - ], - "properties": { - "user": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Enumerate all voting recipients and return their current voting power; returns [`Vec`] of [`VotingPowerResponse`]'s", - "type": "object", - "required": [ - "voting_powers" - ], - "properties": { - "voting_powers": { - "type": "object" - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/voting/src/contract.rs b/contracts/voting/src/contract.rs deleted file mode 100644 index 4d450cc2..00000000 --- a/contracts/voting/src/contract.rs +++ /dev/null @@ -1,140 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, - Uint128, -}; -use cw2::set_contract_version; - -use crate::msg::{ConfigResponse, ExecuteMsg, InstantiateMsg, QueryMsg, VotingPowerResponse}; -use crate::state::{OWNER, TOKENS_LOCKED}; - -const CONTRACT_NAME: &str = "crates.io:neutron-dao"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -//-------------------------------------------------------------------------------------------------- -// Instantiation -//-------------------------------------------------------------------------------------------------- - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - OWNER.save(deps.storage, &deps.api.addr_validate(&msg.owner)?)?; - - init_voting(deps)?; - - Ok(Response::new()) -} - -//-------------------------------------------------------------------------------------------------- -// Executions -//-------------------------------------------------------------------------------------------------- - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> StdResult { - let api = deps.api; - match msg { - ExecuteMsg::TransferOwnership(new_owner) => { - transfer_ownership(deps, info.sender, api.addr_validate(&new_owner)?) - } - ExecuteMsg::InitVoting {} => init_voting(deps), - } -} - -pub fn transfer_ownership( - deps: DepsMut, - sender_addr: Addr, - new_owner_addr: Addr, -) -> StdResult { - let owner_addr = OWNER.load(deps.storage)?; - if sender_addr != owner_addr { - return Err(StdError::generic_err("only owner can transfer ownership")); - } - - OWNER.save(deps.storage, &new_owner_addr)?; - - Ok(Response::new() - .add_attribute("action", "neutron/voting/transfer_ownership") - .add_attribute("previous_owner", owner_addr) - .add_attribute("new_owner", new_owner_addr)) -} - -pub fn init_voting(deps: DepsMut) -> StdResult { - let value1 = Uint128::new(11111111111); - let value2 = Uint128::new(333333333); - TOKENS_LOCKED.save( - deps.storage, - &deps - .api - .addr_validate("neutron1m9l358xunhhwds0568za49mzhvuxx9ux8xafx2")?, - &value1, - )?; - TOKENS_LOCKED.save( - deps.storage, - &deps - .api - .addr_validate("neutron17dtl0mjt3t77kpuhg2edqzjpszulwhgzcdvagh")?, - &value2, - )?; - Ok(Response::default()) -} -//-------------------------------------------------------------------------------------------------- -// Queries -//-------------------------------------------------------------------------------------------------- - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - let api = deps.api; - match msg { - QueryMsg::Config {} => to_binary(&query_config(deps)?), - QueryMsg::VotingPower { user } => { - to_binary(&query_voting_power(deps, api.addr_validate(&user)?)?) - } - QueryMsg::VotingPowers {} => to_binary(&query_voting_powers(deps)?), - } -} - -pub fn query_config(deps: Deps) -> StdResult { - Ok(ConfigResponse { - owner: OWNER.load(deps.storage)?.into(), - }) -} - -pub fn query_voting_power(deps: Deps, user_addr: Addr) -> StdResult { - let voting_power = match TOKENS_LOCKED.may_load(deps.storage, &user_addr) { - Ok(Some(voting_power)) => voting_power, - Ok(None) => Uint128::zero(), - Err(err) => return Err(err), - }; - - Ok(VotingPowerResponse { - user: user_addr.to_string(), - voting_power, - }) -} - -pub fn query_voting_powers(deps: Deps) -> StdResult> { - let voting_powers = TOKENS_LOCKED - .range(deps.storage, None, None, cosmwasm_std::Order::Ascending) - .map(|res| { - let (addr, voting_power) = res?; - Ok(VotingPowerResponse { - user: addr.to_string(), - voting_power, - }) - }) - .collect::>>()?; - - Ok(voting_powers) -} diff --git a/contracts/voting/src/lib.rs b/contracts/voting/src/lib.rs deleted file mode 100644 index 4934c19d..00000000 --- a/contracts/voting/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod contract; -pub mod msg; -pub mod state; diff --git a/contracts/voting/src/msg.rs b/contracts/voting/src/msg.rs deleted file mode 100644 index 2b5bbb72..00000000 --- a/contracts/voting/src/msg.rs +++ /dev/null @@ -1,40 +0,0 @@ -use cosmwasm_std::Uint128; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct InstantiateMsg { - /// The contract's owner - pub owner: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - /// Transfer the contract's ownership to another account - TransferOwnership(String), - InitVoting {}, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum QueryMsg { - /// The contract's configurations; returns [`ConfigResponse`] - Config {}, - /// Amount of NTRN tokens of a voting recipient currently locked in the contract; - /// returns [`VotingPowerResponse`] - VotingPower { user: String }, - /// Enumerate all voting recipients and return their current voting power; - /// returns [`Vec`] of [`VotingPowerResponse`]'s - VotingPowers {}, -} - -pub type ConfigResponse = InstantiateMsg; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct VotingPowerResponse { - /// Address of the user - pub user: String, - /// The user's current voting power, i.e. the amount of NTRN tokens locked in voting contract - pub voting_power: Uint128, -} diff --git a/contracts/voting/src/state.rs b/contracts/voting/src/state.rs deleted file mode 100644 index 40c5149b..00000000 --- a/contracts/voting/src/state.rs +++ /dev/null @@ -1,6 +0,0 @@ -use cosmwasm_std::{Addr, Uint128}; -use cw_storage_plus::{Item, Map}; - -pub const TOKENS_LOCKED: Map<&Addr, Uint128> = Map::new("positions"); - -pub const OWNER: Item = Item::new("owner"); diff --git a/packages/cw-denom/Cargo.toml b/packages/cw-denom/Cargo.toml new file mode 100644 index 00000000..c2ac0648 --- /dev/null +++ b/packages/cw-denom/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "cw-denom" +version = "0.2.0" +edition = "2021" +authors = ["ekez ekez@withoutdoing.com"] +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A package for validation and handling of cw20 and native Cosmos SDK denominations." + + +[dependencies] +cosmwasm-std = "1.0" +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +cw20 = "0.13" + +[dev-dependencies] +cw20-base = "0.13" +cw-multi-test = "0.13" diff --git a/packages/cw-denom/README b/packages/cw-denom/README new file mode 100644 index 00000000..437cc548 --- /dev/null +++ b/packages/cw-denom/README @@ -0,0 +1,16 @@ +# CosmWasm Denom + +This is a simple package for validating cw20 and Cosmos SDK native +denominations. It proves the types, `UncheckedDenom` and +`CheckedDenom`. `UncheckedDenom` may be used in CosmWasm contract +messages and checked via the `into_checked` method. + +To validate native denominations, this package uses the same rules +as the SDK [1]. + +To validate cw20 denominations this package ensures that the +specified address is valid, that the specified address is a +CosmWasm contract, and that the specified address responds +correctly to cw20 `TokenInfo` queries. + +[1] https://github.com/cosmos/cosmos-sdk/blob/7728516abfab950dc7a9120caad4870f1f962df5/types/coin.go#L865-L867 \ No newline at end of file diff --git a/packages/cw-denom/src/integration_tests.rs b/packages/cw-denom/src/integration_tests.rs new file mode 100644 index 00000000..0cf73c55 --- /dev/null +++ b/packages/cw-denom/src/integration_tests.rs @@ -0,0 +1,93 @@ +use cosmwasm_std::{coins, Addr, Empty, Uint128}; +use cw20::Cw20Coin; +use cw_multi_test::{App, BankSudo, Contract, ContractWrapper, Executor}; + +use crate::CheckedDenom; + +fn cw20_contract() -> Box> { + let contract = ContractWrapper::new( + cw20_base::contract::execute, + cw20_base::contract::instantiate, + cw20_base::contract::query, + ); + Box::new(contract) +} + +#[test] +fn test_cw20_denom_send() { + let mut app = App::default(); + + let cw20_id = app.store_code(cw20_contract()); + let cw20 = app + .instantiate_contract( + cw20_id, + Addr::unchecked("ekez"), + &cw20_base::msg::InstantiateMsg { + name: "token".to_string(), + symbol: "symbol".to_string(), + decimals: 6, + initial_balances: vec![Cw20Coin { + amount: Uint128::new(10), + address: "ekez".to_string(), + }], + mint: None, + marketing: None, + }, + &[], + "token contract", + None, + ) + .unwrap(); + + let denom = CheckedDenom::Cw20(cw20); + + let start_balance = denom + .query_balance(&app.wrap(), &Addr::unchecked("ekez")) + .unwrap(); + let send_message = denom + .get_transfer_to_message(&Addr::unchecked("dao"), Uint128::new(9)) + .unwrap(); + app.execute(Addr::unchecked("ekez"), send_message).unwrap(); + let end_balance = denom + .query_balance(&app.wrap(), &Addr::unchecked("ekez")) + .unwrap(); + + assert_eq!(start_balance, Uint128::new(10)); + assert_eq!(end_balance, Uint128::new(1)); + + let dao_balance = denom + .query_balance(&app.wrap(), &Addr::unchecked("dao")) + .unwrap(); + assert_eq!(dao_balance, Uint128::new(9)) +} + +#[test] +fn test_native_denom_send() { + let mut app = App::default(); + app.sudo(cw_multi_test::SudoMsg::Bank(BankSudo::Mint { + to_address: "ekez".to_string(), + amount: coins(10, "ujuno"), + })) + .unwrap(); + + let denom = CheckedDenom::Native("ujuno".to_string()); + + let start_balance = denom + .query_balance(&app.wrap(), &Addr::unchecked("ekez")) + .unwrap(); + let send_message = denom + .get_transfer_to_message(&Addr::unchecked("dao"), Uint128::new(9)) + .unwrap(); + app.execute(Addr::unchecked("ekez"), send_message).unwrap(); + let end_balance = denom + .query_balance(&app.wrap(), &Addr::unchecked("ekez")) + .unwrap(); + + assert_eq!(start_balance, Uint128::new(10)); + assert_eq!(end_balance, Uint128::new(1)); + + let dao_balance = denom + .query_balance(&app.wrap(), &Addr::unchecked("dao")) + .unwrap(); + assert_eq!(dao_balance, Uint128::new(9)) +} diff --git a/packages/cw-denom/src/lib.rs b/packages/cw-denom/src/lib.rs new file mode 100644 index 00000000..970e9a39 --- /dev/null +++ b/packages/cw-denom/src/lib.rs @@ -0,0 +1,336 @@ +//! This is a simple package for validating cw20 and Cosmos SDK native +//! denominations. It proves the types, `UncheckedDenom` and +//! `CheckedDenom`. `UncheckedDenom` may be used in CosmWasm contract +//! messages and checked via the `into_checked` method. +//! +//! To validate native denominations, this package uses the [same +//! rules](https://github.com/cosmos/cosmos-sdk/blob/7728516abfab950dc7a9120caad4870f1f962df5/types/coin.go#L865-L867) +//! as the SDK. +//! +//! To validate cw20 denominations this package ensures that the +//! specified address is valid, that the specified address is a +//! CosmWasm contract, and that the specified address responds +//! correctly to cw20 `TokenInfo` queries. + +#[cfg(test)] +mod integration_tests; + +use std::fmt::{self}; + +use cosmwasm_std::{ + to_binary, Addr, BankMsg, Coin, CosmosMsg, CustomQuery, Deps, QuerierWrapper, StdError, + StdResult, Uint128, WasmMsg, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum DenomError { + #[error(transparent)] + Std(#[from] StdError), + + #[error("invalid cw20 - did not respond to `TokenInfo` query: {err}")] + InvalidCw20 { err: StdError }, + + #[error("invalid native denom. length must be between in [3, 128], got ({len})")] + NativeDenomLength { len: usize }, + + #[error("expected alphabetic ascii character in native denomination")] + NonAlphabeticAscii, + + #[error("invalid character ({c}) in native denom")] + InvalidCharacter { c: char }, +} + +/// A denom that has been checked to point to a valid asset. This enum +/// should never be constructed literally and should always be built +/// by calling `into_checked` on an `UncheckedDenom` instance. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CheckedDenom { + /// A native (bank module) asset. + Native(String), + /// A cw20 asset. + Cw20(Addr), +} + +/// A denom that has not been checked to confirm it points to a valid +/// asset. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum UncheckedDenom { + /// A native (bank module) asset. + Native(String), + /// A cw20 asset. + Cw20(String), +} + +impl UncheckedDenom { + /// Converts an unchecked denomination into a checked one. In the + /// case of native denominations, it is checked that the + /// denomination is valid according to the [default SDK rules]. In + /// the case of cw20 denominations the it is checked that the + /// specified address is valid and that that address responds to a + /// `TokenInfo` query without erroring and returns a valid + /// `cw20::TokenInfoResponse`. + /// + /// [default SDK rules]: https://github.com/cosmos/cosmos-sdk/blob/7728516abfab950dc7a9120caad4870f1f962df5/types/coin.go#L865-L867 + pub fn into_checked(self, deps: Deps) -> Result { + match self { + Self::Native(denom) => validate_native_denom(denom), + Self::Cw20(addr) => { + let addr = deps.api.addr_validate(&addr)?; + let _info: cw20::TokenInfoResponse = deps + .querier + .query_wasm_smart(addr.clone(), &cw20::Cw20QueryMsg::TokenInfo {}) + .map_err(|err| DenomError::InvalidCw20 { err })?; + Ok(CheckedDenom::Cw20(addr)) + } + } + } +} + +impl CheckedDenom { + /// Queries WHO's balance for the denomination. + pub fn query_balance<'a, C: CustomQuery>( + &self, + querier: &QuerierWrapper<'a, C>, + who: &Addr, + ) -> StdResult { + match self { + CheckedDenom::Native(denom) => Ok(querier.query_balance(who, denom)?.amount), + CheckedDenom::Cw20(address) => { + let balance: cw20::BalanceResponse = querier.query_wasm_smart( + address, + &cw20::Cw20QueryMsg::Balance { + address: who.to_string(), + }, + )?; + Ok(balance.balance) + } + } + } + + /// Gets a `CosmosMsg` that, when executed, will transfer AMOUNT + /// tokens to WHO. AMOUNT being zero will cause the message + /// execution to fail. + pub fn get_transfer_to_message(&self, who: &Addr, amount: Uint128) -> StdResult { + Ok(match self { + CheckedDenom::Native(denom) => BankMsg::Send { + to_address: who.to_string(), + amount: vec![Coin { + amount, + denom: denom.to_string(), + }], + } + .into(), + CheckedDenom::Cw20(address) => WasmMsg::Execute { + contract_addr: address.to_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { + recipient: who.to_string(), + amount, + })?, + funds: vec![], + } + .into(), + }) + } +} + +/// Follows cosmos SDK validation logic. Specifically, the regex +/// string `[a-zA-Z][a-zA-Z0-9/:._-]{2,127}`. +/// +/// +pub fn validate_native_denom(denom: String) -> Result { + if denom.len() < 3 || denom.len() > 128 { + return Err(DenomError::NativeDenomLength { len: denom.len() }); + } + let mut chars = denom.chars(); + // Really this means that a non utf-8 character is in here, but + // non-ascii is also correct. + let first = chars.next().ok_or(DenomError::NonAlphabeticAscii)?; + if !first.is_ascii_alphabetic() { + return Err(DenomError::NonAlphabeticAscii); + } + + for c in chars { + if !(c.is_ascii_alphanumeric() || c == '/' || c == ':' || c == '.' || c == '_' || c == '-') + { + return Err(DenomError::InvalidCharacter { c }); + } + } + + Ok(CheckedDenom::Native(denom)) +} + +// Useful for returning these in response objects when updating the +// config or doing a withdrawal. +impl fmt::Display for CheckedDenom { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Native(inner) => write!(f, "{}", inner), + Self::Cw20(inner) => write!(f, "{}", inner), + } + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{ + testing::{mock_dependencies, MockQuerier}, + to_binary, Addr, ContractResult, QuerierResult, StdError, SystemError, Uint128, WasmQuery, + }; + + use super::*; + + const CW20_ADDR: &str = "cw20"; + + fn token_info_mock_querier(works: bool) -> impl Fn(&WasmQuery) -> QuerierResult { + move |query: &WasmQuery| -> QuerierResult { + match query { + WasmQuery::Smart { contract_addr, .. } => { + if *contract_addr == CW20_ADDR { + if works { + QuerierResult::Ok(ContractResult::Ok( + to_binary(&cw20::TokenInfoResponse { + name: "coin".to_string(), + symbol: "symbol".to_string(), + decimals: 6, + total_supply: Uint128::new(10), + }) + .unwrap(), + )) + } else { + QuerierResult::Err(SystemError::NoSuchContract { + addr: CW20_ADDR.to_string(), + }) + } + } else { + unimplemented!() + } + } + _ => unimplemented!(), + } + } + } + + #[test] + fn test_into_checked_cw20_valid() { + let mut querier = MockQuerier::default(); + querier.update_wasm(token_info_mock_querier(true)); + + let mut deps = mock_dependencies(); + deps.querier = querier; + + let unchecked = UncheckedDenom::Cw20(CW20_ADDR.to_string()); + let checked = unchecked.into_checked(deps.as_ref()).unwrap(); + + assert_eq!(checked, CheckedDenom::Cw20(Addr::unchecked(CW20_ADDR))) + } + + #[test] + fn test_into_checked_cw20_invalid() { + let mut querier = MockQuerier::default(); + querier.update_wasm(token_info_mock_querier(false)); + + let mut deps = mock_dependencies(); + deps.querier = querier; + + let unchecked = UncheckedDenom::Cw20(CW20_ADDR.to_string()); + let err = unchecked.into_checked(deps.as_ref()).unwrap_err(); + assert_eq!( + err, + DenomError::InvalidCw20 { + err: StdError::GenericErr { + msg: format!("Querier system error: No such contract: {}", CW20_ADDR) + } + } + ) + } + + #[test] + fn test_into_checked_cw20_addr_invalid() { + let mut querier = MockQuerier::default(); + querier.update_wasm(token_info_mock_querier(true)); + + let mut deps = mock_dependencies(); + deps.querier = querier; + + let unchecked = UncheckedDenom::Cw20("HasCapitalsSoShouldNotValidate".to_string()); + let err = unchecked.into_checked(deps.as_ref()).unwrap_err(); + assert_eq!( + err, + DenomError::Std(StdError::GenericErr { + msg: "Invalid input: address not normalized".to_string() + }) + ) + } + + #[test] + fn test_validate_native_denom_invalid() { + let invalids = [ + "ab".to_string(), // Too short. + (0..129).map(|_| "a").collect::(), // Too long. + "1abc".to_string(), // Starts with non alphabetic character. + "abc~d".to_string(), // Contains invalid character. + "".to_string(), // Too short, also empty. + "๐Ÿฅตabc".to_string(), // Weird unicode start. + "ab:12๐Ÿฅตa".to_string(), // Weird unocide in non-head position. + "ab,cd".to_string(), // Comma is not a valid seperator. + ]; + + for invalid in invalids { + assert!(validate_native_denom(invalid).is_err()) + } + + // Check that we're getting the errors we expect. + assert_eq!( + validate_native_denom("".to_string()), + Err(DenomError::NativeDenomLength { len: 0 }) + ); + // Should check length before contents for better runtime. + assert_eq!( + validate_native_denom("1".to_string()), + Err(DenomError::NativeDenomLength { len: 1 }) + ); + assert_eq!( + validate_native_denom("๐Ÿฅตabc".to_string()), + Err(DenomError::NonAlphabeticAscii) + ); + // The regex that the SDK specifies works on ASCII characters + // (not unicode classes), so this emoji has a "length" that is + // greater than one (counted in terms of ASCII characters). As + // such, we expect to fail on character validation and not + // length. + assert_eq!( + validate_native_denom("๐Ÿฅต".to_string()), + Err(DenomError::NonAlphabeticAscii) + ); + assert_eq!( + validate_native_denom("a๐Ÿฅตabc".to_string()), + Err(DenomError::InvalidCharacter { c: '๐Ÿฅต' }) + ); + } + + #[test] + fn test_validate_native_denom_valid() { + let valids = [ + "ujuno", + "uosmo", + "IBC/A59A9C955F1AB8B76671B00C1A0482C64A6590352944BB5880E5122358F7E1CE", + "wasm.juno123/channel-1/badkids", + ]; + for valid in valids { + validate_native_denom(valid.to_string()).unwrap(); + } + } + + #[test] + fn test_display() { + let denom = CheckedDenom::Native("hello".to_string()); + assert_eq!(denom.to_string(), "hello".to_string()); + let denom = CheckedDenom::Cw20(Addr::unchecked("hello")); + assert_eq!(denom.to_string(), "hello".to_string()); + } +} diff --git a/packages/cw-paginate/Cargo.toml b/packages/cw-paginate/Cargo.toml new file mode 100644 index 00000000..f291685c --- /dev/null +++ b/packages/cw-paginate/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cw-paginate" +version = "0.2.0" +edition = "2021" +authors = ["ekez ekez@withoutdoing.com"] +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A package for paginating cosmwasm maps." + + +[dependencies] +cosmwasm-std = { version = "1.0.0" } +cosmwasm-storage = { version = "1.0.0" } +cw-storage-plus = "0.13" +serde = { version = "1.0.147", default-features = false } + +[dev-dependencies] +cw-multi-test = "0.13" diff --git a/packages/cw-paginate/README.md b/packages/cw-paginate/README.md new file mode 100644 index 00000000..ecf08f35 --- /dev/null +++ b/packages/cw-paginate/README.md @@ -0,0 +1,32 @@ +# CosmWasm Map Pagination + +This package provides generic convienence methods for paginating keys +and values in a CosmWasm `Map` or `SnapshotMap`. If you use these +methods to paginate the maps in your contract you may [make larry0x +happy](https://twitter.com/larry0x/status/1530537243709939719). + +## Example + +Given a map like: + +```rust +pub const ITEMS: Map = Map::new("items"); +``` + +You can use this package to write a query to list it's contents like: + +```rust +pub fn query_list_items( + deps: Deps, + start_after: Option, + limit: Option, +) -> StdResult { + to_binary(&paginate_map( + deps, + &ITEMS, + start_after, + limit, + cosmwasm_std::Order::Descending, + )?) +} +``` diff --git a/packages/cw-paginate/src/lib.rs b/packages/cw-paginate/src/lib.rs new file mode 100644 index 00000000..1453c797 --- /dev/null +++ b/packages/cw-paginate/src/lib.rs @@ -0,0 +1,576 @@ +//! # CosmWasm Map Pagination +// +//! This package provides generic convienence methods for paginating keys +//! and values in a CosmWasm `Maop` or `SnapshotMap`. If you use these +//! methods to paginate the maps in your contract you may [make larry0x +//! happy](https://twitter.com/larry0x/status/1530537243709939719). +// +//! ## Example +// +//! Given a map like: +// +//! ```rust +//! # use cw_storage_plus::Map; +//! +//! pub const ITEMS: Map = Map::new("items"); +//! ``` +// +//! You can use this package to write a query to list it's contents like: +// +//! ```rust +//! # use cosmwasm_std::{Deps, Binary, to_binary, StdResult}; +//! # use cw_storage_plus::Map; +//! # use cw_paginate::paginate_map; +//! +//! # pub const ITEMS: Map = Map::new("items"); +//! +//! pub fn query_list_items( +//! deps: Deps, +//! start_after: Option, +//! limit: Option, +//! ) -> StdResult { +//! to_binary(&paginate_map( +//! deps, +//! &ITEMS, +//! start_after, +//! limit, +//! cosmwasm_std::Order::Descending, +//! )?) +//! } +//! ``` + +use cosmwasm_std::{Deps, Order, StdResult}; + +#[allow(unused_imports)] +use cw_storage_plus::{Bound, Bounder, KeyDeserialize, Map, SnapshotMap, Strategy}; + +/// Generic function for paginating a list of (K, V) pairs in a +/// CosmWasm Map. +pub fn paginate_map<'a, 'b, K, V, R: 'static>( + deps: Deps, + map: &Map<'a, K, V>, + start_after: Option, + limit: Option, + order: Order, +) -> StdResult> +where + K: Bounder<'a> + KeyDeserialize + 'b, + V: serde::de::DeserializeOwned + serde::Serialize, +{ + let (range_min, range_max) = match order { + Order::Ascending => (start_after.map(Bound::exclusive), None), + Order::Descending => (None, start_after.map(Bound::exclusive)), + }; + + let items = map.range(deps.storage, range_min, range_max, order); + match limit { + Some(limit) => Ok(items + .take(limit.try_into().unwrap()) + .collect::>()?), + None => Ok(items.collect::>()?), + } +} + +/// Same as `paginate_map` but only returns the keys. +pub fn paginate_map_keys<'a, 'b, K, V, R: 'static>( + deps: Deps, + map: &Map<'a, K, V>, + start_after: Option, + limit: Option, + order: Order, +) -> StdResult> +where + K: Bounder<'a> + KeyDeserialize + 'b, + V: serde::de::DeserializeOwned + serde::Serialize, +{ + let (range_min, range_max) = match order { + Order::Ascending => (start_after.map(Bound::exclusive), None), + Order::Descending => (None, start_after.map(Bound::exclusive)), + }; + + let items = map.keys(deps.storage, range_min, range_max, order); + match limit { + Some(limit) => Ok(items + .take(limit.try_into().unwrap()) + .collect::>()?), + None => Ok(items.collect::>()?), + } +} + +/// Same as `paginate_map` but for use with `SnapshotMap`. +pub fn paginate_snapshot_map<'a, 'b, K, V, R: 'static>( + deps: Deps, + map: &SnapshotMap<'a, K, V>, + start_after: Option, + limit: Option, + order: Order, +) -> StdResult> +where + K: Bounder<'a> + KeyDeserialize + 'b, + V: serde::de::DeserializeOwned + serde::Serialize, +{ + let (range_min, range_max) = match order { + Order::Ascending => (start_after.map(Bound::exclusive), None), + Order::Descending => (None, start_after.map(Bound::exclusive)), + }; + + let items = map.range(deps.storage, range_min, range_max, order); + match limit { + Some(limit) => Ok(items + .take(limit.try_into().unwrap()) + .collect::>()?), + None => Ok(items.collect::>()?), + } +} + +/// Same as `paginate_map` but only returns the values. +pub fn paginate_map_values<'a, K, V>( + deps: Deps, + map: &Map<'a, K, V>, + start_after: Option, + limit: Option, + order: Order, +) -> StdResult> +where + K: Bounder<'a> + KeyDeserialize + 'static, + V: serde::de::DeserializeOwned + serde::Serialize, +{ + let (range_min, range_max) = match order { + Order::Ascending => (start_after.map(Bound::exclusive), None), + Order::Descending => (None, start_after.map(Bound::exclusive)), + }; + + let items = map + .range(deps.storage, range_min, range_max, order) + .map(|kv| Ok(kv?.1)); + + match limit { + Some(limit) => Ok(items + .take(limit.try_into().unwrap()) + .collect::>()?), + None => Ok(items.collect::>()?), + } +} + +/// Same as `paginate_map` but only returns the keys. For use with +/// `SnaphotMap`. +pub fn paginate_snapshot_map_keys<'a, 'b, K, V, R: 'static>( + deps: Deps, + map: &SnapshotMap<'a, K, V>, + start_after: Option, + limit: Option, + order: Order, +) -> StdResult> +where + K: Bounder<'a> + KeyDeserialize + 'b, + V: serde::de::DeserializeOwned + serde::Serialize, +{ + let (range_min, range_max) = match order { + Order::Ascending => (start_after.map(Bound::exclusive), None), + Order::Descending => (None, start_after.map(Bound::exclusive)), + }; + + let items = map.keys(deps.storage, range_min, range_max, order); + match limit { + Some(limit) => Ok(items + .take(limit.try_into().unwrap()) + .collect::>()?), + None => Ok(items.collect::>()?), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{mock_dependencies, mock_env}; + use cosmwasm_std::{Addr, Uint128}; + + #[test] + fn pagination() { + let mut deps = mock_dependencies(); + let map: Map = Map::new("items"); + + for num in 1..3 { + map.save(&mut deps.storage, num.to_string(), &(num * 2).to_string()) + .unwrap(); + } + + let items = paginate_map(deps.as_ref(), &map, None, None, Order::Descending).unwrap(); + assert_eq!( + items, + vec![ + ("2".to_string(), "4".to_string()), + ("1".to_string(), "2".to_string()) + ] + ); + + let items = paginate_map(deps.as_ref(), &map, None, None, Order::Ascending).unwrap(); + assert_eq!( + items, + vec![ + ("1".to_string(), "2".to_string()), + ("2".to_string(), "4".to_string()) + ] + ); + + let items = paginate_map( + deps.as_ref(), + &map, + Some("1".to_string()), + None, + Order::Ascending, + ) + .unwrap(); + assert_eq!(items, vec![("2".to_string(), "4".to_string())]); + + let items = paginate_map(deps.as_ref(), &map, None, Some(1), Order::Ascending).unwrap(); + assert_eq!(items, vec![("1".to_string(), "2".to_string())]); + } + + #[test] + fn key_pagination() { + let mut deps = mock_dependencies(); + let map: Map = Map::new("items"); + + for num in 1..3 { + map.save(&mut deps.storage, num.to_string(), &(num * 2).to_string()) + .unwrap(); + } + + let items = paginate_map_keys(deps.as_ref(), &map, None, None, Order::Descending).unwrap(); + assert_eq!(items, vec!["2".to_string(), "1".to_string()]); + + let items = paginate_map_keys(deps.as_ref(), &map, None, None, Order::Ascending).unwrap(); + assert_eq!(items, vec!["1".to_string(), "2".to_string()]); + + let items = paginate_map_keys( + deps.as_ref(), + &map, + Some("1".to_string()), + None, + Order::Ascending, + ) + .unwrap(); + assert_eq!(items, vec!["2"]); + + let items = + paginate_map_keys(deps.as_ref(), &map, None, Some(1), Order::Ascending).unwrap(); + assert_eq!(items, vec!["1".to_string()]); + } + + // this test will double check the descending keys with the rewrite + #[test] + fn key_pagination_test2() { + let mut deps = mock_dependencies(); + let map: Map = Map::new("items"); + + for num in 1u32..=10 { + map.save(&mut deps.storage, num, &(num * 2).to_string()) + .unwrap(); + } + + let items = paginate_map_keys(deps.as_ref(), &map, None, None, Order::Descending).unwrap(); + assert_eq!(items, vec![10, 9, 8, 7, 6, 5, 4, 3, 2, 1]); + + let items = paginate_map_keys(deps.as_ref(), &map, None, None, Order::Ascending).unwrap(); + assert_eq!(items, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + let items = + paginate_map_keys(deps.as_ref(), &map, Some(3), Some(3), Order::Ascending).unwrap(); + assert_eq!(items, vec![4, 5, 6]); + + let items = + paginate_map_keys(deps.as_ref(), &map, Some(7), Some(4), Order::Descending).unwrap(); + assert_eq!(items, vec![6, 5, 4, 3]); + } + + #[test] + fn snapshot_pagination() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let map: SnapshotMap<&Addr, Uint128> = SnapshotMap::new( + "items", + "items__checkpoints", + "items__changelog", + Strategy::EveryBlock, + ); + + for ctr in 1..100 { + let addr = Addr::unchecked(format!("test_addr{:0>3}", ctr.clone())); + map.save( + &mut deps.storage, + &addr, + &Uint128::new(ctr), + env.block.height, + ) + .unwrap(); + } + + // grab first 10 items + let items = + paginate_snapshot_map(deps.as_ref(), &map, None, Some(10), Order::Ascending).unwrap(); + + assert_eq!(items.len(), 10); + + let mut test_vec: Vec<(Addr, Uint128)> = vec![]; + for ctr in 1..=10 { + let addr = Addr::unchecked(format!("test_addr{:0>3}", ctr.clone())); + + test_vec.push((addr, Uint128::new(ctr))); + } + assert_eq!(items, test_vec); + + // using the last result of the last item (10), grab the next one + let items = paginate_snapshot_map( + deps.as_ref(), + &map, + Some(&items[items.len() - 1].0), + Some(10), + Order::Ascending, + ) + .unwrap(); + + // should be the 11th item + assert_eq!(items[0].0, Addr::unchecked("test_addr011".to_string())); + assert_eq!(items[0].1, Uint128::new(11)); + + let items = + paginate_snapshot_map(deps.as_ref(), &map, None, None, Order::Descending).unwrap(); + + // 20th item (19 index) should be 80 + assert_eq!(items[19].0, Addr::unchecked("test_addr080".to_string())); + assert_eq!(items[19].1, Uint128::new(80)); + } + + // this test will encapsulate the generic changes for &Addr + #[test] + fn snapshot_pagination_keys_new_generic() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let map: SnapshotMap<&Addr, Uint128> = SnapshotMap::new( + "items", + "items__checkpoints", + "items__changelog", + Strategy::EveryBlock, + ); + + for ctr in 1..100 { + let addr = Addr::unchecked(format!("test_addr{:0>3}", ctr.clone())); + map.save( + &mut deps.storage, + &addr, + &Uint128::new(ctr), + env.block.height, + ) + .unwrap(); + } + + // grab first 10 items + let items = + paginate_snapshot_map_keys(deps.as_ref(), &map, None, Some(10), Order::Ascending) + .unwrap(); + + assert_eq!(items.len(), 10); + + let mut test_vec: Vec = vec![]; + for ctr in 1..=10 { + let addr = Addr::unchecked(format!("test_addr{:0>3}", ctr.clone())); + + test_vec.push(addr); + } + assert_eq!(items, test_vec); + + // max item from before was the 10th, so it'll go backwards from 9->1 + let items = paginate_snapshot_map_keys( + deps.as_ref(), + &map, + Some(&items[items.len() - 1]), + None, + Order::Descending, + ) + .unwrap(); + + // 3rd item in vec should be 006 + assert_eq!(items[3], Addr::unchecked("test_addr006".to_string())); + + let items = + paginate_snapshot_map_keys(deps.as_ref(), &map, None, None, Order::Descending).unwrap(); + + // 20th item (19 index) should be 80 + assert_eq!(items[19], Addr::unchecked("test_addr080".to_string())); + } + + #[test] + fn snapshot_pagination_keys() { + let mut deps = mock_dependencies(); + let env = mock_env(); + + let map: SnapshotMap = SnapshotMap::new( + "items", + "items__checkpoints", + "items__changelog", + Strategy::EveryBlock, + ); + + for ctr in 1..=100 { + map.save( + &mut deps.storage, + ctr, + &Uint128::new(ctr.try_into().unwrap()), + env.block.height, + ) + .unwrap(); + } + + // grab first 10 items + let items = + paginate_snapshot_map_keys(deps.as_ref(), &map, None, Some(10), Order::Ascending) + .unwrap(); + + assert_eq!(items.len(), 10); + assert_eq!(items, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + let items = + paginate_snapshot_map_keys(deps.as_ref(), &map, Some(50), Some(10), Order::Ascending) + .unwrap(); + + assert_eq!(items, vec![51, 52, 53, 54, 55, 56, 57, 58, 59, 60]); + + let items = + paginate_snapshot_map_keys(deps.as_ref(), &map, Some(50), Some(10), Order::Descending) + .unwrap(); + + assert_eq!(items, vec![49, 48, 47, 46, 45, 44, 43, 42, 41, 40]); + } + + #[test] + fn pagination_order_desc_tests() { + let mut deps = mock_dependencies(); + let map: Map = Map::new("items"); + + map.save(&mut deps.storage, 1, &40).unwrap(); + map.save(&mut deps.storage, 2, &22).unwrap(); + map.save(&mut deps.storage, 3, &77).unwrap(); + map.save(&mut deps.storage, 4, &66).unwrap(); + map.save(&mut deps.storage, 5, &0).unwrap(); + + let items = paginate_map(deps.as_ref(), &map, None, None, Order::Descending).unwrap(); + assert_eq!(items, vec![(5, 0), (4, 66), (3, 77), (2, 22), (1, 40)]); + + let items = paginate_map(deps.as_ref(), &map, Some(3), None, Order::Descending).unwrap(); + assert_eq!(items, vec![(2, 22), (1, 40)]); + + let items = paginate_map(deps.as_ref(), &map, Some(1), None, Order::Descending).unwrap(); + assert_eq!(items, vec![]); + } + + /// testing reworked paginate_map and paginate_map_keys. + /// pay particular attention to the values added. this is to ensure + /// that the values arent being assessed + #[test] + fn pagination_keys_refs() { + let mut deps = mock_dependencies(); + let map: Map<&Addr, u32> = Map::new("items"); + + map.save( + &mut deps.storage, + &Addr::unchecked(format!("test_addr{:0>3}", 1)), + &40, + ) + .unwrap(); + map.save( + &mut deps.storage, + &Addr::unchecked(format!("test_addr{:0>3}", 2)), + &22, + ) + .unwrap(); + map.save( + &mut deps.storage, + &Addr::unchecked(format!("test_addr{:0>3}", 3)), + &77, + ) + .unwrap(); + map.save( + &mut deps.storage, + &Addr::unchecked(format!("test_addr{:0>3}", 4)), + &66, + ) + .unwrap(); + map.save( + &mut deps.storage, + &Addr::unchecked(format!("test_addr{:0>3}", 5)), + &0, + ) + .unwrap(); + + let items = paginate_map_keys(deps.as_ref(), &map, None, None, Order::Descending).unwrap(); + assert_eq!(items[1], Addr::unchecked(format!("test_addr{:0>3}", 4))); + assert_eq!(items[4], Addr::unchecked(format!("test_addr{:0>3}", 1))); + + let addr: Addr = Addr::unchecked(format!("test_addr{:0>3}", 3)); + let items = + paginate_map_keys(deps.as_ref(), &map, Some(&addr), None, Order::Ascending).unwrap(); + assert_eq!(items[0], Addr::unchecked(format!("test_addr{:0>3}", 4))); + } + + /// testing reworked paginate_map and paginate_map_keys. + /// pay particular attention to the values added. this is to ensure + /// that the values arent being assessed + #[test] + fn pagination_refs() { + let mut deps = mock_dependencies(); + let map: Map<&Addr, u32> = Map::new("items"); + + map.save( + &mut deps.storage, + &Addr::unchecked(format!("test_addr{:0>3}", 1)), + &0, + ) + .unwrap(); + map.save( + &mut deps.storage, + &Addr::unchecked(format!("test_addr{:0>3}", 2)), + &22, + ) + .unwrap(); + map.save( + &mut deps.storage, + &Addr::unchecked(format!("test_addr{:0>3}", 3)), + &77, + ) + .unwrap(); + map.save( + &mut deps.storage, + &Addr::unchecked(format!("test_addr{:0>3}", 4)), + &66, + ) + .unwrap(); + map.save( + &mut deps.storage, + &Addr::unchecked(format!("test_addr{:0>3}", 6)), + &0, + ) + .unwrap(); + + let items = paginate_map(deps.as_ref(), &map, None, None, Order::Descending).unwrap(); + assert_eq!( + items[1], + (Addr::unchecked(format!("test_addr{:0>3}", 4)), 66) + ); + assert_eq!( + items[4], + (Addr::unchecked(format!("test_addr{:0>3}", 1)), 0) + ); + + let addr: Addr = Addr::unchecked(format!("test_addr{:0>3}", 3)); + let items = + paginate_map(deps.as_ref(), &map, Some(&addr), Some(2), Order::Ascending).unwrap(); + let test_vec: Vec<(Addr, u32)> = vec![ + (Addr::unchecked(format!("test_addr{:0>3}", 4)), 66), + (Addr::unchecked(format!("test_addr{:0>3}", 6)), 0), + ]; + assert_eq!(items, test_vec); + } +} diff --git a/packages/cw721-controllers/Cargo.toml b/packages/cw721-controllers/Cargo.toml new file mode 100644 index 00000000..5f4085dc --- /dev/null +++ b/packages/cw721-controllers/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cw721-controllers" +version = "0.2.0" +authors = ["CypherApe cypherape@protonmail.com"] +edition = "2021" +description = "A package for managing cw721 claims." +repository = "https://github.com/DA0-DA0/dao-contracts" + +[dependencies] +cosmwasm-std = { version = "1.0.0-beta6" } +cw-utils = { version = "0.13.1" } +cw-storage-plus = { version = "0.13.1" } +schemars = "0.8.1" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.21" } diff --git a/packages/cw721-controllers/README.md b/packages/cw721-controllers/README.md new file mode 100644 index 00000000..794a1ac5 --- /dev/null +++ b/packages/cw721-controllers/README.md @@ -0,0 +1,6 @@ +# CW721 Controllers: Common cw721 controllers for many contracts + +This is an implementation of cw-plus' +[cw-controllers](https://github.com/CosmWasm/cw-plus/tree/72afcde846b907fac5c0394ce86ed5a59ce47524/packages/controllers) +package for CW721 NFTs. It manages claims for our [cw721 staking +contract](../../contracts/voting/cwd-voting-cw721-staked). diff --git a/packages/cw721-controllers/src/lib.rs b/packages/cw721-controllers/src/lib.rs new file mode 100644 index 00000000..89909830 --- /dev/null +++ b/packages/cw721-controllers/src/lib.rs @@ -0,0 +1,3 @@ +mod nft_claim; + +pub use nft_claim::{NftClaim, NftClaims, NftClaimsResponse}; diff --git a/packages/cw721-controllers/src/nft_claim.rs b/packages/cw721-controllers/src/nft_claim.rs new file mode 100644 index 00000000..6157b12e --- /dev/null +++ b/packages/cw721-controllers/src/nft_claim.rs @@ -0,0 +1,399 @@ +use cosmwasm_std::{Addr, BlockInfo, CustomQuery, Deps, StdResult, Storage}; +use cw_storage_plus::Map; +use cw_utils::Expiration; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct NftClaimsResponse { + pub nft_claims: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct NftClaim { + pub token_id: String, + pub release_at: Expiration, +} + +impl NftClaim { + pub fn new(token_id: String, released: Expiration) -> Self { + NftClaim { + token_id, + release_at: released, + } + } +} + +pub struct NftClaims<'a>(Map<'a, &'a Addr, Vec>); + +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 + /// address. + pub fn create_nft_claims( + &self, + storage: &mut dyn Storage, + addr: &Addr, + 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::>()) + })?; + 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. + pub fn claim_nfts( + &self, + storage: &mut dyn Storage, + addr: &Addr, + 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 + } + }); + Ok(waiting) + })?; + Ok(to_send) + } + + 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 }) + } +} + +#[cfg(test)] +mod test { + use cosmwasm_std::{ + testing::{mock_dependencies, mock_env}, + Order, + }; + + use super::*; + const TEST_BAYC_TOKEN_ID: &str = "BAYC"; + const TEST_CRYPTO_PUNKS_TOKEN_ID: &str = "CRYPTOPUNKS"; + const TEST_EXPIRATION: Expiration = Expiration::AtHeight(10); + + #[test] + fn can_create_claim() { + let claim = NftClaim::new(TEST_BAYC_TOKEN_ID.to_string(), TEST_EXPIRATION); + assert_eq!(claim.token_id, TEST_BAYC_TOKEN_ID.to_string()); + assert_eq!(claim.release_at, TEST_EXPIRATION); + } + + #[test] + fn can_create_claims() { + let deps = mock_dependencies(); + let claims = NftClaims::new("claims"); + // Assert that claims creates a map and there are no keys in the map. + assert_eq!( + claims + .0 + .range_raw(&deps.storage, None, None, Order::Ascending) + .collect::>>() + .unwrap() + .len(), + 0 + ); + } + + #[test] + fn check_create_claim_updates_map() { + 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.into()], + TEST_EXPIRATION, + ) + .unwrap(); + + // 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")) + .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); + + // Adding another claim to same address, make sure that both claims are saved. + claims + .create_nft_claims( + deps.as_mut().storage, + &Addr::unchecked("addr"), + vec![TEST_CRYPTO_PUNKS_TOKEN_ID.into()], + TEST_EXPIRATION, + ) + .unwrap(); + + // Assert that both claims exist for the address. + let saved_claims = claims + .0 + .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .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); + + // Adding another claim to different address, make sure that other address only has one claim. + claims + .create_nft_claims( + deps.as_mut().storage, + &Addr::unchecked("addr2"), + vec![TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()], + TEST_EXPIRATION, + ) + .unwrap(); + + // Assert that both claims exist for the address. + let saved_claims = claims + .0 + .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .unwrap(); + + let saved_claims_addr2 = claims + .0 + .load(deps.as_mut().storage, &Addr::unchecked("addr2")) + .unwrap(); + assert_eq!(saved_claims.len(), 2); + assert_eq!(saved_claims_addr2.len(), 1); + } + + #[test] + fn test_claim_tokens_with_no_claims() { + let mut deps = mock_dependencies(); + let claims = NftClaims::new("claims"); + + let nfts = 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")) + .unwrap(); + + assert_eq!(nfts.len(), 0); + assert_eq!(saved_claims.len(), 0); + } + + #[test] + fn test_claim_tokens_with_no_released_claims() { + let mut deps = mock_dependencies(); + let claims = NftClaims::new("claims"); + + claims + .create_nft_claims( + deps.as_mut().storage, + &Addr::unchecked("addr"), + vec![TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()], + Expiration::AtHeight(10), + ) + .unwrap(); + + claims + .create_nft_claims( + deps.as_mut().storage, + &Addr::unchecked("addr"), + vec![TEST_BAYC_TOKEN_ID.to_string()], + Expiration::AtHeight(100), + ) + .unwrap(); + + 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 saved_claims = claims + .0 + .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .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)); + } + + #[test] + fn test_claim_tokens_with_one_released_claim() { + 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()], + Expiration::AtHeight(10), + ) + .unwrap(); + + claims + .create_nft_claims( + deps.as_mut().storage, + &Addr::unchecked("addr"), + vec![TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()], + Expiration::AtHeight(100), + ) + .unwrap(); + + 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) + .unwrap(); + + let saved_claims = claims + .0 + .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .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)); + } + + #[test] + fn test_claim_tokens_with_all_released_claims() { + 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()], + Expiration::AtHeight(10), + ) + .unwrap(); + + claims + .create_nft_claims( + deps.as_mut().storage, + &Addr::unchecked("addr"), + vec![TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()], + Expiration::AtHeight(100), + ) + .unwrap(); + + 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) + .unwrap(); + + let saved_claims = claims + .0 + .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .unwrap(); + + assert_eq!( + nfts, + vec![ + TEST_BAYC_TOKEN_ID.to_string(), + TEST_CRYPTO_PUNKS_TOKEN_ID.to_string() + ] + ); + assert_eq!(saved_claims.len(), 0); + } + + #[test] + fn test_query_claims_returns_correct_claims() { + let mut deps = mock_dependencies(); + let claims = NftClaims::new("claims"); + + claims + .create_nft_claims( + deps.as_mut().storage, + &Addr::unchecked("addr"), + vec![TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()], + Expiration::AtHeight(10), + ) + .unwrap(); + + let queried_claims = claims + .query_claims(deps.as_ref(), &Addr::unchecked("addr")) + .unwrap(); + let saved_claims = claims + .0 + .load(deps.as_mut().storage, &Addr::unchecked("addr")) + .unwrap(); + assert_eq!(queried_claims.nft_claims, saved_claims); + } + + #[test] + fn test_query_claims_returns_empty_for_non_existent_user() { + let mut deps = mock_dependencies(); + let claims = NftClaims::new("claims"); + + claims + .create_nft_claims( + deps.as_mut().storage, + &Addr::unchecked("addr"), + vec![TEST_CRYPTO_PUNKS_TOKEN_ID.to_string()], + Expiration::AtHeight(10), + ) + .unwrap(); + + let queried_claims = claims + .query_claims(deps.as_ref(), &Addr::unchecked("addr2")) + .unwrap(); + + assert_eq!(queried_claims.nft_claims.len(), 0); + } +} diff --git a/packages/cwd-hooks/Cargo.toml b/packages/cwd-hooks/Cargo.toml new file mode 100644 index 00000000..be79ebb8 --- /dev/null +++ b/packages/cwd-hooks/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cwd-hooks" +version = "0.2.0" +edition = "2021" +authors = ["Callum Anderson "] +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A package for managing a set of hooks which can be accessed by their index." + +[dependencies] +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +cosmwasm-std = "1.0.0" +cw-storage-plus = "0.13" diff --git a/packages/cwd-hooks/README.md b/packages/cwd-hooks/README.md new file mode 100644 index 00000000..d00e6d43 --- /dev/null +++ b/packages/cwd-hooks/README.md @@ -0,0 +1,10 @@ +# CosmWasm DAO Hooks + +This package provides shared hook functionality used for +[proposal](../cwd-proposal-hooks) and [vote](../cwd-vote-hooks) hooks. + +It deviates from other CosmWasm hook packages in that hooks can be +modified based on their index in the hook list AND based on the +address receiving the hook. This allows dispatching hooks with their +index as the reply ID of a submessage and removing hooks if they fail +to process the hook message. diff --git a/packages/cwd-hooks/src/lib.rs b/packages/cwd-hooks/src/lib.rs new file mode 100644 index 00000000..5048a8eb --- /dev/null +++ b/packages/cwd-hooks/src/lib.rs @@ -0,0 +1,144 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use cosmwasm_std::{Addr, CustomQuery, Deps, StdError, StdResult, Storage, SubMsg}; +use cw_storage_plus::Item; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct HooksResponse { + pub hooks: Vec, +} + +#[derive(Error, Debug, PartialEq)] +pub enum HookError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Given address already registered as a hook")] + HookAlreadyRegistered {}, + + #[error("Given address not registered as a hook")] + HookNotRegistered {}, +} + +// store all hook addresses in one item. We cannot have many of them before the contract becomes unusable anyway. +pub struct Hooks<'a>(Item<'a, Vec>); + +impl<'a> Hooks<'a> { + pub const fn new(storage_key: &'a str) -> Self { + Hooks(Item::new(storage_key)) + } + + pub fn add_hook(&self, storage: &mut dyn Storage, addr: Addr) -> Result<(), HookError> { + let mut hooks = self.0.may_load(storage)?.unwrap_or_default(); + if !hooks.iter().any(|h| h == &addr) { + hooks.push(addr); + } else { + return Err(HookError::HookAlreadyRegistered {}); + } + Ok(self.0.save(storage, &hooks)?) + } + + pub fn remove_hook(&self, storage: &mut dyn Storage, addr: Addr) -> Result<(), HookError> { + let mut hooks = self.0.load(storage)?; + if let Some(p) = hooks.iter().position(|x| x == &addr) { + hooks.remove(p); + } else { + return Err(HookError::HookNotRegistered {}); + } + Ok(self.0.save(storage, &hooks)?) + } + + pub fn remove_hook_by_index( + &self, + storage: &mut dyn Storage, + index: u64, + ) -> Result { + let mut hooks = self.0.load(storage)?; + let hook = hooks.remove(index as usize); + self.0.save(storage, &hooks)?; + Ok(hook) + } + + pub fn prepare_hooks StdResult>( + &self, + storage: &dyn Storage, + prep: F, + ) -> StdResult> { + self.0 + .may_load(storage)? + .unwrap_or_default() + .into_iter() + .map(prep) + .collect() + } + + pub fn hook_count(&self, storage: &dyn Storage) -> StdResult { + // The WASM VM (as of version 1) is 32 bit and sets limits for + // memory accordingly: + // . We + // can safely return a u32 here as that's the biggest size in + // the WASM VM. + Ok(self.0.may_load(storage)?.unwrap_or_default().len() as u32) + } + + pub fn query_hooks(&self, deps: Deps) -> StdResult { + let hooks = self.0.may_load(deps.storage)?.unwrap_or_default(); + let hooks = hooks.into_iter().map(String::from).collect(); + Ok(HooksResponse { hooks }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::{coins, testing::mock_dependencies, BankMsg}; + + // Shorthand for an unchecked address. + macro_rules! addr { + ($x:expr ) => { + Addr::unchecked($x) + }; + } + + #[test] + fn test_hooks() { + let mut deps = mock_dependencies(); + let storage = &mut deps.storage; + let hooks = Hooks::new("hooks"); + hooks.add_hook(storage, addr!("ekez")).unwrap(); + hooks.add_hook(storage, addr!("meow")).unwrap(); + + assert_eq!(hooks.hook_count(storage).unwrap(), 2); + + hooks.remove_hook_by_index(storage, 0).unwrap(); + + assert_eq!(hooks.hook_count(storage).unwrap(), 1); + + let msgs = hooks + .prepare_hooks(storage, |a| { + Ok(SubMsg::reply_always( + BankMsg::Burn { + amount: coins(a.as_str().len() as u128, "uekez"), + }, + 2, + )) + }) + .unwrap(); + + assert_eq!( + msgs, + vec![SubMsg::reply_always( + BankMsg::Burn { + amount: coins(4, "uekez"), + }, + 2, + )] + ); + + let HooksResponse { hooks: the_hooks } = hooks.query_hooks(deps.as_ref()).unwrap(); + + assert_eq!(the_hooks, vec![addr!("meow")]); + } +} diff --git a/packages/cwd-interface/.cargo/config b/packages/cwd-interface/.cargo/config new file mode 100644 index 00000000..e44e70f3 --- /dev/null +++ b/packages/cwd-interface/.cargo/config @@ -0,0 +1,2 @@ +[alias] +schema = "run --example schema" diff --git a/packages/cwd-interface/Cargo.toml b/packages/cwd-interface/Cargo.toml new file mode 100644 index 00000000..9bb2c807 --- /dev/null +++ b/packages/cwd-interface/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cwd-interface" +version = "0.2.0" +edition = "2021" +authors = ["ekez ekez@withoutdoing.com"] +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A package containing interface definitions for DAO DAO DAOs." + +[dependencies] +cosmwasm-std = { version = "1.0.0" } +cwd-macros = { path = "../cwd-macros" } +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +schemars = "0.8" +cw2 = "0.13" + +[dev-dependencies] +cosmwasm-schema = { version = "1.0.0" } diff --git a/packages/cwd-interface/README.md b/packages/cwd-interface/README.md new file mode 100644 index 00000000..674b33b2 --- /dev/null +++ b/packages/cwd-interface/README.md @@ -0,0 +1,4 @@ +# CosmWasm DAO Interface + +This package provides the types and interfaces needed for interacting +with DAO modules. diff --git a/packages/cwd-interface/examples/schema.rs b/packages/cwd-interface/examples/schema.rs new file mode 100644 index 00000000..37ae3dd3 --- /dev/null +++ b/packages/cwd-interface/examples/schema.rs @@ -0,0 +1,20 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use cwd_interface::voting::{ + InfoResponse, Query, TotalPowerAtHeightResponse, VotingPowerAtHeightResponse, +}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(Query), &out_dir); + export_schema(&schema_for!(TotalPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(VotingPowerAtHeightResponse), &out_dir); + export_schema(&schema_for!(InfoResponse), &out_dir); +} diff --git a/packages/cwd-interface/src/lib.rs b/packages/cwd-interface/src/lib.rs new file mode 100644 index 00000000..0930307f --- /dev/null +++ b/packages/cwd-interface/src/lib.rs @@ -0,0 +1,114 @@ +use cosmwasm_std::{Addr, Binary, WasmMsg}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +pub mod voting; + +/// Information about the CosmWasm level admin of a contract. Used in +/// conjunction with `ModuleInstantiateInfo` to instantiate modules. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Admin { + /// Set the admin to a specified address. + Address { addr: String }, + /// Sets the admin as the core module address. + CoreModule {}, +} + +/// Information needed to instantiate a module. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct ModuleInstantiateInfo { + /// Code ID of the contract to be instantiated. + pub code_id: u64, + /// Instantiate message to be used to create the contract. + pub msg: Binary, + /// CosmWasm level admin of the instantiated contract. See: + /// + pub admin: Option, + /// Label for the instantiated contract. + pub label: String, +} + +impl ModuleInstantiateInfo { + pub fn into_wasm_msg(self, dao_address: Addr) -> WasmMsg { + WasmMsg::Instantiate { + admin: self.admin.map(|admin| match admin { + Admin::Address { addr } => addr, + Admin::CoreModule {} => dao_address.into_string(), + }), + code_id: self.code_id, + msg: self.msg, + funds: vec![], + label: self.label, + } + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{to_binary, Addr, WasmMsg}; + + use crate::{Admin, ModuleInstantiateInfo}; + + #[test] + fn test_module_instantiate_admin_none() { + let no_admin = ModuleInstantiateInfo { + code_id: 42, + msg: to_binary("foo").unwrap(), + admin: None, + label: "bar".to_string(), + }; + assert_eq!( + no_admin.into_wasm_msg(Addr::unchecked("ekez")), + WasmMsg::Instantiate { + admin: None, + code_id: 42, + msg: to_binary("foo").unwrap(), + funds: vec![], + label: "bar".to_string() + } + ) + } + + #[test] + fn test_module_instantiate_admin_addr() { + let no_admin = ModuleInstantiateInfo { + code_id: 42, + msg: to_binary("foo").unwrap(), + admin: Some(Admin::Address { + addr: "core".to_string(), + }), + label: "bar".to_string(), + }; + assert_eq!( + no_admin.into_wasm_msg(Addr::unchecked("ekez")), + WasmMsg::Instantiate { + admin: Some("core".to_string()), + code_id: 42, + msg: to_binary("foo").unwrap(), + funds: vec![], + label: "bar".to_string() + } + ) + } + + #[test] + fn test_module_instantiate_instantiator_addr() { + let no_admin = ModuleInstantiateInfo { + code_id: 42, + msg: to_binary("foo").unwrap(), + admin: Some(Admin::CoreModule {}), + label: "bar".to_string(), + }; + assert_eq!( + no_admin.into_wasm_msg(Addr::unchecked("ekez")), + WasmMsg::Instantiate { + admin: Some("ekez".to_string()), + code_id: 42, + msg: to_binary("foo").unwrap(), + funds: vec![], + label: "bar".to_string() + } + ) + } +} diff --git a/packages/cwd-interface/src/voting.rs b/packages/cwd-interface/src/voting.rs new file mode 100644 index 00000000..8427ba08 --- /dev/null +++ b/packages/cwd-interface/src/voting.rs @@ -0,0 +1,71 @@ +use cosmwasm_std::Uint128; +use cw2::ContractVersion; +use cwd_macros::{active_query, info_query, proposal_module_query, token_query, voting_query}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[token_query] +#[voting_query] +#[info_query] +#[active_query] +#[proposal_module_query] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Query {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct VotingPowerAtHeightResponse { + pub power: Uint128, + pub height: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct TotalPowerAtHeightResponse { + pub power: Uint128, + pub height: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct ClaimsResponse { + pub power: Uint128, + pub height: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InfoResponse { + pub info: ContractVersion, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct IsActiveResponse { + pub active: bool, +} + +mod tests { + /// Make sure the enum has all of the fields we expect. This will + /// fail to compile if not. + #[test] + fn test_macro_expansion() { + use cwd_macros::{active_query, info_query, token_query, voting_query}; + use schemars::JsonSchema; + use serde::{Deserialize, Serialize}; + + #[token_query] + #[voting_query] + #[info_query] + #[active_query] + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] + #[serde(rename_all = "snake_case")] + enum Query {} + + let query = Query::TokenContract {}; + + match query { + Query::TokenContract {} => (), + Query::VotingPowerAtHeight { .. } => (), + Query::TotalPowerAtHeight { .. } => (), + Query::IsActive {} => (), + Query::Info {} => (), + } + } +} diff --git a/packages/cwd-macros/.cargo/config b/packages/cwd-macros/.cargo/config new file mode 100644 index 00000000..e44e70f3 --- /dev/null +++ b/packages/cwd-macros/.cargo/config @@ -0,0 +1,2 @@ +[alias] +schema = "run --example schema" diff --git a/packages/cwd-macros/Cargo.toml b/packages/cwd-macros/Cargo.toml new file mode 100644 index 00000000..0b0c9b99 --- /dev/null +++ b/packages/cwd-macros/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "cwd-macros" +version = "0.2.0" +edition = "2021" +authors = ["ekez ekez@withoutdoing.com"] +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A package macros for deriving DAO module interfaces." + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0", features = ["derive"] } +quote = "1.0" +proc-macro2 = "1.0" + +[dev-dependencies] +cosmwasm-schema = { version = "1.0.0" } +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } diff --git a/packages/cwd-macros/README.md b/packages/cwd-macros/README.md new file mode 100644 index 00000000..2ff49778 --- /dev/null +++ b/packages/cwd-macros/README.md @@ -0,0 +1,14 @@ +# CosmWasm DAO Macros + +This package provides a collection of macros that may be used to +derive DAO module interfaces on message enums. For example, to derive +the voting module interface on an enum: + +```rust +#[token_query] +#[voting_query] +#[info_query] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Query {} +``` diff --git a/packages/cwd-macros/examples/schema.rs b/packages/cwd-macros/examples/schema.rs new file mode 100644 index 00000000..2e82f1be --- /dev/null +++ b/packages/cwd-macros/examples/schema.rs @@ -0,0 +1,22 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; + +use cwd_macros::{info_query, voting_query}; + +#[voting_query] +#[info_query] +#[derive(Serialize, Deserialize, JsonSchema)] +enum VotingQuery {} + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(VotingQuery), &out_dir); +} diff --git a/packages/cwd-macros/src/lib.rs b/packages/cwd-macros/src/lib.rs new file mode 100644 index 00000000..60d7a71e --- /dev/null +++ b/packages/cwd-macros/src/lib.rs @@ -0,0 +1,445 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, AttributeArgs, DataEnum, DeriveInput, Variant}; + +/// Adds the necessary fields to an such that the enum implements the +/// interface needed to be a voting module. +/// +/// For example: +/// +/// ``` +/// use cwd_macros::voting_query; +/// +/// #[voting_query] +/// enum QueryMsg {} +/// ``` +/// +/// Will transform the enum to: +/// +/// ``` +/// enum QueryMsg { +/// VotingPowerAtHeight { +/// address: String, +/// height: Option +/// }, +/// TotalPowerAtHeight { +/// height: Option +/// }, +/// } +/// ``` +/// +/// Note that other derive macro invocations must occur after this +/// procedural macro as they may depend on the new fields. For +/// example, the following will fail becase the `Clone` derivation +/// occurs before the addition of the field. +/// +/// ```compile_fail +/// use cwd_macros::voting_query; +/// +/// #[derive(Clone)] +/// #[voting_query] +/// #[allow(dead_code)] +/// enum Test { +/// Foo, +/// Bar(u64), +/// Baz { foo: u64 }, +/// } +/// ``` +#[proc_macro_attribute] +pub fn voting_query(metadata: TokenStream, input: TokenStream) -> TokenStream { + // Make sure that no arguments were passed in. + let args = parse_macro_input!(metadata as AttributeArgs); + if let Some(first_arg) = args.first() { + return syn::Error::new_spanned(first_arg, "voting query macro takes no arguments") + .to_compile_error() + .into(); + } + + let mut ast: DeriveInput = parse_macro_input!(input); + match &mut ast.data { + syn::Data::Enum(DataEnum { variants, .. }) => { + let voting_power: Variant = syn::parse2(quote! { VotingPowerAtHeight { + address: ::std::string::String, + height: ::std::option::Option<::std::primitive::u64> + } }) + .unwrap(); + + let total_power: Variant = syn::parse2(quote! { TotalPowerAtHeight { + height: ::std::option::Option<::std::primitive::u64> + } }) + .unwrap(); + + // This is example how possible we can implement such methods, + // but there is requirement to modify core contract (so for now it makes nno sense) + // let claims: Variant = syn::parse2(quote! { Claims { + // address: ::std::string::String, + // } }) + // .unwrap(); + // + // let list_stakers: Variant = syn::parse2(quote! { ListStakers { + // address: ::std::option::Option<::std::string::String>, + // height: ::std::option::Option<::std::primitive::u32> + // } }) + // .unwrap(); + + variants.push(voting_power); + variants.push(total_power); + // variants.push(claims); + // variants.push(list_stakers); + } + _ => { + return syn::Error::new( + ast.ident.span(), + "voting query types can not be only be derived for enums", + ) + .to_compile_error() + .into() + } + }; + + quote! { + #ast + } + .into() +} + +/// Adds the necessary fields to an enum such that it implements the +/// interface needed to be a voting module with a token. +/// +/// For example: +/// +/// ``` +/// use cwd_macros::token_query; +/// +/// #[token_query] +/// enum QueryMsg {} +/// ``` +/// +/// Will transform the enum to: +/// +/// ``` +/// enum QueryMsg { +/// TokenContract {}, +/// } +/// ``` +/// +/// Note that other derive macro invocations must occur after this +/// procedural macro as they may depend on the new fields. For +/// example, the following will fail becase the `Clone` derivation +/// occurs before the addition of the field. +/// +/// ```compile_fail +/// use cwd_macros::token_query; +/// +/// #[derive(Clone)] +/// #[token_query] +/// #[allow(dead_code)] +/// enum Test { +/// Foo, +/// Bar(u64), +/// Baz { foo: u64 }, +/// } +/// ``` +#[proc_macro_attribute] +pub fn token_query(metadata: TokenStream, input: TokenStream) -> TokenStream { + // Make sure that no arguments were passed in. + let args = parse_macro_input!(metadata as AttributeArgs); + if let Some(first_arg) = args.first() { + return syn::Error::new_spanned(first_arg, "token query macro takes no arguments") + .to_compile_error() + .into(); + } + + let mut ast: DeriveInput = parse_macro_input!(input); + match &mut ast.data { + syn::Data::Enum(DataEnum { variants, .. }) => { + let info: Variant = syn::parse2(quote! { TokenContract {} }).unwrap(); + + variants.push(info); + } + _ => { + return syn::Error::new( + ast.ident.span(), + "token query types can not be only be derived for enums", + ) + .to_compile_error() + .into() + } + }; + + quote! { + #ast + } + .into() +} + +/// Adds the necessary fields to an enum such that it implements the +/// interface needed to be a voting module that has an +/// active check threshold. +/// +/// For example: +/// +/// ``` +/// use cwd_macros::active_query; +/// +/// #[active_query] +/// enum QueryMsg {} +/// ``` +/// +/// Will transform the enum to: +/// +/// ``` +/// enum QueryMsg { +/// IsActive {}, +/// } +/// ``` +/// +/// Note that other derive macro invocations must occur after this +/// procedural macro as they may depend on the new fields. For +/// example, the following will fail becase the `Clone` derivation +/// occurs before the addition of the field. +/// +/// ```compile_fail +/// use cwd_macros::active_query; +/// +/// #[derive(Clone)] +/// #[active_query] +/// #[allow(dead_code)] +/// enum Test { +/// Foo, +/// Bar(u64), +/// Baz { foo: u64 }, +/// } +/// ``` +#[proc_macro_attribute] +pub fn active_query(metadata: TokenStream, input: TokenStream) -> TokenStream { + // Make sure that no arguments were passed in. + let args = parse_macro_input!(metadata as AttributeArgs); + if let Some(first_arg) = args.first() { + return syn::Error::new_spanned(first_arg, "token query macro takes no arguments") + .to_compile_error() + .into(); + } + + let mut ast: DeriveInput = parse_macro_input!(input); + match &mut ast.data { + syn::Data::Enum(DataEnum { variants, .. }) => { + let info: Variant = syn::parse2(quote! { IsActive {} }).unwrap(); + + variants.push(info); + } + _ => { + return syn::Error::new( + ast.ident.span(), + "token query types can not be only be derived for enums", + ) + .to_compile_error() + .into() + } + }; + + quote! { + #ast + } + .into() +} + +/// Adds the necessary fields to an enum such that it implements the +/// interface needed to be a module that has an +/// info query. +/// +/// For example: +/// +/// ``` +/// use cwd_macros::info_query; +/// +/// #[info_query] +/// enum QueryMsg {} +/// ``` +/// +/// Will transform the enum to: +/// +/// ``` +/// enum QueryMsg { +/// Info {}, +/// } +/// ``` +/// +/// Note that other derive macro invocations must occur after this +/// procedural macro as they may depend on the new fields. For +/// example, the following will fail becase the `Clone` derivation +/// occurs before the addition of the field. +/// +/// ```compile_fail +/// use cwd_macros::info_query; +/// +/// #[derive(Clone)] +/// #[info_query] +/// #[allow(dead_code)] +/// enum Test { +/// Foo, +/// Bar(u64), +/// Baz { foo: u64 }, +/// } +/// ``` +#[proc_macro_attribute] +pub fn info_query(metadata: TokenStream, input: TokenStream) -> TokenStream { + // Make sure that no arguments were passed in. + let args = parse_macro_input!(metadata as AttributeArgs); + if let Some(first_arg) = args.first() { + return syn::Error::new_spanned(first_arg, "info query macro takes no arguments") + .to_compile_error() + .into(); + } + + let mut ast: DeriveInput = parse_macro_input!(input); + match &mut ast.data { + syn::Data::Enum(DataEnum { variants, .. }) => { + let info: Variant = syn::parse2(quote! { Info {} }).unwrap(); + + variants.push(info); + } + _ => { + return syn::Error::new( + ast.ident.span(), + "info query types can not be only be derived for enums", + ) + .to_compile_error() + .into() + } + }; + + quote! { + #ast + } + .into() +} + +/// Adds the necessary fields to an enum such that it implements the +/// interface needed to be a proposal module. +/// +/// For example: +/// +/// ``` +/// use cwd_macros::proposal_module_query; +/// +/// #[proposal_module_query] +/// enum QueryMsg {} +/// ``` +/// +/// Will transform the enum to: +/// +/// ``` +/// enum QueryMsg { +/// Dao {}, +/// } +/// ``` +/// +/// Note that other derive macro invocations must occur after this +/// procedural macro as they may depend on the new fields. For +/// example, the following will fail becase the `Clone` derivation +/// occurs before the addition of the field. +/// +/// ```compile_fail +/// use cwd_macros::proposal_module_query; +/// +/// #[derive(Clone)] +/// #[proposal_module_query] +/// #[allow(dead_code)] +/// enum Test { +/// Foo, +/// Bar(u64), +/// Baz { foo: u64 }, +/// } +/// ``` +#[proc_macro_attribute] +pub fn proposal_module_query(metadata: TokenStream, input: TokenStream) -> TokenStream { + // Make sure that no arguments were passed in. + let args = parse_macro_input!(metadata as AttributeArgs); + if let Some(first_arg) = args.first() { + return syn::Error::new_spanned(first_arg, "govmod query macro takes no arguments") + .to_compile_error() + .into(); + } + + let mut ast: DeriveInput = parse_macro_input!(input); + match &mut ast.data { + syn::Data::Enum(DataEnum { variants, .. }) => { + let dao: Variant = syn::parse2(quote! { Dao {} }).unwrap(); + + variants.push(dao); + } + _ => { + return syn::Error::new( + ast.ident.span(), + "govmod query types can not be only be derived for enums", + ) + .to_compile_error() + .into() + } + }; + + quote! { + #ast + } + .into() +} + +/// Limits the number of variants allowed on an enum at compile +/// time. For example, the following will not compile: +/// +/// ```compile_fail +/// use cwd_macros::limit_variant_count; +/// +/// #[limit_variant_count(1)] +/// enum Two { +/// One {}, +/// Two {}, +/// } +/// ``` +#[proc_macro_attribute] +pub fn limit_variant_count(metadata: TokenStream, input: TokenStream) -> TokenStream { + let args = parse_macro_input!(metadata as AttributeArgs); + if args.len() != 1 { + panic!("macro takes one argument. ex: `#[limit_variant_count(4)]`") + } + + let limit: usize = if let syn::NestedMeta::Lit(syn::Lit::Int(unparsed)) = args.first().unwrap() + { + match unparsed.base10_parse() { + Ok(limit) => limit, + Err(e) => return e.to_compile_error().into(), + } + } else { + return syn::Error::new_spanned(args[0].clone(), "argument should be an integer literal") + .to_compile_error() + .into(); + }; + + let ast: DeriveInput = parse_macro_input!(input); + match ast.data { + syn::Data::Enum(DataEnum { ref variants, .. }) => { + if variants.len() > limit { + return syn::Error::new_spanned( + variants[limit].clone(), + format!("this enum's variant count is limited to {}", limit), + ) + .to_compile_error() + .into(); + } + } + _ => { + return syn::Error::new( + ast.ident.span(), + "limit_variant_count may only be derived for enums", + ) + .to_compile_error() + .into() + } + }; + + quote! { + #ast + } + .into() +} diff --git a/packages/cwd-macros/tests/govmod.rs b/packages/cwd-macros/tests/govmod.rs new file mode 100644 index 00000000..00a331dc --- /dev/null +++ b/packages/cwd-macros/tests/govmod.rs @@ -0,0 +1,20 @@ +use cwd_macros::proposal_module_query; + +#[proposal_module_query] +#[derive(Clone)] +#[allow(dead_code)] +enum Test { + Foo, + Bar(u64), + Baz { foo: u64 }, +} + +#[test] +fn proposal_module_query_derive() { + let test = Test::Dao {}; + + // If this compiles we have won. + match test { + Test::Foo | Test::Bar(_) | Test::Baz { .. } | Test::Dao {} => "yay", + }; +} diff --git a/packages/cwd-macros/tests/voting.rs b/packages/cwd-macros/tests/voting.rs new file mode 100644 index 00000000..3a82716d --- /dev/null +++ b/packages/cwd-macros/tests/voting.rs @@ -0,0 +1,37 @@ +use cwd_macros::{info_query, voting_query}; + +/// enum for testing. Important that this derives things / has other +/// attributes so we can be sure we aren't messing with other macros +/// with ours. +#[voting_query] +#[info_query] +#[derive(Clone)] +#[allow(dead_code)] +enum Test { + Foo, + Bar(u64), + Baz { foo: u64 }, +} + +#[test] +fn voting_query_derive() { + let _test = Test::VotingPowerAtHeight { + address: "foo".to_string(), + height: Some(10), + }; + + let test = Test::TotalPowerAtHeight { height: Some(10) }; + + // If this compiles we have won. + match test { + Test::Foo + | Test::Bar(_) + | Test::Baz { .. } + | Test::TotalPowerAtHeight { height: _ } + | Test::VotingPowerAtHeight { + height: _, + address: _, + } + | Test::Info {} => "yay", + }; +} diff --git a/packages/cwd-pre-propose-base/.cargo/config b/packages/cwd-pre-propose-base/.cargo/config new file mode 100644 index 00000000..336b618a --- /dev/null +++ b/packages/cwd-pre-propose-base/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/packages/cwd-pre-propose-base/Cargo.toml b/packages/cwd-pre-propose-base/Cargo.toml new file mode 100644 index 00000000..e535e026 --- /dev/null +++ b/packages/cwd-pre-propose-base/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "cwd-pre-propose-base" +version = "0.2.0" +edition = "2021" +authors = ["ekez ekez@withoutdoing.com"] +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A package for implementing pre-propose modules." + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query WASM exports +library = [] + +[dependencies] +cosmwasm-std = "1.0.0" +cw-storage-plus = "0.13.2" +cw2 = "0.13.2" +schemars = "0.8.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.31" } +cwd-voting = { path = "../cwd-voting" } +cwd-proposal-hooks = { path = "../cwd-proposal-hooks" } +cwd-interface = { path = "../cwd-interface" } +cw-denom = { path = "../cw-denom" } + +[dev-dependencies] +cosmwasm-schema = "1.0.0" +cw-multi-test = "0.13.2" diff --git a/packages/cwd-pre-propose-base/README.md b/packages/cwd-pre-propose-base/README.md new file mode 100644 index 00000000..0895d860 --- /dev/null +++ b/packages/cwd-pre-propose-base/README.md @@ -0,0 +1,11 @@ +# Pre-Propose Base + +This provides a base package that may be used to implement pre-propose +modules. It is modeled after the +[cw721-base](https://github.com/CosmWasm/cw-nfts/tree/27ffdc6c24c2d173be6c677d04bec1420191184d/contracts/cw721-base) +contract for implementing NFT contracts. + +See the +[pre-propose-single](../../contracts/pre-propose/cwd-pre-propose-single) +contract for an example of using this package to implement a proposal +module with deposits. diff --git a/packages/cwd-pre-propose-base/src/error.rs b/packages/cwd-pre-propose-base/src/error.rs new file mode 100644 index 00000000..a0bcef47 --- /dev/null +++ b/packages/cwd-pre-propose-base/src/error.rs @@ -0,0 +1,35 @@ +use cosmwasm_std::StdError; +use cw_denom::DenomError; +use thiserror::Error; + +use cwd_voting::{deposit::DepositError, status::Status}; + +#[derive(Error, Debug, PartialEq)] +pub enum PreProposeError { + #[error(transparent)] + Std(#[from] StdError), + + #[error(transparent)] + Denom(#[from] DenomError), + + #[error(transparent)] + Deposit(#[from] DepositError), + + #[error("Message sender is not proposal module")] + NotModule {}, + + #[error("Message sender is not dao")] + NotDao {}, + + #[error("You must be a member of this DAO (have voting power) to create a proposal")] + NotMember {}, + + #[error("No denomination for withdrawal. specify a denomination to withdraw")] + NoWithdrawalDenom {}, + + #[error("Nothing to withdraw")] + NothingToWithdraw {}, + + #[error("Proposal status ({status}) not closed or executed")] + NotClosedOrExecuted { status: Status }, +} diff --git a/packages/cwd-pre-propose-base/src/execute.rs b/packages/cwd-pre-propose-base/src/execute.rs new file mode 100644 index 00000000..a196d8d2 --- /dev/null +++ b/packages/cwd-pre-propose-base/src/execute.rs @@ -0,0 +1,309 @@ +use cosmwasm_std::{ + to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, WasmMsg, +}; + +use cw2::set_contract_version; + +use cw_denom::UncheckedDenom; +use cwd_interface::voting::{Query as CwCoreQuery, VotingPowerAtHeightResponse}; +use cwd_voting::{ + deposit::{DepositRefundPolicy, UncheckedDepositInfo}, + status::Status, +}; +use serde::Serialize; + +use crate::{ + error::PreProposeError, + msg::{DepositInfoResponse, ExecuteMsg, InstantiateMsg, QueryMsg}, + state::{Config, PreProposeContract}, +}; + +const CONTRACT_NAME: &str = "crates.io::cwd-pre-propose-base"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +impl PreProposeContract +where + ProposalMessage: Serialize, +{ + pub fn instantiate( + &self, + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + // The proposal module instantiates us. We're + // making limited assumptions here. The only way to associate + // a deposit module with a proposal module is for the proposal + // module to instantiate it. + self.proposal_module.save(deps.storage, &info.sender)?; + + // Query the proposal module for its DAO. + let dao: Addr = deps + .querier + .query_wasm_smart(info.sender.clone(), &CwCoreQuery::Dao {})?; + + self.dao.save(deps.storage, &dao)?; + + let deposit_info = msg + .deposit_info + .map(|info| info.into_checked(deps.as_ref(), dao.clone())) + .transpose()?; + + let config = Config { + deposit_info, + open_proposal_submission: msg.open_proposal_submission, + }; + + self.config.save(deps.storage, &config)?; + + Ok(Response::default() + .add_attribute("method", "instantiate") + .add_attribute("proposal_module", info.sender.into_string()) + .add_attribute("deposit_info", format!("{:?}", config.deposit_info)) + .add_attribute( + "open_proposal_submission", + config.open_proposal_submission.to_string(), + ) + .add_attribute("dao", dao)) + } + + pub fn execute( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, + ) -> Result { + match msg { + ExecuteMsg::Propose { msg } => self.execute_propose(deps.as_ref(), env, info, msg), + ExecuteMsg::UpdateConfig { + deposit_info, + open_proposal_submission, + } => self.execute_update_config(deps, info, deposit_info, open_proposal_submission), + ExecuteMsg::Withdraw { denom } => { + self.execute_withdraw(deps.as_ref(), env, info, denom) + } + ExecuteMsg::ProposalCreatedHook { + proposal_id, + proposer, + } => self.execute_proposal_created_hook(deps, info, proposal_id, proposer), + ExecuteMsg::ProposalCompletedHook { + proposal_id, + new_status, + } => self.execute_proposal_completed_hook(deps.as_ref(), info, proposal_id, new_status), + } + } + + pub fn query(&self, deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::ProposalModule {} => to_binary(&self.proposal_module.load(deps.storage)?), + QueryMsg::Dao {} => to_binary(&self.dao.load(deps.storage)?), + QueryMsg::Config {} => to_binary(&self.config.load(deps.storage)?), + QueryMsg::DepositInfo { proposal_id } => { + let (deposit_info, proposer) = self.deposits.load(deps.storage, proposal_id)?; + to_binary(&DepositInfoResponse { + deposit_info, + proposer, + }) + } + } + } + + pub fn execute_propose( + &self, + deps: Deps, + env: Env, + info: MessageInfo, + msg: ProposalMessage, + ) -> Result { + let config = self.config.load(deps.storage)?; + + if !config.open_proposal_submission { + let dao = self.dao.load(deps.storage)?; + let voting_power: VotingPowerAtHeightResponse = deps.querier.query_wasm_smart( + dao.into_string(), + &CwCoreQuery::VotingPowerAtHeight { + address: info.sender.to_string(), + height: None, + }, + )?; + if voting_power.power.is_zero() { + return Err(PreProposeError::NotMember {}); + } + } + + let deposit_messages = if let Some(ref deposit_info) = config.deposit_info { + deposit_info.check_native_deposit_paid(&info)?; + deposit_info.get_take_deposit_messages(&info.sender, &env.contract.address)? + } else { + vec![] + }; + + let proposal_module = self.proposal_module.load(deps.storage)?; + let propose_messsage = WasmMsg::Execute { + contract_addr: proposal_module.into_string(), + msg: to_binary(&msg)?, + funds: vec![], + }; + + Ok(Response::default() + .add_attribute("method", "execute_propose") + .add_attribute("sender", info.sender) + .add_attribute("deposit_info", to_binary(&config.deposit_info)?.to_string()) + .add_messages(deposit_messages) + .add_message(propose_messsage)) + } + + pub fn execute_update_config( + &self, + deps: DepsMut, + info: MessageInfo, + deposit_info: Option, + open_proposal_submission: bool, + ) -> Result { + let dao = self.dao.load(deps.storage)?; + if info.sender != dao { + Err(PreProposeError::NotDao {}) + } else { + let deposit_info = deposit_info + .map(|d| d.into_checked(deps.as_ref(), dao)) + .transpose()?; + self.config.save( + deps.storage, + &Config { + deposit_info, + open_proposal_submission, + }, + )?; + + Ok(Response::default() + .add_attribute("method", "update_config") + .add_attribute("sender", info.sender)) + } + } + + pub fn execute_withdraw( + &self, + deps: Deps, + env: Env, + info: MessageInfo, + denom: Option, + ) -> Result { + let dao = self.dao.load(deps.storage)?; + if info.sender != dao { + Err(PreProposeError::NotDao {}) + } else { + let denom = match denom { + Some(denom) => Some(denom.into_checked(deps)?), + None => { + let config = self.config.load(deps.storage)?; + config.deposit_info.map(|d| d.denom) + } + }; + match denom { + None => Err(PreProposeError::NoWithdrawalDenom {}), + Some(denom) => { + let balance = denom.query_balance(&deps.querier, &env.contract.address)?; + if balance.is_zero() { + Err(PreProposeError::NothingToWithdraw {}) + } else { + let withdraw_message = denom.get_transfer_to_message(&dao, balance)?; + Ok(Response::default() + .add_message(withdraw_message) + .add_attribute("method", "withdraw") + .add_attribute("receiver", &dao) + .add_attribute("denom", denom.to_string())) + } + } + } + } + } + + pub fn execute_proposal_completed_hook( + &self, + deps: Deps, + info: MessageInfo, + id: u64, + new_status: Status, + ) -> Result { + let proposal_module = self.proposal_module.load(deps.storage)?; + if info.sender != proposal_module { + return Err(PreProposeError::NotModule {}); + } + + // These are the only proposal statuses we handle deposits for. + if new_status != Status::Closed && new_status != Status::Executed { + return Err(PreProposeError::NotClosedOrExecuted { status: new_status }); + } + + match self.deposits.may_load(deps.storage, id)? { + Some((deposit_info, proposer)) => { + let messages = if let Some(ref deposit_info) = deposit_info { + // Refund can be issued if proposal if it is going to + // closed or executed. + let should_refund_to_proposer = (new_status == Status::Closed + && deposit_info.refund_policy == DepositRefundPolicy::Always) + || (new_status == Status::Executed + && deposit_info.refund_policy != DepositRefundPolicy::Never); + + if should_refund_to_proposer { + deposit_info.get_return_deposit_message(&proposer)? + } else { + // If the proposer doesn't get the deposit, the DAO does. + let dao = self.dao.load(deps.storage)?; + deposit_info.get_return_deposit_message(&dao)? + } + } else { + // No deposit info for this proposal. Nothing to do. + vec![] + }; + + Ok(Response::default() + .add_attribute("method", "execute_proposal_completed_hook") + .add_attribute("proposal", id.to_string()) + .add_attribute("deposit_info", to_binary(&deposit_info)?.to_string()) + .add_messages(messages)) + } + + // If we do not have a deposit for this proposal it was + // likely created before we were added to the proposal + // module. In that case, it's not our problem and we just + // do nothing. + None => Ok(Response::default() + .add_attribute("method", "execute_proposal_completed_hook") + .add_attribute("proposal", id.to_string())), + } + } + + pub fn execute_proposal_created_hook( + &self, + deps: DepsMut, + info: MessageInfo, + id: u64, + proposer: String, + ) -> Result { + let proposer = deps.api.addr_validate(&proposer)?; + let proposal_module = self.proposal_module.load(deps.storage)?; + if info.sender != proposal_module { + return Err(PreProposeError::NotModule {}); + } + + // Save the deposit. + // + // It is possibe that a malicious proposal hook could run + // before us and update our config! We don't have to worry + // about this though as the only way to be able to update our + // config is to have root on the code module and if someone + // has that we're totally screwed anyhow. + let config = self.config.load(deps.storage)?; + self.deposits + .save(deps.storage, id, &(config.deposit_info, proposer))?; + + Ok(Response::default() + .add_attribute("method", "execute_new_proposal_hook") + .add_attribute("proposal_id", id.to_string())) + } +} diff --git a/packages/cwd-pre-propose-base/src/lib.rs b/packages/cwd-pre-propose-base/src/lib.rs new file mode 100644 index 00000000..edd273f7 --- /dev/null +++ b/packages/cwd-pre-propose-base/src/lib.rs @@ -0,0 +1,4 @@ +pub mod error; +pub mod execute; +pub mod msg; +pub mod state; diff --git a/packages/cwd-pre-propose-base/src/msg.rs b/packages/cwd-pre-propose-base/src/msg.rs new file mode 100644 index 00000000..943f613c --- /dev/null +++ b/packages/cwd-pre-propose-base/src/msg.rs @@ -0,0 +1,108 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cw_denom::UncheckedDenom; +use cwd_voting::{ + deposit::{CheckedDepositInfo, UncheckedDepositInfo}, + status::Status, +}; + +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct InstantiateMsg { + /// Information about the deposit requirements for this + /// module. None if no deposit. + pub deposit_info: Option, + /// If false, only members (addresses with voting power) may create + /// proposals in the DAO. Otherwise, any address may create a + /// proposal so long as they pay the deposit. + pub open_proposal_submission: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + /// Creates a new proposal in the pre-propose module. MSG will be + /// serialized and used as the proposal creation message. + Propose { msg: ProposalMessage }, + + /// Updates the configuration of this module. This will completely + /// override the existing configuration. This new configuration + /// will only apply to proposals created after the config is + /// updated. Only the DAO may execute this message. + UpdateConfig { + deposit_info: Option, + open_proposal_submission: bool, + }, + + /// Withdraws funds inside of this contract to the message + /// sender. The contracts entire balance for the specifed DENOM is + /// withdrawn to the message sender. Only the DAO may call this + /// method. + /// + /// This is intended only as an escape hatch in the event of a + /// critical bug in this contract or it's proposal + /// module. Withdrawing funds will cause future attempts to return + /// proposal deposits to fail their transactions as the contract + /// will have insufficent balance to return them. In the case of + /// `cw-proposal-single` this transaction failure will cause the + /// module to remove the pre-propose module from its proposal hook + /// receivers. + /// + /// More likely than not, this should NEVER BE CALLED unless a bug + /// in this contract or the proposal module it is associated with + /// has caused it to stop receiving proposal hook messages, or if + /// a critical security vulnerability has been found that allows + /// an attacker to drain proposal deposits. + Withdraw { + /// The denom to withdraw funds for. If no denom is specified, + /// the denomination currently configured for proposal + /// deposits will be used. + /// + /// You may want to specify a denomination here if you are + /// withdrawing funds that were previously accepted for + /// proposal deposits but are not longer used due to an + /// `UpdateConfig` message being executed on the contract. + denom: Option, + }, + + /// Handles proposal hook fired by the associated proposal + /// module when a proposal is created. By default, the base contract will return deposits + /// proposals, when they are closed. + /// when proposals are executed, or, if it is refunding failed + ProposalCreatedHook { proposal_id: u64, proposer: String }, + + /// Handles proposal hook fired by the associated proposal + /// module when a proposal is completed (ie executed or rejected). + /// By default, the base contract will return deposits + /// proposals, when they are closed. + /// when proposals are executed, or, if it is refunding failed + ProposalCompletedHook { + proposal_id: u64, + new_status: Status, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + /// Gets the proposal module that this pre propose module is + /// associated with. Returns `Addr`. + ProposalModule {}, + /// Gets the DAO (cw-dao-core) module this contract is associated + /// with. Returns `Addr`. + Dao {}, + /// Gets the module's configuration. Returns `state::Config`. + Config {}, + /// Gets the deposit info for the proposal identified by + /// PROPOSAL_ID. Returns `DepositInfoResponse`. + DepositInfo { proposal_id: u64 }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct DepositInfoResponse { + /// The deposit that has been paid for the specified proposal. + pub deposit_info: Option, + /// The address that created the proposal. + pub proposer: cosmwasm_std::Addr, +} diff --git a/packages/cwd-pre-propose-base/src/state.rs b/packages/cwd-pre-propose-base/src/state.rs new file mode 100644 index 00000000..b83bf838 --- /dev/null +++ b/packages/cwd-pre-propose-base/src/state.rs @@ -0,0 +1,61 @@ +use std::marker::PhantomData; + +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; +use serde::{Deserialize, Serialize}; + +use cwd_voting::deposit::CheckedDepositInfo; + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +pub struct Config { + /// Information about the deposit required to create a + /// proposal. If `None`, no deposit is required. + pub deposit_info: Option, + /// If false, only members (addresses with voting power) may create + /// proposals in the DAO. Otherwise, any address may create a + /// proposal so long as they pay the deposit. + pub open_proposal_submission: bool, +} + +pub struct PreProposeContract { + /// The proposal module that this module is associated with. + pub proposal_module: Item<'static, Addr>, + /// The DAO (cw-dao-core module) that this module is associated + /// with. + pub dao: Item<'static, Addr>, + /// The configuration for this module. + pub config: Item<'static, Config>, + /// Map between proposal IDs and (deposit, proposer) pairs. + pub deposits: Map<'static, u64, (Option, Addr)>, + + // These types are used in associated functions, but not + // assocaited data. To stop the compiler complaining about unused + // generics, we build this phantom data. + proposal_type: PhantomData, +} + +impl PreProposeContract { + const fn new( + proposal_key: &'static str, + dao_key: &'static str, + config_key: &'static str, + deposits_key: &'static str, + ) -> Self { + Self { + proposal_module: Item::new(proposal_key), + dao: Item::new(dao_key), + config: Item::new(config_key), + deposits: Map::new(deposits_key), + proposal_type: PhantomData, + } + } +} + +impl Default for PreProposeContract { + fn default() -> Self { + // Call into constant function here. Presumably, the compiler + // is clever enough to inline this. This gives us + // "more-or-less" constant evaluation for our default method. + Self::new("proposal_module", "dao", "config", "deposits") + } +} diff --git a/packages/cwd-proposal-hooks/Cargo.toml b/packages/cwd-proposal-hooks/Cargo.toml new file mode 100644 index 00000000..89ff01b5 --- /dev/null +++ b/packages/cwd-proposal-hooks/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cwd-proposal-hooks" +version = "0.2.0" +edition = "2021" +authors = ["Callum Anderson "] +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A package for managing proposal hooks." + +[dependencies] +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +cosmwasm-std = "1.0.0" +cwd-hooks = { path = "../cwd-hooks" } +cwd-voting = { path = "../cwd-voting" } \ No newline at end of file diff --git a/packages/cwd-proposal-hooks/README.md b/packages/cwd-proposal-hooks/README.md new file mode 100644 index 00000000..dd30e1b2 --- /dev/null +++ b/packages/cwd-proposal-hooks/README.md @@ -0,0 +1,4 @@ +# CosmWasm DAO Proposal Hooks + +This package provides an interface for managing and dispatching +proposal hooks from a proposal module. diff --git a/packages/cwd-proposal-hooks/src/lib.rs b/packages/cwd-proposal-hooks/src/lib.rs new file mode 100644 index 00000000..980277a0 --- /dev/null +++ b/packages/cwd-proposal-hooks/src/lib.rs @@ -0,0 +1,95 @@ +use cosmwasm_std::{to_binary, StdResult, Storage, SubMsg, WasmMsg}; +use cwd_hooks::Hooks; +use cwd_voting::reply::mask_proposal_hook_index; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ProposalHookMsg { + NewProposal { + id: u64, + proposer: String, + }, + ProposalStatusChanged { + id: u64, + old_status: String, + new_status: String, + }, +} + +// This is just a helper to properly serialize the above message +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ProposalHookExecuteMsg { + ProposalHook(ProposalHookMsg), +} + +/// Prepares new proposal hook messages. These messages reply on error +/// and have even reply IDs. +/// IDs are set to even numbers to then be interleaved with the vote hooks. +pub fn new_proposal_hooks( + hooks: Hooks, + storage: &dyn Storage, + id: u64, + proposer: &str, +) -> StdResult> { + let msg = to_binary(&ProposalHookExecuteMsg::ProposalHook( + ProposalHookMsg::NewProposal { + id, + proposer: proposer.to_string(), + }, + ))?; + + let mut index: u64 = 0; + let messages = hooks.prepare_hooks(storage, |a| { + let execute = WasmMsg::Execute { + contract_addr: a.to_string(), + msg: msg.clone(), + funds: vec![], + }; + let masked_index = mask_proposal_hook_index(index); + let tmp = SubMsg::reply_on_error(execute, masked_index); + index += 1; + Ok(tmp) + })?; + + Ok(messages) +} + +/// Prepares proposal status hook messages. These messages reply on error +/// and have even reply IDs. +/// IDs are set to even numbers to then be interleaved with the vote hooks. +pub fn proposal_status_changed_hooks( + hooks: Hooks, + storage: &dyn Storage, + id: u64, + old_status: String, + new_status: String, +) -> StdResult> { + if old_status == new_status { + return Ok(vec![]); + } + + let msg = to_binary(&ProposalHookExecuteMsg::ProposalHook( + ProposalHookMsg::ProposalStatusChanged { + id, + old_status, + new_status, + }, + ))?; + let mut index: u64 = 0; + let messages = hooks.prepare_hooks(storage, |a| { + let execute = WasmMsg::Execute { + contract_addr: a.to_string(), + msg: msg.clone(), + funds: vec![], + }; + let masked_index = mask_proposal_hook_index(index); + let tmp = SubMsg::reply_on_error(execute, masked_index); + index += 1; + Ok(tmp) + })?; + + Ok(messages) +} diff --git a/packages/cwd-vote-hooks/Cargo.toml b/packages/cwd-vote-hooks/Cargo.toml new file mode 100644 index 00000000..7a1b8d39 --- /dev/null +++ b/packages/cwd-vote-hooks/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "cwd-vote-hooks" +version = "0.2.0" +edition = "2021" +authors = ["ekez ekez@withoutdoing.com"] +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "A package for managing vote hooks." + +[dependencies] +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +cosmwasm-std = "1.0.0" +cwd-hooks = { path = "../cwd-hooks" } +cwd-voting = { version = "0.2.0", path = "../cwd-voting" } \ No newline at end of file diff --git a/packages/cwd-vote-hooks/README.md b/packages/cwd-vote-hooks/README.md new file mode 100644 index 00000000..29e6b0f9 --- /dev/null +++ b/packages/cwd-vote-hooks/README.md @@ -0,0 +1,4 @@ +# CosmWasm DAO Vote Hooks + +This package provides an interface for managing and dispatching +vote hooks from a proposal module. diff --git a/packages/cwd-vote-hooks/src/lib.rs b/packages/cwd-vote-hooks/src/lib.rs new file mode 100644 index 00000000..d7c56421 --- /dev/null +++ b/packages/cwd-vote-hooks/src/lib.rs @@ -0,0 +1,51 @@ +use cosmwasm_std::{to_binary, StdResult, Storage, SubMsg, WasmMsg}; +use cwd_hooks::Hooks; +use cwd_voting::reply::mask_vote_hook_index; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum VoteHookMsg { + NewVote { + proposal_id: u64, + voter: String, + vote: String, + }, +} + +// This is just a helper to properly serialize the above message +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum VoteHookExecuteMsg { + VoteHook(VoteHookMsg), +} + +/// Prepares new vote hook messages. These messages reply on error +/// and have even reply IDs. +/// IDs are set to odd numbers to then be interleaved with the proposal hooks. +pub fn new_vote_hooks( + hooks: Hooks, + storage: &dyn Storage, + proposal_id: u64, + voter: String, + vote: String, +) -> StdResult> { + let msg = to_binary(&VoteHookExecuteMsg::VoteHook(VoteHookMsg::NewVote { + proposal_id, + voter, + vote, + }))?; + let mut index: u64 = 0; + hooks.prepare_hooks(storage, |a| { + let execute = WasmMsg::Execute { + contract_addr: a.to_string(), + msg: msg.clone(), + funds: vec![], + }; + let masked_index = mask_vote_hook_index(index); + let tmp = SubMsg::reply_on_error(execute, masked_index); + index += 1; + Ok(tmp) + }) +} diff --git a/packages/cwd-voting/Cargo.toml b/packages/cwd-voting/Cargo.toml new file mode 100644 index 00000000..4ac9b80e --- /dev/null +++ b/packages/cwd-voting/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cwd-voting" +version = "0.2.0" +edition = "2021" +authors = ["ekez ekez@withoutdoing.com"] +repository = "https://github.com/DA0-DA0/dao-contracts" +description = "Types and methods for CosmWasm DAO voting." + +[dependencies] +neutron_bindings = { package = "neutron-sdk", version = "0.1.0", git = "https://github.com/neutron-org/neutron-contracts.git" } +cosmwasm-std = { version = "1.0.0" } +schemars = "0.8" +serde = { version = "1.0.147", default-features = false, features = ["derive"] } +thiserror = { version = "1.0" } +cw20 = "0.13" +cwd-interface = { path = "../cwd-interface" } +cwd-macros = { path = "../cwd-macros" } +cwd-core = { path = "../../contracts/cwd-core" } +cw-denom = { path = "../cw-denom" } +cw-utils = "0.13" +cw-storage-plus = "0.13" diff --git a/packages/cwd-voting/README.md b/packages/cwd-voting/README.md new file mode 100644 index 00000000..478c5e24 --- /dev/null +++ b/packages/cwd-voting/README.md @@ -0,0 +1,4 @@ +# CosmWasm DAO Voting + +This package provides types and associated methods for handling voting +in a CosmWasm DAO. diff --git a/packages/cwd-voting/src/deposit.rs b/packages/cwd-voting/src/deposit.rs new file mode 100644 index 00000000..f7e91ae6 --- /dev/null +++ b/packages/cwd-voting/src/deposit.rs @@ -0,0 +1,392 @@ +use cosmwasm_std::{ + to_binary, Addr, CosmosMsg, Deps, MessageInfo, StdError, StdResult, Uint128, WasmMsg, +}; +use cw_utils::{must_pay, PaymentError}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use cw_denom::{CheckedDenom, DenomError, UncheckedDenom}; + +/// Error type for deposit methods. +#[derive(Error, Debug, PartialEq)] +pub enum DepositError { + #[error(transparent)] + Std(#[from] StdError), + + #[error(transparent)] + Payment(#[from] PaymentError), + + #[error(transparent)] + Denom(#[from] DenomError), + + #[error("invalid zero deposit. set the deposit to `None` to have no deposit")] + ZeroDeposit, + + #[error("invalid deposit amount. got ({actual}), expected ({expected})")] + InvalidDeposit { actual: Uint128, expected: Uint128 }, +} + +/// Information about the token to use for proposal deposits. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DepositToken { + /// Use a specific token address as the deposit token. + Token { denom: UncheckedDenom }, + /// Use the token address of the associated DAO's voting + /// module. NOTE: in order to use the token address of the voting + /// module the voting module must (1) use a cw20 token and (2) + /// implement the `TokenContract {}` query type defined by + /// `cwd_macros::token_query`. Failing to implement that + /// and using this option will cause instantiation to fail. + VotingModuleToken {}, +} + +/// Information about the deposit required to create a proposal. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct UncheckedDepositInfo { + /// The address of the token to be used for proposal deposits. + pub denom: DepositToken, + /// The number of tokens that must be deposited to create a + /// proposal. Must be a positive, non-zero number. + pub amount: Uint128, + /// The policy used for refunding deposits on proposal completion. + pub refund_policy: DepositRefundPolicy, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DepositRefundPolicy { + /// Deposits should always be refunded. + Always, + /// Deposits should only be refunded for passed proposals. + OnlyPassed, + /// Deposits should never be refunded. + Never, +} + +/// Counterpart to the `DepositInfo` struct which has been +/// processed. This type should never be constructed literally and +/// should always by built by calling `into_checked` on a +/// `DepositInfo` instance. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CheckedDepositInfo { + /// The address of the cw20 token to be used for proposal + /// deposits. + pub denom: CheckedDenom, + /// The number of tokens that must be deposited to create a + /// proposal. This is validated to be non-zero if this struct is + /// constructed by converted via the `into_checked` method on + /// `DepositInfo`. + pub amount: Uint128, + /// The policy used for refunding proposal deposits. + pub refund_policy: DepositRefundPolicy, +} + +impl UncheckedDepositInfo { + /// Converts deposit info into checked deposit info. + pub fn into_checked(self, deps: Deps, dao: Addr) -> Result { + let Self { + denom, + amount, + refund_policy, + } = self; + // Check that the deposit is non-zero. Modules should make + // deposit information optional and consumers should provide + // `None` when they do not want to have a proposal deposit. + if amount.is_zero() { + return Err(DepositError::ZeroDeposit); + } + + let denom = match denom { + DepositToken::Token { denom } => denom.into_checked(deps), + DepositToken::VotingModuleToken {} => { + let voting_module: Addr = deps + .querier + .query_wasm_smart(dao, &cwd_core::msg::QueryMsg::VotingModule {})?; + // If the voting module has no token this will + // error. This is desirable. + let token_addr: Addr = deps.querier.query_wasm_smart( + voting_module, + &cwd_interface::voting::Query::TokenContract {}, + )?; + // We don't assume here that the voting module has + // returned a valid token. Conversion of the unchecked + // denom into a checked one will do a `TokenInfo {}` + // query. + UncheckedDenom::Cw20(token_addr.into_string()).into_checked(deps) + } + }?; + + Ok(CheckedDepositInfo { + denom, + amount, + refund_policy, + }) + } +} + +impl CheckedDepositInfo { + pub fn check_native_deposit_paid(&self, info: &MessageInfo) -> Result<(), DepositError> { + if let Self { + amount, + denom: CheckedDenom::Native(denom), + .. + } = self + { + // must_pay > may_pay. The method this is getting called + // in is accepting a deposit. It seems likely to me that + // if other payments are here it's a bug in a frontend and + // not an intentional thing. + let paid = must_pay(info, denom)?; + if paid != *amount { + Err(DepositError::InvalidDeposit { + actual: paid, + expected: *amount, + }) + } else { + Ok(()) + } + } else { + // Nothing to do if we're a cw20. + Ok(()) + } + } + + pub fn get_take_deposit_messages( + &self, + depositor: &Addr, + contract: &Addr, + ) -> StdResult> { + let take_deposit_msg: Vec = if let Self { + amount, + denom: CheckedDenom::Cw20(address), + .. + } = self + { + // into_checked() makes sure this isn't the case, but just for + // posterity. + if amount.is_zero() { + vec![] + } else { + vec![WasmMsg::Execute { + contract_addr: address.to_string(), + funds: vec![], + msg: to_binary(&cw20::Cw20ExecuteMsg::TransferFrom { + owner: depositor.to_string(), + recipient: contract.to_string(), + amount: *amount, + })?, + } + .into()] + } + } else { + // Deposits are pushed, not pulled for native + // deposits. See: `check_native_deposit_paid`. + vec![] + }; + Ok(take_deposit_msg) + } + + pub fn get_return_deposit_message(&self, depositor: &Addr) -> StdResult> { + // Should get caught in `into_checked()`, but to be pedantic. + if self.amount.is_zero() { + return Ok(vec![]); + } + let message = self.denom.get_transfer_to_message(depositor, self.amount)?; + Ok(vec![message]) + } +} + +#[cfg(test)] +pub mod tests { + use cosmwasm_std::{coin, coins, testing::mock_info, BankMsg}; + + use super::*; + + const NATIVE_DENOM: &str = "uekez"; + const CW20: &str = "cw20"; + + #[test] + fn test_check_native_deposit_paid_yes() { + let info = mock_info("ekez", &coins(10, NATIVE_DENOM)); + let deposit_info = CheckedDepositInfo { + denom: CheckedDenom::Native(NATIVE_DENOM.to_string()), + amount: Uint128::new(10), + refund_policy: DepositRefundPolicy::Always, + }; + deposit_info.check_native_deposit_paid(&info).unwrap(); + + let mut info = info; + let mut deposit_info = deposit_info; + + // Doesn't matter what we submit if it's a cw20 token. + info.funds = vec![]; + deposit_info.denom = CheckedDenom::Cw20(Addr::unchecked(CW20)); + deposit_info.check_native_deposit_paid(&info).unwrap(); + + info.funds = coins(100, NATIVE_DENOM); + deposit_info.check_native_deposit_paid(&info).unwrap(); + } + + #[test] + fn test_native_deposit_paid_wrong_amount() { + let info = mock_info("ekez", &coins(9, NATIVE_DENOM)); + let deposit_info = CheckedDepositInfo { + denom: CheckedDenom::Native(NATIVE_DENOM.to_string()), + amount: Uint128::new(10), + refund_policy: DepositRefundPolicy::Always, + }; + let err = deposit_info.check_native_deposit_paid(&info).unwrap_err(); + assert_eq!( + err, + DepositError::InvalidDeposit { + actual: Uint128::new(9), + expected: Uint128::new(10) + } + ) + } + + #[test] + fn check_native_deposit_paid_wrong_denom() { + let info = mock_info("ekez", &coins(10, "unotekez")); + let deposit_info = CheckedDepositInfo { + denom: CheckedDenom::Native(NATIVE_DENOM.to_string()), + amount: Uint128::new(10), + refund_policy: DepositRefundPolicy::Always, + }; + let err = deposit_info.check_native_deposit_paid(&info).unwrap_err(); + assert_eq!( + err, + DepositError::Payment(PaymentError::MissingDenom(NATIVE_DENOM.to_string())) + ); + } + + // If you're receiving other denoms in the same place you're + // receiving your deposit you should probably write your own + // package, or you're working on dao dao and can remove this + // one. At the time of writing, other denoms coming in with a + // deposit seems like a frontend bug off. + #[test] + fn check_sending_other_denoms_is_not_allowed() { + let info = mock_info("ekez", &[coin(10, "unotekez"), coin(10, "ekez")]); + let deposit_info = CheckedDepositInfo { + denom: CheckedDenom::Native(NATIVE_DENOM.to_string()), + amount: Uint128::new(10), + refund_policy: DepositRefundPolicy::Always, + }; + + let err = deposit_info.check_native_deposit_paid(&info).unwrap_err(); + assert_eq!(err, DepositError::Payment(PaymentError::MultipleDenoms {})); + } + + #[test] + fn check_native_deposit_paid_no_denoms() { + let info = mock_info("ekez", &[]); + let deposit_info = CheckedDepositInfo { + denom: CheckedDenom::Native(NATIVE_DENOM.to_string()), + amount: Uint128::new(10), + refund_policy: DepositRefundPolicy::Always, + }; + let err = deposit_info.check_native_deposit_paid(&info).unwrap_err(); + assert_eq!(err, DepositError::Payment(PaymentError::NoFunds {})); + } + + #[test] + fn test_get_take_deposit_messages() { + // Does nothing if a native token is being used. + let mut deposit_info = CheckedDepositInfo { + denom: CheckedDenom::Native(NATIVE_DENOM.to_string()), + amount: Uint128::new(10), + refund_policy: DepositRefundPolicy::Always, + }; + let messages = deposit_info + .get_take_deposit_messages(&Addr::unchecked("ekez"), &Addr::unchecked(CW20)) + .unwrap(); + assert_eq!(messages, vec![]); + + // Does something for cw20s. + deposit_info.denom = CheckedDenom::Cw20(Addr::unchecked(CW20)); + let messages = deposit_info + .get_take_deposit_messages(&Addr::unchecked("ekez"), &Addr::unchecked("contract")) + .unwrap(); + assert_eq!( + messages, + vec![CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: CW20.to_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::TransferFrom { + owner: "ekez".to_string(), + recipient: "contract".to_string(), + amount: Uint128::new(10) + }) + .unwrap(), + funds: vec![], + })] + ); + + // Does nothing when the amount is zero (this would cause the + // tx to fail for a valid cw20). + deposit_info.amount = Uint128::zero(); + let messages = deposit_info + .get_take_deposit_messages(&Addr::unchecked("ekez"), &Addr::unchecked(CW20)) + .unwrap(); + assert_eq!(messages, vec![]); + } + + #[test] + fn test_get_return_deposit_message_native() { + let mut deposit_info = CheckedDepositInfo { + denom: CheckedDenom::Native(NATIVE_DENOM.to_string()), + amount: Uint128::new(10), + refund_policy: DepositRefundPolicy::Always, + }; + let messages = deposit_info + .get_return_deposit_message(&Addr::unchecked("ekez")) + .unwrap(); + assert_eq!( + messages, + vec![CosmosMsg::Bank(BankMsg::Send { + to_address: "ekez".to_string(), + amount: coins(10, "uekez") + })] + ); + + // Don't fire a message if there is nothing to send! + deposit_info.amount = Uint128::zero(); + let messages = deposit_info + .get_return_deposit_message(&Addr::unchecked("ekez")) + .unwrap(); + assert_eq!(messages, vec![]); + } + + #[test] + fn test_get_return_deposit_message_cw20() { + let mut deposit_info = CheckedDepositInfo { + denom: CheckedDenom::Cw20(Addr::unchecked(CW20)), + amount: Uint128::new(10), + refund_policy: DepositRefundPolicy::Always, + }; + let messages = deposit_info + .get_return_deposit_message(&Addr::unchecked("ekez")) + .unwrap(); + assert_eq!( + messages, + vec![CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: CW20.to_string(), + msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { + recipient: "ekez".to_string(), + amount: Uint128::new(10) + }) + .unwrap(), + funds: vec![] + })] + ); + + // Don't fire a message if there is nothing to send! + deposit_info.amount = Uint128::zero(); + let messages = deposit_info + .get_return_deposit_message(&Addr::unchecked("ekez")) + .unwrap(); + assert_eq!(messages, vec![]); + } +} diff --git a/packages/cwd-voting/src/error.rs b/packages/cwd-voting/src/error.rs new file mode 100644 index 00000000..18843be1 --- /dev/null +++ b/packages/cwd-voting/src/error.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum VotingError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("min_voting_period and max_voting_period must have the same units (height or time)")] + DurationUnitsConflict {}, + + #[error("Min voting period must be less than or equal to max voting period")] + InvalidMinVotingPeriod {}, +} diff --git a/packages/cwd-voting/src/lib.rs b/packages/cwd-voting/src/lib.rs new file mode 100644 index 00000000..8c90f894 --- /dev/null +++ b/packages/cwd-voting/src/lib.rs @@ -0,0 +1,9 @@ +pub mod deposit; +pub mod error; +pub mod multiple_choice; +pub mod pre_propose; +pub mod proposal; +pub mod reply; +pub mod status; +pub mod threshold; +pub mod voting; diff --git a/packages/cwd-voting/src/multiple_choice.rs b/packages/cwd-voting/src/multiple_choice.rs new file mode 100644 index 00000000..5303773d --- /dev/null +++ b/packages/cwd-voting/src/multiple_choice.rs @@ -0,0 +1,258 @@ +use cosmwasm_std::{CosmosMsg, StdError, StdResult, Uint128}; +use neutron_bindings::bindings::msg::NeutronMsg; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::threshold::{validate_quorum, PercentageThreshold, ThresholdError}; + +/// Maximum number of choices for multiple choice votes +pub const MAX_NUM_CHOICES: u32 = 10; +const NONE_OPTION_DESCRIPTION: &str = "None of the above"; + +/// Determines how many choices may be selected. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum VotingStrategy { + SingleChoice { quorum: PercentageThreshold }, +} + +impl VotingStrategy { + pub fn validate(&self) -> Result<(), ThresholdError> { + match self { + VotingStrategy::SingleChoice { quorum } => validate_quorum(quorum), + } + } + + pub fn get_quorum(&self) -> PercentageThreshold { + match self { + VotingStrategy::SingleChoice { quorum } => *quorum, + } + } +} + +/// A multiple choice vote, picking the desired option +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, JsonSchema, Debug)] +pub struct MultipleChoiceVote { + // A vote indicates which option the user has selected. + pub option_id: u32, +} + +impl std::fmt::Display for MultipleChoiceVote { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.option_id) + } +} + +// Holds the vote weights for each option +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct MultipleChoiceVotes { + // Vote counts is a vector of integers indicating the vote weight for each option + // (the index corresponds to the option). + pub vote_weights: Vec, +} + +impl MultipleChoiceVotes { + /// Sum of all vote weights + pub fn total(&self) -> Uint128 { + self.vote_weights.iter().sum() + } + + // Add a vote to the tally + pub fn add_vote(&mut self, vote: MultipleChoiceVote, weight: Uint128) -> StdResult<()> { + self.vote_weights[vote.option_id as usize] = self.vote_weights[vote.option_id as usize] + .checked_add(weight) + .map_err(StdError::overflow)?; + Ok(()) + } + + // Remove a vote from the tally + pub fn remove_vote(&mut self, vote: MultipleChoiceVote, weight: Uint128) -> StdResult<()> { + self.vote_weights[vote.option_id as usize] = self.vote_weights[vote.option_id as usize] + .checked_sub(weight) + .map_err(StdError::overflow)?; + Ok(()) + } + + // Default tally of zero for all multiple choice options + pub fn zero(num_choices: usize) -> Self { + Self { + vote_weights: vec![Uint128::zero(); num_choices], + } + } +} + +/// Represents the type of Multiple choice option. "None of the above" has a special +/// type for example. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub enum MultipleChoiceOptionType { + /// Choice that represents selecting none of the options; still counts toward quorum + /// and allows proposals with all bad options to be voted against. + None, + Standard, +} + +/// Represents unchecked multipl choice options +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MultipleChoiceOptions { + pub options: Vec, +} + +/// Unchecked multiple choice option +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MultipleChoiceOption { + pub description: String, + pub msgs: Option>>, +} + +/// Multiple choice options that have been verified for correctness, and have all fields +/// necessary for voting. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CheckedMultipleChoiceOptions { + pub options: Vec, +} + +/// A verified option that has all fields needed for voting. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CheckedMultipleChoiceOption { + // This is the index of the option in both the vote_weights and proposal.choices vectors. + // Workaround due to not being able to use HashMaps in Cosmwasm. + pub index: u32, + pub option_type: MultipleChoiceOptionType, + pub description: String, + pub msgs: Option>>, + pub vote_count: Uint128, +} + +impl MultipleChoiceOptions { + pub fn into_checked(self) -> StdResult { + if self.options.len() < 2 || self.options.len() > MAX_NUM_CHOICES as usize { + return Err(StdError::GenericErr { + msg: "Wrong number of choices".to_string(), + }); + } + + let mut checked_options: Vec = + Vec::with_capacity(self.options.len() + 1); + + // Iterate through choices and save the index and option type for each + self.options + .into_iter() + .enumerate() + .for_each(|(idx, choice)| { + let checked_option = CheckedMultipleChoiceOption { + index: idx as u32, + option_type: MultipleChoiceOptionType::Standard, + description: choice.description, + msgs: choice.msgs, + vote_count: Uint128::zero(), + }; + checked_options.push(checked_option); + }); + + // Add a "None of the above" option, required for every multiple choice proposal. + let none_option = CheckedMultipleChoiceOption { + index: (checked_options.capacity() - 1) as u32, + option_type: MultipleChoiceOptionType::None, + description: NONE_OPTION_DESCRIPTION.to_string(), + msgs: None, + vote_count: Uint128::zero(), + }; + + checked_options.push(none_option); + + let options = CheckedMultipleChoiceOptions { + options: checked_options, + }; + Ok(options) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_display_multiple_choice_vote() { + let vote = MultipleChoiceVote { option_id: 0 }; + assert_eq!("0", vote.to_string()) + } + + #[test] + fn test_multiple_choice_votes() { + let mut votes = MultipleChoiceVotes { + vote_weights: vec![Uint128::new(10), Uint128::new(100)], + }; + let total = votes.total(); + assert_eq!(total, Uint128::new(110)); + + votes + .add_vote(MultipleChoiceVote { option_id: 0 }, Uint128::new(10)) + .unwrap(); + let total = votes.total(); + assert_eq!(total, Uint128::new(120)); + + votes + .remove_vote(MultipleChoiceVote { option_id: 0 }, Uint128::new(20)) + .unwrap(); + votes + .remove_vote(MultipleChoiceVote { option_id: 1 }, Uint128::new(100)) + .unwrap(); + + assert_eq!(votes, MultipleChoiceVotes::zero(2)) + } + + #[test] + fn test_into_checked() { + let options = vec![ + super::MultipleChoiceOption { + description: "multiple choice option 1".to_string(), + msgs: None, + }, + super::MultipleChoiceOption { + description: "multiple choice option 2".to_string(), + msgs: None, + }, + ]; + + let mc_options = super::MultipleChoiceOptions { options }; + + let checked_mc_options = mc_options.into_checked().unwrap(); + assert_eq!(checked_mc_options.options.len(), 3); + assert_eq!( + checked_mc_options.options[0].option_type, + super::MultipleChoiceOptionType::Standard + ); + assert_eq!( + checked_mc_options.options[0].description, + "multiple choice option 1", + ); + assert_eq!( + checked_mc_options.options[1].option_type, + super::MultipleChoiceOptionType::Standard + ); + assert_eq!( + checked_mc_options.options[1].description, + "multiple choice option 2", + ); + assert_eq!( + checked_mc_options.options[2].option_type, + super::MultipleChoiceOptionType::None + ); + assert_eq!( + checked_mc_options.options[2].description, + super::NONE_OPTION_DESCRIPTION, + ); + } + + #[should_panic(expected = "Wrong number of choices")] + #[test] + fn test_into_checked_wrong_num_choices() { + let options = vec![super::MultipleChoiceOption { + description: "multiple choice option 1".to_string(), + msgs: None, + }]; + + let mc_options = super::MultipleChoiceOptions { options }; + mc_options.into_checked().unwrap(); + } +} diff --git a/packages/cwd-voting/src/pre_propose.rs b/packages/cwd-voting/src/pre_propose.rs new file mode 100644 index 00000000..feae8c9a --- /dev/null +++ b/packages/cwd-voting/src/pre_propose.rs @@ -0,0 +1,147 @@ +//! Types related to the pre-propose module. Motivation: +//! . + +use cosmwasm_std::{Addr, Empty, StdResult, SubMsg}; +use cwd_interface::ModuleInstantiateInfo; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::reply::pre_propose_module_instantiation_id; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub enum PreProposeInfo { + /// Anyone may create a proposal free of charge. + AnyoneMayPropose {}, + /// The module specified in INFO has exclusive rights to proposal + /// creation. + ModuleMayPropose { info: ModuleInstantiateInfo }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub enum ProposalCreationPolicy { + /// Anyone may create a proposal, free of charge. + Anyone {}, + /// Only ADDR may create proposals. It is expected that ADDR is a + /// pre-propose module, though we only require that it is a valid + /// address. + Module { addr: Addr }, +} + +impl ProposalCreationPolicy { + /// Determines if CREATOR is permitted to create a + /// proposal. Returns true if so and false otherwise. + pub fn is_permitted(&self, creator: &Addr) -> bool { + match self { + Self::Anyone {} => true, + Self::Module { addr } => creator == addr, + } + } +} + +impl PreProposeInfo { + pub fn into_initial_policy_and_messages( + self, + dao_address: Addr, + ) -> StdResult<(ProposalCreationPolicy, Vec>)> { + Ok(match self { + Self::AnyoneMayPropose {} => (ProposalCreationPolicy::Anyone {}, vec![]), + Self::ModuleMayPropose { info } => ( + // Anyone can propose will be set until instantiation succeeds, then + // `ModuleMayPropose` will be set. This ensures that we fail open + // upon instantiation failure. + ProposalCreationPolicy::Anyone {}, + vec![SubMsg::reply_on_success( + info.into_wasm_msg(dao_address), + pre_propose_module_instantiation_id(), + )], + ), + }) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{to_binary, WasmMsg}; + + use super::*; + + #[test] + fn test_anyone_is_permitted() { + let policy = ProposalCreationPolicy::Anyone {}; + + // I'll actually stand by this as a legit testing strategy + // when looking at string inputs. If anything is going to + // screw things up, its weird unicode characters. + // + // For example, my langauge server explodes for me if I use + // the granddaddy of weird unicode characters, the large + // family: ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ. + // + // The family emoji you see is actually a combination of + // individual person emojis. You can browse the whole + // collection of combo emojis here: + // . + // + // You may also enjoy this PDF wherein there is a discussion + // about the feesability of supporting all 7230 possible + // combos of family emojis: + // . + for c in '๐Ÿ˜€'..'๐Ÿคฃ' { + assert!(policy.is_permitted(&Addr::unchecked(c.to_string()))) + } + } + + #[test] + fn test_module_is_permitted() { + let policy = ProposalCreationPolicy::Module { + addr: Addr::unchecked("deposit_module"), + }; + assert!(!policy.is_permitted(&Addr::unchecked("๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"))); + assert!(policy.is_permitted(&Addr::unchecked("deposit_module"))); + } + + #[test] + fn test_pre_any_conversion() { + let info = PreProposeInfo::AnyoneMayPropose {}; + let (policy, messages) = info + .into_initial_policy_and_messages(Addr::unchecked("๐Ÿ˜ƒ")) + .unwrap(); + assert_eq!(policy, ProposalCreationPolicy::Anyone {}); + assert!(messages.is_empty()) + } + + #[test] + fn test_pre_module_conversion() { + let info = PreProposeInfo::ModuleMayPropose { + info: ModuleInstantiateInfo { + code_id: 42, + msg: to_binary("foo").unwrap(), + admin: None, + label: "pre-propose-9000".to_string(), + }, + }; + let (policy, messages) = info + .into_initial_policy_and_messages(Addr::unchecked("๐Ÿฅต")) + .unwrap(); + + // In this case the package is expected to allow anyone to + // create a proposal (fail-open), and provide some messages + // that, when handled in a `reply` handler will set the + // creation policy to a specific module. + assert_eq!(policy, ProposalCreationPolicy::Anyone {}); + assert_eq!(messages.len(), 1); + assert_eq!( + messages[0], + SubMsg::reply_on_success( + WasmMsg::Instantiate { + admin: None, + code_id: 42, + msg: to_binary("foo").unwrap(), + funds: vec![], + label: "pre-propose-9000".to_string() + }, + crate::reply::pre_propose_module_instantiation_id() + ) + ) + } +} diff --git a/packages/cwd-voting/src/proposal.rs b/packages/cwd-voting/src/proposal.rs new file mode 100644 index 00000000..2fde7755 --- /dev/null +++ b/packages/cwd-voting/src/proposal.rs @@ -0,0 +1,12 @@ +use cosmwasm_std::Addr; + +use crate::status::Status; + +/// Default limit for proposal pagination. +pub const DEFAULT_LIMIT: u64 = 30; +pub const MAX_PROPOSAL_SIZE: u64 = 30_000; + +pub trait Proposal { + fn proposer(&self) -> Addr; + fn status(&self) -> Status; +} diff --git a/packages/cwd-voting/src/reply.rs b/packages/cwd-voting/src/reply.rs new file mode 100644 index 00000000..7f2fd2c2 --- /dev/null +++ b/packages/cwd-voting/src/reply.rs @@ -0,0 +1,121 @@ +use cwd_macros::limit_variant_count; + +const FAILED_PROPOSAL_EXECUTION_MASK: u64 = 0b000; +const FAILED_PROPOSAL_HOOK_MASK: u64 = 0b001; +const FAILED_VOTE_HOOK_MASK: u64 = 0b010; + +/// These are IDs as opposed to bitmasks since they only need to +/// convey one piece of information (the type of reply the reply +/// handler is handling.) +const PRE_PROPOSE_MODULE_INSTANTIATION_ID: u64 = 0b011; +const FAILED_PRE_PROPOSE_MODULE_HOOK_ID: u64 = 0b100; + +const BITS_RESERVED_FOR_REPLY_TYPE: u8 = 3; +const REPLY_TYPE_MASK: u64 = (1 << BITS_RESERVED_FOR_REPLY_TYPE) - 1; + +/// Since we can only pass `id`, and we need to perform different actions in reply, +/// we decided to take few bits to identify "Reply Type". +/// See +// Limit variant count to `2 ** BITS_RESERVED_FOR_REPLY_TYPE`. This +// must be manually updated if additional bits are allocated as +// constexpr and procedural macros are seprate in the rust compiler. +#[limit_variant_count(8)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Eq))] +pub enum TaggedReplyId { + /// Fired when a proposal's execution fails. + FailedProposalExecution(u64), + /// Fired when a proposal hook's execution fails. + FailedProposalHook(u64), + /// Fired when a vote hook's execution fails. + FailedVoteHook(u64), + /// Fired when a pre-propse module's execution fails. + FailedPreProposeModuleHook, + /// Fired when a pre-propose module is successfully instantiated. + PreProposeModuleInstantiation, +} + +impl TaggedReplyId { + /// Takes `Reply.id` and returns tagged version of it, + /// depending on a first few bits. + /// + /// We know it costs extra to pattern match, but cleaner code in `reply` Methods + pub fn new(id: u64) -> Result { + let reply_type = id & REPLY_TYPE_MASK; + let id_after_shift = id >> BITS_RESERVED_FOR_REPLY_TYPE; + match reply_type { + FAILED_PROPOSAL_EXECUTION_MASK => { + Ok(TaggedReplyId::FailedProposalExecution(id_after_shift)) + } + FAILED_PROPOSAL_HOOK_MASK => Ok(TaggedReplyId::FailedProposalHook(id_after_shift)), + FAILED_VOTE_HOOK_MASK => Ok(TaggedReplyId::FailedVoteHook(id_after_shift)), + PRE_PROPOSE_MODULE_INSTANTIATION_ID => Ok(TaggedReplyId::PreProposeModuleInstantiation), + FAILED_PRE_PROPOSE_MODULE_HOOK_ID => Ok(TaggedReplyId::FailedPreProposeModuleHook), + _ => Err(error::TagError::UnknownReplyId { id }), + } + } +} + +/// This function can drop bits, if you have more than `u(64-[`BITS_RESERVED_FOR_REPLY_TYPE`])` proposals. +pub const fn mask_proposal_execution_proposal_id(proposal_id: u64) -> u64 { + FAILED_PROPOSAL_EXECUTION_MASK | (proposal_id << BITS_RESERVED_FOR_REPLY_TYPE) +} + +pub const fn mask_proposal_hook_index(index: u64) -> u64 { + FAILED_PROPOSAL_HOOK_MASK | (index << BITS_RESERVED_FOR_REPLY_TYPE) +} + +pub const fn mask_vote_hook_index(index: u64) -> u64 { + FAILED_VOTE_HOOK_MASK | (index << BITS_RESERVED_FOR_REPLY_TYPE) +} + +pub const fn pre_propose_module_instantiation_id() -> u64 { + PRE_PROPOSE_MODULE_INSTANTIATION_ID +} + +pub const fn failed_pre_propose_module_hook_id() -> u64 { + FAILED_PRE_PROPOSE_MODULE_HOOK_ID +} + +pub mod error { + use thiserror::Error; + + #[derive(Error, Debug, PartialEq, Eq)] + pub enum TagError { + #[error("Unknown reply id ({id}).")] + UnknownReplyId { id: u64 }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_tagged_reply_id() { + // max u61 + let proposal_id_max: u64 = 2_u64.pow(61) - 1; + let proposal_hook_idx = 1234; + let vote_hook_idx = 4321; + + let m_proposal_id = mask_proposal_execution_proposal_id(proposal_id_max); + let m_proposal_hook_idx = mask_proposal_hook_index(proposal_hook_idx); + let m_vote_hook_idx = mask_vote_hook_index(vote_hook_idx); + + assert_eq!( + TaggedReplyId::new(m_proposal_id).unwrap(), + TaggedReplyId::FailedProposalExecution(proposal_id_max) + ); + assert_eq!( + TaggedReplyId::new(m_proposal_hook_idx).unwrap(), + TaggedReplyId::FailedProposalHook(proposal_hook_idx) + ); + assert_eq!( + TaggedReplyId::new(m_vote_hook_idx).unwrap(), + TaggedReplyId::FailedVoteHook(vote_hook_idx) + ); + assert_eq!( + TaggedReplyId::new(0b110).unwrap_err(), + error::TagError::UnknownReplyId { id: 0b110 } + ); + } +} diff --git a/packages/cwd-voting/src/status.rs b/packages/cwd-voting/src/status.rs new file mode 100644 index 00000000..ec27c2f3 --- /dev/null +++ b/packages/cwd-voting/src/status.rs @@ -0,0 +1,34 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug, Copy)] +#[serde(rename_all = "snake_case")] +#[repr(u8)] +pub enum Status { + /// The proposal is open for voting. + Open, + /// The proposal has been rejected. + Rejected, + /// The proposal has been passed but has not been executed. + Passed, + /// The proposal has been passed and executed. + Executed, + /// The proposal has failed or expired and has been closed. A + /// proposal deposit refund has been issued if applicable. + Closed, + /// The proposal's execution failed. + ExecutionFailed, +} + +impl std::fmt::Display for Status { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Status::Open => write!(f, "open"), + Status::Rejected => write!(f, "rejected"), + Status::Passed => write!(f, "passed"), + Status::Executed => write!(f, "executed"), + Status::Closed => write!(f, "closed"), + Status::ExecutionFailed => write!(f, "execution_failed"), + } + } +} diff --git a/packages/cwd-voting/src/threshold.rs b/packages/cwd-voting/src/threshold.rs new file mode 100644 index 00000000..149d4c52 --- /dev/null +++ b/packages/cwd-voting/src/threshold.rs @@ -0,0 +1,185 @@ +use cosmwasm_std::{Decimal, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum ThresholdError { + #[error("Required threshold cannot be zero")] + ZeroThreshold {}, + + #[error("Not possible to reach required (passing) threshold")] + UnreachableThreshold {}, +} + +/// A percentage of voting power that must vote yes for a proposal to +/// pass. An example of why this is needed: +/// +/// If a user specifies a 60% passing threshold, and there are 10 +/// voters they likely expect that proposal to pass when there are 6 +/// yes votes. This implies that the condition for passing should be +/// `yes_votes >= total_votes * threshold`. +/// +/// With this in mind, how should a user specify that they would like +/// proposals to pass if the majority of voters choose yes? Selecting +/// a 50% passing threshold with those rules doesn't properly cover +/// that case as 5 voters voting yes out of 10 would pass the +/// proposal. Selecting 50.0001% or or some variation of that also +/// does not work as a very small yes vote which technically makes the +/// majority yes may not reach that threshold. +/// +/// To handle these cases we provide both a majority and percent +/// option for all percentages. If majority is selected passing will +/// be determined by `yes > total_votes * 0.5`. If percent is selected +/// passing is determined by `yes >= total_votes * percent`. +/// +/// In both of these cases a proposal with only abstain votes must +/// fail. This requires a special case passing logic. +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum PercentageThreshold { + /// The majority of voters must vote yes for the proposal to pass. + Majority {}, + /// A percentage of voting power >= percent must vote yes for the + /// proposal to pass. + Percent(Decimal), +} + +/// The ways a proposal may reach its passing / failing threshold. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum Threshold { + /// Declares a percentage of the total weight that must cast Yes + /// votes in order for a proposal to pass. See + /// `ThresholdResponse::AbsolutePercentage` in the cw3 spec for + /// details. + AbsolutePercentage { percentage: PercentageThreshold }, + + /// Declares a `quorum` of the total votes that must participate + /// in the election in order for the vote to be considered at all. + /// See `ThresholdResponse::ThresholdQuorum` in the cw3 spec for + /// details. + ThresholdQuorum { + threshold: PercentageThreshold, + quorum: PercentageThreshold, + }, + + /// An absolute number of votes needed for something to cross the + /// threshold. Useful for multisig style voting. + AbsoluteCount { threshold: Uint128 }, +} + +/// Asserts that the 0.0 < percent <= 1.0 +fn validate_percentage(percent: &PercentageThreshold) -> Result<(), ThresholdError> { + if let PercentageThreshold::Percent(percent) = percent { + if percent.is_zero() { + Err(ThresholdError::ZeroThreshold {}) + } else if *percent > Decimal::one() { + Err(ThresholdError::UnreachableThreshold {}) + } else { + Ok(()) + } + } else { + Ok(()) + } +} + +/// Asserts that a quorum <= 1. Quorums may be zero, to enable plurality-style voting. +pub fn validate_quorum(quorum: &PercentageThreshold) -> Result<(), ThresholdError> { + match quorum { + PercentageThreshold::Majority {} => Ok(()), + PercentageThreshold::Percent(quorum) => { + if *quorum > Decimal::one() { + Err(ThresholdError::UnreachableThreshold {}) + } else { + Ok(()) + } + } + } +} + +impl Threshold { + /// Validates the threshold. + /// + /// - Quorums must never be over 100%. + /// - Passing thresholds must never be over 100%, nor be 0%. + /// - Absolute count thresholds must be non-zero. + pub fn validate(&self) -> Result<(), ThresholdError> { + match self { + Threshold::AbsolutePercentage { + percentage: percentage_needed, + } => validate_percentage(percentage_needed), + Threshold::ThresholdQuorum { threshold, quorum } => { + validate_percentage(threshold)?; + validate_quorum(quorum) + } + Threshold::AbsoluteCount { threshold } => { + if threshold.is_zero() { + Err(ThresholdError::ZeroThreshold {}) + } else { + Ok(()) + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! p { + ($x:expr ) => { + PercentageThreshold::Percent(Decimal::percent($x)) + }; + } + + #[test] + fn test_threshold_validation() { + let t = Threshold::AbsoluteCount { + threshold: Uint128::zero(), + }; + assert_eq!(t.validate().unwrap_err(), ThresholdError::ZeroThreshold {}); + + let t = Threshold::AbsolutePercentage { percentage: p!(0) }; + assert_eq!(t.validate().unwrap_err(), ThresholdError::ZeroThreshold {}); + + let t = Threshold::AbsolutePercentage { + percentage: p!(101), + }; + assert_eq!( + t.validate().unwrap_err(), + ThresholdError::UnreachableThreshold {} + ); + + let t = Threshold::AbsolutePercentage { + percentage: p!(100), + }; + t.validate().unwrap(); + + let t = Threshold::ThresholdQuorum { + threshold: p!(101), + quorum: p!(0), + }; + assert_eq!( + t.validate().unwrap_err(), + ThresholdError::UnreachableThreshold {} + ); + + let t = Threshold::ThresholdQuorum { + threshold: p!(100), + quorum: p!(0), + }; + t.validate().unwrap(); + + let t = Threshold::ThresholdQuorum { + threshold: p!(100), + quorum: p!(101), + }; + assert_eq!( + t.validate().unwrap_err(), + ThresholdError::UnreachableThreshold {} + ); + } +} diff --git a/packages/cwd-voting/src/voting.rs b/packages/cwd-voting/src/voting.rs new file mode 100644 index 00000000..b18dcd2d --- /dev/null +++ b/packages/cwd-voting/src/voting.rs @@ -0,0 +1,531 @@ +use cosmwasm_std::{Addr, Decimal, Deps, StdError, StdResult, Uint128, Uint256}; +use cw_utils::Duration; +use cwd_interface::voting; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::threshold::PercentageThreshold; + +// We multiply by this when calculating needed_votes in order to round +// up properly. +const PRECISION_FACTOR: u128 = 10u128.pow(9); + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct Votes { + pub yes: Uint128, + pub no: Uint128, + pub abstain: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, JsonSchema, Debug)] +#[serde(rename_all = "lowercase")] +#[repr(u8)] +pub enum Vote { + /// Marks support for the proposal. + Yes, + /// Marks opposition to the proposal. + No, + /// Marks participation but does not count towards the ratio of + /// support / opposed. + Abstain, +} + +#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, JsonSchema, Debug)] +pub struct MultipleChoiceVote { + // A vote indicates which option the user has selected. + pub option_id: u32, +} + +impl std::fmt::Display for MultipleChoiceVote { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.option_id) + } +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +pub struct MultipleChoiceVotes { + // Vote counts is a vector of integers indicating the vote weight for each option + // (the index corresponds to the option). + pub vote_weights: Vec, +} + +impl MultipleChoiceVotes { + /// Sum of all vote weights + pub fn total(&self) -> Uint128 { + self.vote_weights.iter().sum() + } + + pub fn add_vote(&mut self, vote: MultipleChoiceVote, weight: Uint128) -> StdResult<()> { + self.vote_weights[vote.option_id as usize] = self.vote_weights[vote.option_id as usize] + .checked_add(weight) + .map_err(StdError::overflow)?; + Ok(()) + } + + pub fn remove_vote(&mut self, vote: MultipleChoiceVote, weight: Uint128) -> StdResult<()> { + self.vote_weights[vote.option_id as usize] = self.vote_weights[vote.option_id as usize] + .checked_sub(weight) + .map_err(StdError::overflow)?; + Ok(()) + } + + pub fn zero(num_choices: usize) -> Self { + Self { + vote_weights: vec![Uint128::zero(); num_choices], + } + } +} + +pub enum VoteCmp { + Greater, + Geq, +} + +/// Compares `votes` with `total_power * passing_percentage`. The +/// comparison function used depends on the `VoteCmp` variation +/// selected. +/// +/// !!NOTE!! THIS FUNCTION DOES NOT ROUND UP. +/// +/// For example, the following assertion will succede: +/// +/// ```rust +/// use cwd_voting::voting::{compare_vote_count, VoteCmp}; +/// use cosmwasm_std::{Uint128, Decimal}; +/// fn test() { +/// assert!(compare_vote_count( +/// Uint128::new(7), +/// VoteCmp::Greater, +/// Uint128::new(13), +/// Decimal::from_ratio(7u64, 13u64) +/// )); +/// } +/// ``` +/// +/// This is because `7 * (7/13)` is `6.999...` after rounding. You +/// MUST ensure this is the behavior you want when calling this +/// function. +/// +/// For our current purposes this is OK as the only place we use the +/// `Greater` comparason is when looking to see if no votes have +/// reached `> (1 - threshold)` and thus made the proposal +/// unpassable. As threshold will be a rounded down version of the +/// infinite percision real version `1 - threshold` will actually be a +/// higher magnitured than the real one meaning that we won't ever be +/// in the position of simeltaniously reporting a proposal as both +/// rejected and passed. +/// +pub fn compare_vote_count( + votes: Uint128, + cmp: VoteCmp, + total_power: Uint128, + passing_percentage: Decimal, +) -> bool { + let votes = votes.full_mul(PRECISION_FACTOR); + let total_power = total_power.full_mul(PRECISION_FACTOR); + let threshold = total_power.multiply_ratio( + passing_percentage.atomics(), + Uint256::from(10u64).pow(passing_percentage.decimal_places()), + ); + match cmp { + VoteCmp::Greater => votes > threshold, + VoteCmp::Geq => votes >= threshold, + } +} + +pub fn does_vote_count_pass( + yes_votes: Uint128, + options: Uint128, + percent: PercentageThreshold, +) -> bool { + // Don't pass proposals if all the votes are abstain. + if options.is_zero() { + return false; + } + match percent { + PercentageThreshold::Majority {} => yes_votes.full_mul(2u64) > options.into(), + PercentageThreshold::Percent(percent) => { + compare_vote_count(yes_votes, VoteCmp::Geq, options, percent) + } + } +} + +pub fn does_vote_count_fail( + no_votes: Uint128, + options: Uint128, + percent: PercentageThreshold, +) -> bool { + // All abstain votes should result in a rejected proposal. + if options.is_zero() { + return true; + } + match percent { + PercentageThreshold::Majority {} => { + // Fails if no votes have >= half of all votes. + no_votes.full_mul(2u64) >= options.into() + } + PercentageThreshold::Percent(percent) => compare_vote_count( + no_votes, + VoteCmp::Greater, + options, + Decimal::one() - percent, + ), + } +} + +impl Votes { + /// Constructs an zero'd out votes struct. + pub fn zero() -> Self { + Self { + yes: Uint128::zero(), + no: Uint128::zero(), + abstain: Uint128::zero(), + } + } + + /// Constructs a vote with a specified number of yes votes. Used + /// for testing. + #[cfg(test)] + pub fn with_yes(yes: Uint128) -> Self { + Self { + yes, + no: Uint128::zero(), + abstain: Uint128::zero(), + } + } + + /// Adds a vote to the votes. + pub fn add_vote(&mut self, vote: Vote, power: Uint128) { + match vote { + Vote::Yes => self.yes += power, + Vote::No => self.no += power, + Vote::Abstain => self.abstain += power, + } + } + + /// Removes a vote from the votes. The vote being removed must + /// have been previously added or this method will cause an + /// overflow. + pub fn remove_vote(&mut self, vote: Vote, power: Uint128) { + match vote { + Vote::Yes => self.yes -= power, + Vote::No => self.no -= power, + Vote::Abstain => self.abstain -= power, + } + } + + /// Computes the total number of votes cast. + /// + /// NOTE: The total number of votes avaliable from a voting module + /// is a `Uint128`. As it is not possible to vote twice we know + /// that the sum of votes must be <= 2^128 and can safely return a + /// `Uint128` from this function. A missbehaving voting power + /// module may break this invariant. + pub fn total(&self) -> Uint128 { + self.yes + self.no + self.abstain + } +} + +impl std::fmt::Display for Vote { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Vote::Yes => write!(f, "yes"), + Vote::No => write!(f, "no"), + Vote::Abstain => write!(f, "abstain"), + } + } +} + +/// A height of None will query for the current block height. +pub fn get_voting_power( + deps: Deps, + address: Addr, + dao: Addr, + height: Option, +) -> StdResult { + let response: voting::VotingPowerAtHeightResponse = deps.querier.query_wasm_smart( + dao, + &voting::Query::VotingPowerAtHeight { + address: address.into_string(), + height, + }, + )?; + Ok(response.power) +} + +/// A height of None will query for the current block height. +pub fn get_total_power(deps: Deps, dao: Addr, height: Option) -> StdResult { + let response: voting::TotalPowerAtHeightResponse = deps + .querier + .query_wasm_smart(dao, &voting::Query::TotalPowerAtHeight { height })?; + Ok(response.power) +} + +/// Validates that the min voting period is less than the max voting +/// period. Passes arguments through the function. +pub fn validate_voting_period( + min: Option, + max: Duration, +) -> Result<(Option, Duration), crate::error::VotingError> { + let min = min + .map(|min| { + let valid = match (min, max) { + (Duration::Time(min), Duration::Time(max)) => min <= max, + (Duration::Height(min), Duration::Height(max)) => min <= max, + _ => return Err(crate::error::VotingError::DurationUnitsConflict {}), + }; + if valid { + Ok(min) + } else { + Err(crate::error::VotingError::InvalidMinVotingPeriod {}) + } + }) + .transpose()?; + + Ok((min, max)) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn count_votes() { + let mut votes = Votes::with_yes(Uint128::new(5)); + votes.add_vote(Vote::No, Uint128::new(10)); + votes.add_vote(Vote::Yes, Uint128::new(30)); + votes.add_vote(Vote::Abstain, Uint128::new(40)); + + assert_eq!(votes.total(), Uint128::new(5 + 10 + 30 + 40)); + assert_eq!(votes.yes, Uint128::new(35)); + assert_eq!(votes.no, Uint128::new(10)); + assert_eq!(votes.abstain, Uint128::new(40)); + } + + #[test] + fn vote_comparisons() { + assert!(!compare_vote_count( + Uint128::new(7), + VoteCmp::Geq, + Uint128::new(15), + Decimal::percent(50) + )); + assert!(!compare_vote_count( + Uint128::new(7), + VoteCmp::Greater, + Uint128::new(15), + Decimal::percent(50) + )); + + assert!(compare_vote_count( + Uint128::new(7), + VoteCmp::Geq, + Uint128::new(14), + Decimal::percent(50) + )); + assert!(!compare_vote_count( + Uint128::new(7), + VoteCmp::Greater, + Uint128::new(14), + Decimal::percent(50) + )); + + assert!(compare_vote_count( + Uint128::new(7), + VoteCmp::Geq, + Uint128::new(13), + Decimal::from_ratio(7u64, 13u64) + )); + + assert!(!compare_vote_count( + Uint128::new(6), + VoteCmp::Greater, + Uint128::new(13), + Decimal::one() - Decimal::from_ratio(7u64, 13u64) + )); + assert!(compare_vote_count( + Uint128::new(7), + VoteCmp::Greater, + Uint128::new(13), + Decimal::from_ratio(7u64, 13u64) + )); + + assert!(!compare_vote_count( + Uint128::new(4), + VoteCmp::Geq, + Uint128::new(9), + Decimal::percent(50) + )) + } + + #[test] + fn more_votes_tests() { + assert!(compare_vote_count( + Uint128::new(1), + VoteCmp::Geq, + Uint128::new(3), + Decimal::permille(333) + )); + + assert!(!compare_vote_count( + Uint128::new(1), + VoteCmp::Geq, + Uint128::new(3), + Decimal::permille(334) + )); + assert!(compare_vote_count( + Uint128::new(2), + VoteCmp::Geq, + Uint128::new(3), + Decimal::permille(334) + )); + + assert!(compare_vote_count( + Uint128::new(11), + VoteCmp::Geq, + Uint128::new(30), + Decimal::permille(333) + )); + + assert!(compare_vote_count( + Uint128::new(15), + VoteCmp::Geq, + Uint128::new(30), + Decimal::permille(500) + )); + assert!(!compare_vote_count( + Uint128::new(15), + VoteCmp::Greater, + Uint128::new(30), + Decimal::permille(500) + )); + + assert!(compare_vote_count( + Uint128::new(0), + VoteCmp::Geq, + Uint128::new(0), + Decimal::permille(500) + )); + assert!(!compare_vote_count( + Uint128::new(0), + VoteCmp::Greater, + Uint128::new(0), + Decimal::permille(500) + )); + + assert!(!compare_vote_count( + Uint128::new(0), + VoteCmp::Geq, + Uint128::new(1), + Decimal::permille(1) + )); + assert!(!compare_vote_count( + Uint128::new(0), + VoteCmp::Greater, + Uint128::new(1), + Decimal::permille(1) + )); + + assert!(compare_vote_count( + Uint128::new(1), + VoteCmp::Geq, + Uint128::new(1), + Decimal::permille(1) + )); + assert!(compare_vote_count( + Uint128::new(1), + VoteCmp::Greater, + Uint128::new(1), + Decimal::permille(1) + )); + + assert!(!compare_vote_count( + Uint128::new(0), + VoteCmp::Geq, + Uint128::new(1), + Decimal::permille(999) + )); + assert!(!compare_vote_count( + Uint128::new(0), + VoteCmp::Greater, + Uint128::new(1), + Decimal::permille(999) + )); + } + + #[test] + fn tricky_vote_counts() { + let threshold = Decimal::percent(50); + for count in 1..50_000 { + assert!(compare_vote_count( + Uint128::new(count), + VoteCmp::Geq, + Uint128::new(count * 2), + threshold + )); + assert!(!compare_vote_count( + Uint128::new(count), + VoteCmp::Greater, + Uint128::new(count * 2), + threshold + )) + } + + // Zero votes out of zero total power meet any threshold. When + // Geq is used. Always fail otherwise. + assert!(compare_vote_count( + Uint128::zero(), + VoteCmp::Geq, + Uint128::new(1), + Decimal::percent(0) + )); + assert!(compare_vote_count( + Uint128::zero(), + VoteCmp::Geq, + Uint128::new(0), + Decimal::percent(0) + )); + assert!(!compare_vote_count( + Uint128::zero(), + VoteCmp::Greater, + Uint128::new(1), + Decimal::percent(0) + )); + assert!(!compare_vote_count( + Uint128::zero(), + VoteCmp::Greater, + Uint128::new(0), + Decimal::percent(0) + )) + } + + #[test] + fn test_display_multiple_choice_vote() { + let vote = MultipleChoiceVote { option_id: 0 }; + assert_eq!("0", format!("{}", vote)) + } + + #[test] + fn test_multiple_choice_votes() { + let mut votes = MultipleChoiceVotes { + vote_weights: vec![Uint128::new(10), Uint128::new(100)], + }; + let total = votes.total(); + assert_eq!(total, Uint128::new(110)); + + votes + .add_vote(MultipleChoiceVote { option_id: 0 }, Uint128::new(10)) + .unwrap(); + let total = votes.total(); + assert_eq!(total, Uint128::new(120)); + + votes + .remove_vote(MultipleChoiceVote { option_id: 0 }, Uint128::new(20)) + .unwrap(); + votes + .remove_vote(MultipleChoiceVote { option_id: 1 }, Uint128::new(100)) + .unwrap(); + + assert_eq!(votes, MultipleChoiceVotes::zero(2)) + } +} diff --git a/scripts/schema.sh b/scripts/schema.sh new file mode 100755 index 00000000..28f2cbc1 --- /dev/null +++ b/scripts/schema.sh @@ -0,0 +1,59 @@ +START_DIR=$(pwd) + +# ${f <-- from variable f +# ## <-- greedy front trim +# * <-- matches anything +# / <-- until the last '/' +# } +# + +echo "generating schema for cwd-core" +cd contracts/neutron-core +cargo run --example schema > /dev/null + +cd "$START_DIR" + +for f in ./contracts/voting/* +do + echo "generating schema for ${f##*/}" + cd "$f" + CMD="cargo run --example schema" + eval $CMD > /dev/null + cd "$START_DIR" +done + +for f in ./contracts/proposal/* +do + echo "generating schema for ${f##*/}" + cd "$f" + CMD="cargo run --example schema" + eval $CMD > /dev/null + cd "$START_DIR" +done + +for f in ./contracts/staking/* +do + echo "generating schema for ${f##*/}" + cd "$f" + CMD="cargo run --example schema" + eval $CMD > /dev/null + cd "$START_DIR" +done + +for f in ./contracts/pre-propose/* +do + echo "generating schema for ${f##*/}" + cd "$f" + CMD="cargo run --example schema" + eval $CMD > /dev/null + cd "$START_DIR" +done + +for f in ./contracts/external/* +do + echo "generating schema for ${f##*/}" + cd "$f" + CMD="cargo run --example schema" + eval $CMD > /dev/null + cd "$START_DIR" +done diff --git a/test_proposal.sh b/test_proposal.sh index 9c128bda..0698f488 100755 --- a/test_proposal.sh +++ b/test_proposal.sh @@ -2,59 +2,234 @@ BIN=neutrond CHAIN_ID_1=test-1 -CHAIN_ID_2=test-2 NEUTRON_DIR=${NEUTRON_DIR:-../neutron} HOME_1=${NEUTRON_DIR}/data/test-1/ -HOME_2=${NEUTRON_DIR}/data/test-2/ + USERNAME_1=demowallet1 -USERNAME_2=demowallet2 -KEY_2=$(neutrond keys show demowallet2 -a --keyring-backend test --home ${HOME_2}) -ADMIN=$(neutrond keys show demowallet1 -a --keyring-backend test --home ${HOME_1}) - -TARGET_ADDRESS=neutron1mjk79fjjgpplak5wq838w0yd982gzkyf8fxu8u -VAL2=neutronvaloper1qnk2n4nlkpw9xfqntladh74w6ujtulwnqshepx - -#Register new proposal -# json formatted proposal is a nightmare, so we use keys for now -RES=$(${BIN} tx gov submit-proposal --title="hello proposal" \ - --description="i believe in neutron supremacy!" \ - --type="Text" \ - --deposit="100000000stake" \ - --from ${USERNAME_1} \ - --gas 500000 \ - --fees 5000stake \ - -y \ - --chain-id ${CHAIN_ID_1} \ - --broadcast-mode=block \ - --home ${HOME_1} \ - --keyring-backend test \ - --node tcp://127.0.0.1:16657) -echo "--- tx gov submit-proposal" -echo $RES -echo - -# print proposal in console, voting period should be active -RES=$(${BIN} q gov proposals --chain-id ${CHAIN_ID_1} --home ${HOME_1} --node tcp://127.0.0.1:16657) -echo "--- q gov proposals" -echo $RES -echo - -# vote yes (w dominance of votes) -RES=$(${BIN} tx gov vote 1 yes --from ${USERNAME_1} --fees 5000stake --chain-id ${CHAIN_ID_1} -y --broadcast-mode=block --home ${HOME_1} --keyring-backend test --node tcp://127.0.0.1:16657) -echo "--- tx gov vote 1 yes" -echo $RES -echo -# wait voting period to end -sleep 60 +USERNAME_2=demowallet3 +USERNAME_3=rly1 + +# DAO addresses +VAULT_ADDRESS=neutron14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s5c2epq +PROPOSE_ADDRESS=neutron1unyuj8qnmygvzuex3dwmg9yzt9alhvyeat0uu0jedg2wj33efl5qmysp02 +VOTE_ADDRESS=neutron1zwv6feuzhy6a9wekh96cd57lsarmqlwxdypdsplw6zhfncqw6ftqqgmq2a +CORE_ADDRESS=neutron1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqcd0mrx +PRE_PROPOSE_ADDRESS=neutron1eyfccmjm6732k7wp4p6gdjwhxjwsvje44j0hfx8nkgrm8fs7vqfs8hrpdj + + # -# print proposal to see that it has passed -echo "--- q gov proposals" -RES=$(${BIN} q gov proposals --chain-id ${CHAIN_ID_1} --home ${HOME_1} --node tcp://127.0.0.1:16657) +# send funds to contract to send them back +RES=$(${BIN} tx bank send ${USERNAME_1} ${CORE_ADDRESS} 1000stake -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) + +echo " + + +bank->send from wallet 1: +" +echo $RES + +# STAKING +# stake funds from wallet 1 +RES=$(${BIN} tx wasm execute $VAULT_ADDRESS "{\"bond\": {}}" --amount 1000stake --from ${USERNAME_1} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +staking from wallet 1: +" +echo $RES + +#stake funds from wallet 2 +RES=$(${BIN} tx wasm execute $VAULT_ADDRESS "{\"bond\": {}}" --amount 1000stake --from ${USERNAME_2} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +staking from wallet 2: +" +echo $RES + +#stake funds from wallet 3 +RES=$(${BIN} tx wasm execute $VAULT_ADDRESS "{\"bond\": {}}" --amount 1000stake --from ${USERNAME_3} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +staking from wallet 3: +" +echo $RES + + + +PROP="{\"propose\": { \"msg\": { \"propose\": {\"title\": \"TEST\", \"description\": \"BOTTOMTTEXT\", \"msgs\":[{\"custom\":{\"submit_admin_proposal\":{\"admin_proposal\":{\"param_change_proposal\":{\"title\":\"title\",\"description\":\"description\",\"param_changes\":[{\"subspace\":\"icahost\",\"key\":\"HostEnabled\",\"value\":\"false\"}]}}}}}]}}}}" +# PROPOSAL 1 (to pass) +#propose proposal we're going to pass +RES=$(${BIN} tx wasm execute $PRE_PROPOSE_ADDRESS "$PROP" --amount 1000stake --from ${USERNAME_1} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +propose proposal to be passed: +" echo $RES -# check that voter has no delegations -echo "--- q staking delegations" -RES=$(${BIN} q staking delegations $ADMIN --node tcp://127.0.0.1:16657) -echo $RES \ No newline at end of file +#### vote YES from wallet 1 +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"vote\": {\"proposal_id\": 1, \"vote\": \"yes\"}}" --from ${USERNAME_1} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +vote YES from wallet1: +" +echo $RES + +#### vote NO from wallet 2 +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"vote\": {\"proposal_id\": 1, \"vote\": \"no\"}}" --from ${USERNAME_2} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +vote NO from wallet 2: +" +echo $RES + +#### vote YES from wallet 3 +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"vote\": {\"proposal_id\": 1, \"vote\": \"yes\"}}" --from ${USERNAME_3} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +vote YES from wallet 3: +" +echo $RES + +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"execute\": {\"proposal_id\": 1}}" --from ${USERNAME_1} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +execute proposal: +" +echo $RES + + + +# PROPOSAL 2 (to decline) +#propose proposal we're going to pass +RES=$(${BIN} tx wasm execute $PRE_PROPOSE_ADDRESS "{\"propose\": { \"msg\": { \"propose\": {\"title\": \"TEST\", \"description\": \"BOTTOMTTEXT\", \"msgs\":[{\"custom\":{\"submit_admin_proposal\":{\"admin_proposal\":{\"param_change_proposal\":{\"title\":\"title\",\"description\":\"description\",\"param_changes\":[{\"subspace\":\"icahost\",\"key\":\"HostEnabled\",\"value\":\"false\"}]}}}}}]}}}}" --amount 1000stake --from ${USERNAME_1} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo ". +. +. +propose proposal to be decline: + +" +echo $RES + +#### vote YES from wallet 1 +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"vote\": {\"proposal_id\": 2, \"vote\": \"yes\"}}" --from ${USERNAME_1} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo ". +. +. +vote YES from wallet1: +" +echo $RES + +#### vote NO from wallet 2 +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"vote\": {\"proposal_id\": 2, \"vote\": \"no\"}}" --from ${USERNAME_2} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +vote NO from wallet 2: +" +echo $RES + +#### vote NO from wallet 3 +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"vote\": {\"proposal_id\": 2, \"vote\": \"no\"}}" --from ${USERNAME_3} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +vote NO from wallet 3: +" +echo $RES + +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"execute\": {\"proposal_id\": 2}}" --from ${USERNAME_1} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +execute proposal: should fail +" +echo $RES + + + + +# PROPOSAL 3 (to pass) +#propose proposal we're going to pass +RES=$(${BIN} tx wasm execute $PRE_PROPOSE_ADDRESS "{\"propose\": { \"msg\": {\"propose\": {\"title\": \"TEST\", \"description\": \"BOTTOMTTEXT\", \"msgs\":[{\"bank\":{\"send\":{\"to_address\":\"neutron1m9l358xunhhwds0568za49mzhvuxx9ux8xafx2\",\"amount\":[{\"denom\":\"stake\",\"amount\":\"1000\"}]}}}]}}}}" --amount 1000stake --from ${USERNAME_1} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +propose proposal # 3 to be passed: +" +echo $RES + +#### vote YES from wallet 1 +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"vote\": {\"proposal_id\": 3, \"vote\": \"yes\"}}" --from ${USERNAME_1} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +vote YES from wallet1: +" +echo $RES + +#### vote NO from wallet 2 +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"vote\": {\"proposal_id\": 3, \"vote\": \"no\"}}" --from ${USERNAME_2} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +vote NO from wallet 2: +" +echo $RES + +#### vote YES from wallet 3 +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"vote\": {\"proposal_id\": 3, \"vote\": \"yes\"}}" --from ${USERNAME_3} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +vote YES from wallet 3: +" +echo $RES + +RES=$(${BIN} tx wasm execute $PROPOSE_ADDRESS "{\"execute\": {\"proposal_id\": 3}}" --from ${USERNAME_1} -y --chain-id ${CHAIN_ID_1} --output json --broadcast-mode=block --gas-prices 0.0025stake --gas 1000000 --keyring-backend test --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +execute proposal: +" +echo $RES + + + + +RES=$(${BIN} q wasm contract-state smart $CORE_ADDRESS "{\"total_power_at_height\": {}}" --chain-id ${CHAIN_ID_1} --output json --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " + + +total voting power, should be 3000: +" +echo $RES + +RES=$(${BIN} q wasm contract-state smart $PROPOSE_ADDRESS "{\"proposal\": {\"proposal_id\": 1}}" --chain-id ${CHAIN_ID_1} --output json --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " +should be status:executed" +echo $RES + +RES=$(${BIN} q wasm contract-state smart $PROPOSE_ADDRESS "{\"proposal\": {\"proposal_id\": 2}}" --chain-id ${CHAIN_ID_1} --output json --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " +should be status:rejected" +echo $RES + +RES=$(${BIN} q wasm contract-state smart $PROPOSE_ADDRESS "{\"proposal\": {\"proposal_id\": 3}}" --chain-id ${CHAIN_ID_1} --output json --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo " +should be status:executed" +echo $RES +sleep 5 + +echo " +list archived adminmodule proposals (should be one param change proposal)" +RES=$(${BIN} q adminmodule archivedproposals --chain-id ${CHAIN_ID_1} --home ${HOME_1} --node tcp://127.0.0.1:16657) +echo $RES diff --git a/types/build/packages/cw-core-interface/CwCoreInterfaceContract.js b/types/build/packages/cw-core-interface/CwCoreInterfaceContract.js new file mode 100644 index 00000000..88c109ac --- /dev/null +++ b/types/build/packages/cw-core-interface/CwCoreInterfaceContract.js @@ -0,0 +1,7 @@ +"use strict"; +/** +* This file was automatically generated by @cosmwasm/ts-codegen@0.5.8. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/types/build/packages/cw-core-macros/CwCoreMacrosContract.js b/types/build/packages/cw-core-macros/CwCoreMacrosContract.js new file mode 100644 index 00000000..88c109ac --- /dev/null +++ b/types/build/packages/cw-core-macros/CwCoreMacrosContract.js @@ -0,0 +1,7 @@ +"use strict"; +/** +* This file was automatically generated by @cosmwasm/ts-codegen@0.5.8. +* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, +* and run the @cosmwasm/ts-codegen generate command to regenerate this file. +*/ +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/types/build/src/codegen.js b/types/build/src/codegen.js new file mode 100644 index 00000000..2c11cfe9 --- /dev/null +++ b/types/build/src/codegen.js @@ -0,0 +1,154 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const dotenv_1 = __importDefault(require("dotenv")); +const fs_1 = __importDefault(require("fs")); +const path_1 = __importDefault(require("path")); +const ts_codegen_1 = require("@cosmwasm/ts-codegen"); +var OutputType; +(function (OutputType) { + OutputType["contracts"] = "contracts"; + OutputType["packages"] = "packages"; +})(OutputType || (OutputType = {})); +dotenv_1.default.config(); +const CONTRACTS_OUTPUT_DIR = "."; +const CODEGEN_LOG_LEVEL = (() => { + const logLevel = process.env.CODEGEN_LOG_LEVEL || ""; + if (logLevel === "verbose") { + return 2; + } + if (logLevel === "debug") { + return 3; + } + if (logLevel === "silent") { + return -1; + } + return 1; +})(); +var LogLevels; +(function (LogLevels) { + LogLevels[LogLevels["Silent"] = -1] = "Silent"; + LogLevels[LogLevels["Verbose"] = 2] = "Verbose"; + LogLevels[LogLevels["Debug"] = 3] = "Debug"; + LogLevels[LogLevels["Normal"] = 1] = "Normal"; +})(LogLevels || (LogLevels = {})); +function log(msg, level = LogLevels.Normal) { + if (CODEGEN_LOG_LEVEL < level) { + return; + } + console.log(msg); +} +const DEFAULT_CONFIG = { + schemaRoots: [ + { + name: OutputType.contracts, + paths: [`../${OutputType.contracts}`], + outputName: OutputType.contracts, + outputDir: CONTRACTS_OUTPUT_DIR, + }, + { + name: OutputType.packages, + paths: [`../${OutputType.packages}`], + outputName: OutputType.packages, + outputDir: CONTRACTS_OUTPUT_DIR, + }, + ] +}; +function generateTs(spec) { + return __awaiter(this, void 0, void 0, function* () { + const out = `${spec.outputPath}/${spec.outputType}/${spec.contractName}`; + const name = spec.contractName; + const schemas = (0, ts_codegen_1.readSchemas)({ schemaDir: spec.schemaDir, argv: { packed: false } }); + return yield (0, ts_codegen_1.generate)(name, schemas, out); + }); +} +function getSchemaDirectories(rootDir, contracts) { + return new Promise((resolve, reject) => { + var _a; + const contractList = (_a = contracts === null || contracts === void 0 ? void 0 : contracts.split(",").map((dir) => dir.trim())) !== null && _a !== void 0 ? _a : []; + const directories = []; + if (contractList.length) { + // get the schema directory for each contract + for (const contractName of contractList) { + const schemaDir = path_1.default.join(rootDir, contractName, "schema"); + directories.push([schemaDir, contractName]); + } + resolve(directories); + } + else { + // get all the schema directories in all the contract directories + fs_1.default.readdir(rootDir, (err, dirEntries) => { + if (err) { + console.error(err); + return; + } + if (!dirEntries) { + console.warn(`no entries found in ${rootDir}`); + resolve([]); + return; + } + dirEntries.forEach((entry) => { + try { + const schemaDir = path_1.default.resolve(rootDir, entry, "schema"); + if (fs_1.default.existsSync(schemaDir) && + fs_1.default.lstatSync(schemaDir).isDirectory()) { + directories.push([schemaDir, entry]); + } + else { + log(`${schemaDir} is not a directory`, LogLevels.Verbose); + } + } + catch (e) { + console.warn(e); + } + }); + resolve(directories); + }); + } + }); +} +function main() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + let config = Object.assign({}, DEFAULT_CONFIG); + const compilationSpecs = []; + log("Calculating generation specs..."); + for (const root of config.schemaRoots) { + const { name, paths, outputName, outputDir } = root; + for (const path of paths) { + const schemaDirectories = yield getSchemaDirectories(path); + for (const [directory, contractName] of schemaDirectories) { + compilationSpecs.push({ + contractName: contractName, + schemaDir: directory, + outputPath: outputDir, + outputType: outputName, + }); + } + } + } + log(`code generating for ${(_a = compilationSpecs === null || compilationSpecs === void 0 ? void 0 : compilationSpecs.length) !== null && _a !== void 0 ? _a : 0} specs...`); + if (CODEGEN_LOG_LEVEL === LogLevels.Debug) { + console.log("Compilation specs:"); + console.dir(compilationSpecs); + } + const codegenResponses = []; + for (const spec of compilationSpecs) { + codegenResponses.push(generateTs(spec)); + } + yield Promise.all(codegenResponses); + log(`code generation complete`, LogLevels.Normal); + }); +} +main();