diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock new file mode 100644 index 0000000..cb5b349 --- /dev/null +++ b/contracts/Cargo.lock @@ -0,0 +1,1119 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "cc" +version = "1.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" +dependencies = [ + "shlex", +] + +[[package]] +name = "certificate" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[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 = "dao-governance" +version = "0.1.0" +dependencies = [ + "ink", + "ink_metadata", + "ink_prelude", + "ink_storage", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ink" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9fd4f77d66c94aa7f27a7cf41cd2edbc2229afe34ec475c3f32b6e8fdf561a0" +dependencies = [ + "derive_more 0.99.20", + "ink_env", + "ink_macro", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "ink_storage", + "parity-scale-codec", +] + +[[package]] +name = "ink_allocator" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870914970470fd77a3f42d3c5d1918b562817af127fd063ee8b1d9fbf59aa1fe" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ink_codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22d79057b2565df31a10af6510a44b161093f110c5f9c22ad02c20af9cea4c29" +dependencies = [ + "blake2", + "derive_more 0.99.20", + "either", + "env_logger", + "heck", + "impl-serde", + "ink_ir", + "ink_primitives", + "itertools", + "log", + "parity-scale-codec", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.101", +] + +[[package]] +name = "ink_engine" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "722ec3a5eb557124b001c60ff8f961079f6d566af643edea579f152b15822fe5" +dependencies = [ + "blake2", + "derive_more 0.99.20", + "ink_primitives", + "parity-scale-codec", + "secp256k1", + "sha2", + "sha3", +] + +[[package]] +name = "ink_env" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "584e73bc0982f6f1a067bb63ebc75262f6dc54ed2a17060efa73eaba84dc9308" +dependencies = [ + "arrayref", + "blake2", + "cfg-if", + "derive_more 0.99.20", + "ink_allocator", + "ink_engine", + "ink_prelude", + "ink_primitives", + "ink_storage_traits", + "num-traits", + "parity-scale-codec", + "paste", + "rlibc", + "scale-decode", + "scale-encode", + "scale-info", + "secp256k1", + "sha2", + "sha3", + "static_assertions", +] + +[[package]] +name = "ink_ir" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b529c941518e8f450395fab9fe8ebba0a7acbb18778fc7e0a87f6248286ec72" +dependencies = [ + "blake2", + "either", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "ink_macro" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8579576c995ca9baa032584beca19155cbd63b6739570aa9da4d35a0415f4be8" +dependencies = [ + "ink_codegen", + "ink_ir", + "ink_primitives", + "parity-scale-codec", + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "ink_metadata" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fddff95ce3e01f42002fdaf96edda691dbccb08c9ae76d7101daa1fa634e601" +dependencies = [ + "derive_more 0.99.20", + "impl-serde", + "ink_prelude", + "ink_primitives", + "scale-info", + "serde", +] + +[[package]] +name = "ink_prelude" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8cfdf91d2b442f08efb34dd3780fd6fbd3d033f63b42f62684fe47534948ef6" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ink_primitives" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6414bcad12ebf0c3abbbb192a09e4d06e22f662cf3e19545204e1b0684be12a1" +dependencies = [ + "derive_more 0.99.20", + "ink_prelude", + "parity-scale-codec", + "scale-decode", + "scale-encode", + "scale-info", + "xxhash-rust", +] + +[[package]] +name = "ink_storage" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd728409de235de0489f71ee2d1beb320613fdb50dda9fa1c564825f4ad06daa" +dependencies = [ + "array-init", + "cfg-if", + "derive_more 0.99.20", + "ink_env", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "ink_storage_traits", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ink_storage_traits" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8dcb50f70377ac35c28d63b06383a0a3cbb79542ea4cdc5b00e3e2b3de4a549" +dependencies = [ + "ink_metadata", + "ink_prelude", + "ink_primitives", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[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.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit 0.22.26", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rlibc" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scale-bits" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "036575c29af9b6e4866ffb7fa055dbf623fe7a9cc159b33786de6013a6969d89" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "scale-decode" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7789f5728e4e954aaa20cadcc370b99096fb8645fca3c9333ace44bb18f30095" +dependencies = [ + "derive_more 0.99.20", + "parity-scale-codec", + "scale-bits", + "scale-decode-derive", + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-decode-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27873eb6005868f8cc72dcfe109fae664cf51223d35387bc2f28be4c28d94c47" +dependencies = [ + "darling", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scale-encode" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" +dependencies = [ + "derive_more 0.99.20", + "parity-scale-codec", + "scale-encode-derive", + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-encode-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995491f110efdc6bea96d6a746140e32bfceb4ea47510750a5467295a4707a25" +dependencies = [ + "darling", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scale-info" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more 1.0.0", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" +dependencies = [ + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.7.10", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" diff --git a/contracts/dao-governance/Cargo.toml b/contracts/dao-governance/Cargo.toml index 9c43c78..d04acf7 100644 --- a/contracts/dao-governance/Cargo.toml +++ b/contracts/dao-governance/Cargo.toml @@ -4,17 +4,17 @@ version = "0.1.0" edition = "2021" [dependencies] -ink = { version = "4.2.1", default-features = false } -ink_lang = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_lang" } -ink_storage = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_storage" } -ink_env = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_env" } -ink_prelude = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_prelude" } -ink_primitives = { git = "https://github.com/paritytech/ink", tag = "v4.0.0-alpha.1", package = "ink_primitives" } +ink = { version = "4.2.0", default-features = false } +ink_metadata = { version = "4.2.0", default-features = false } +ink_storage = { version = "4.2.0", default-features = false } +ink_prelude = { version = "4.2.0", default-features = false } +scale = { package = "parity-scale-codec", version = "3.6.5", default-features = false, features = ["derive"] } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +serde = { version = "1.0", default-features = false, features = ["derive"] } +thiserror = "1.0" -parity-scale-codec = { version = "3.6", default-features = false } -scale-info = { version = "2.10", features = ["derive"], optional = true } -serde = { version = "1.0", features = ["derive"], optional = true } -thiserror = { version = "1.0", optional = true } +[dev-dependencies] +ink_env = { version = "4.2.0", default-features = false } [lib] name = "dao_governance" @@ -24,13 +24,13 @@ crate-type = ["cdylib", "rlib"] [features] default = ["std"] std = [ - "ink/std", - "ink_lang/std", - "ink_storage/std", - "parity-scale-codec/std", - "scale-info/std", - "serde", - "thiserror", + "ink/std", + "ink_metadata/std", + "ink_storage/std", + "ink_prelude/std", + "scale/std", + "scale-info/std", + "serde/std", ] # For testing with cargo-contract diff --git a/contracts/dao-governance/src/dao_tests.rs b/contracts/dao-governance/src/dao_tests.rs deleted file mode 100644 index acad89d..0000000 --- a/contracts/dao-governance/src/dao_tests.rs +++ /dev/null @@ -1,41 +0,0 @@ -#[cfg(test)] -mod tests { - use super::*; - use ink_lang as ink; - - #[ink::test] - fn create_and_vote_proposal_works() { - let mut contract = dao_governance::DaoGovernance::new(); - let id = contract.create_proposal("Learn Rust and Substrate".into(), 60); - - assert_eq!(contract.get_proposal(id).unwrap().content, "Learn Rust and Substrate"); - - let vote_result = contract.vote(id, true); - assert!(vote_result.is_ok()); - - // Attempting to vote again should fail - let second_vote = contract.vote(id, false); - assert!(second_vote.is_err()); - } - - #[ink::test] - fn finalize_proposal_works() { - let mut contract = dao_governance::DaoGovernance::new(); - let id = contract.create_proposal("Add Advanced Smart Contract Course".into(), 0); - contract.vote(id, true).unwrap(); - - // Simulate passing time (manually finalize immediately since voting_duration = 0) - let finalize_result = contract.finalize_proposal(id); - assert_eq!(finalize_result.unwrap(), dao_governance::ProposalStatus::Approved); - } - - #[ink::test] - fn reject_proposal_if_more_no_votes() { - let mut contract = dao_governance::DaoGovernance::new(); - let id = contract.create_proposal("Add Flawed Course".into(), 0); - - contract.vote(id, false).unwrap(); - let result = contract.finalize_proposal(id); - assert_eq!(result.unwrap(), dao_governance::ProposalStatus::Rejected); - } -} diff --git a/contracts/dao-governance/src/governance.rs b/contracts/dao-governance/src/governance.rs index 89bcd65..9d3cba3 100644 --- a/contracts/dao-governance/src/governance.rs +++ b/contracts/dao-governance/src/governance.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; - +use std::collections::HashMap; #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub enum Role { @@ -9,102 +9,164 @@ pub enum Role { Admin, } - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserIdentity { - pub address: String, + pub address: String, pub role: Role, } - -#[cfg_attr(feature = "std", derive(thiserror::Error))] -pub enum GovernanceError { - #[error("You are not authorized to perform this action.")] - Unauthorized, - - #[error("The requested proposal was not found.")] - ProposalNotFound, - - #[error("Invalid operation.")] - InvalidOperation, +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub enum ProposalStatus { + Active, + Accepted, + Rejected, + Expired, } - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Proposal { pub id: u64, pub title: String, pub description: String, - pub proposer: String, + pub proposer: String, pub votes_for: u32, pub votes_against: u32, + pub status: ProposalStatus, + pub end_time: u64, // Unix timestamp for voting deadline } +#[derive(Debug, thiserror::Error)] +pub enum GovernanceError { + #[error("You are not authorized to perform this action.")] + Unauthorized, + #[error("The requested proposal was not found.")] + ProposalNotFound, + #[error("Voting period has expired.")] + VotingExpired, + #[error("User has already voted.")] + AlreadyVoted, + #[error("Invalid end time for proposal.")] + InvalidEndTime, +} pub struct GovernanceDAO { pub proposals: Vec, proposal_counter: u64, + user_roles: HashMap, + votes: HashMap<(u64, String), bool>, + quorum: u32, } impl GovernanceDAO { - - pub fn new() -> Self { + pub fn new(quorum: u32) -> Self { Self { proposals: Vec::new(), proposal_counter: 0, + user_roles: HashMap::new(), + votes: HashMap::new(), + quorum, + } + } + + pub fn set_role(&mut self, caller: &UserIdentity, user: UserIdentity) -> Result<(), GovernanceError> { + if !matches!(caller.role, Role::Admin) { + return Err(GovernanceError::Unauthorized); } + self.user_roles.insert(user.address.clone(), user.role); + Ok(()) } - pub fn submit_proposal( &mut self, user: &UserIdentity, title: String, description: String, + end_time: u64, + current_time: u64, ) -> Result<(), GovernanceError> { - if matches!(user.role, Role::Tutor | Role::Admin) { - self.proposal_counter += 1; - let proposal = Proposal { - id: self.proposal_counter, - title, - description, - proposer: user.address.clone(), - votes_for: 0, - votes_against: 0, - }; - self.proposals.push(proposal); - Ok(()) - } else { - Err(GovernanceError::Unauthorized) + if !matches!(user.role, Role::Tutor | Role::Admin) { + return Err(GovernanceError::Unauthorized); + } + if end_time <= current_time { + return Err(GovernanceError::InvalidEndTime); } + + self.proposal_counter += 1; + let proposal = Proposal { + id: self.proposal_counter, + title, + description, + proposer: user.address.clone(), + votes_for: 0, + votes_against: 0, + status: ProposalStatus::Active, + end_time, + }; + self.proposals.push(proposal); + Ok(()) } - pub fn vote_on_proposal( &mut self, user: &UserIdentity, proposal_id: u64, - support: bool, + support: bool, + current_time: u64, ) -> Result<(), GovernanceError> { - if matches!(user.role, Role::Student | Role::Tutor) { - let proposal = self.proposals - .iter_mut() - .find(|p| p.id == proposal_id) - .ok_or(GovernanceError::ProposalNotFound)?; - - if support { - proposal.votes_for += 1; - } else { - proposal.votes_against += 1; - } - Ok(()) + if !matches!(user.role, Role::Student | Role::Tutor) { + return Err(GovernanceError::Unauthorized); + } + + let proposal = self.proposals + .iter_mut() + .find(|p| p.id == proposal_id) + .ok_or(GovernanceError::ProposalNotFound)?; + + if proposal.status != ProposalStatus::Active { + return Err(GovernanceError::VotingExpired); + } + if current_time > proposal.end_time { + return Err(GovernanceError::VotingExpired); + } + + let vote_key = (proposal_id, user.address.clone()); + if self.votes.contains_key(&vote_key) { + return Err(GovernanceError::AlreadyVoted); + } + + if support { + proposal.votes_for += 1; } else { - Err(GovernanceError::Unauthorized) + proposal.votes_against += 1; } + self.votes.insert(vote_key, support); + Ok(()) + } + + pub fn finalize_proposal(&mut self, proposal_id: u64, current_time: u64) -> Result<(), GovernanceError> { + let proposal = self.proposals + .iter_mut() + .find(|p| p.id == proposal_id) + .ok_or(GovernanceError::ProposalNotFound)?; + + if proposal.status != ProposalStatus::Active { + return Err(GovernanceError::VotingExpired); + } + if current_time <= proposal.end_time { + return Err(GovernanceError::VotingExpired); + } + + let total_votes = proposal.votes_for + proposal.votes_against; + proposal.status = if total_votes >= self.quorum && proposal.votes_for > proposal.votes_against { + ProposalStatus::Accepted + } else { + ProposalStatus::Rejected + }; + Ok(()) } - pub fn list_proposals(&self) -> &Vec { &self.proposals } } + diff --git a/contracts/dao-governance/src/governance_test.rs b/contracts/dao-governance/src/governance_test.rs deleted file mode 100644 index 4ba60dd..0000000 --- a/contracts/dao-governance/src/governance_test.rs +++ /dev/null @@ -1,143 +0,0 @@ -#[cfg(test)] -mod tests { - use super::super::governance::*; - - fn create_tutor(address: &str) -> UserIdentity { - UserIdentity { - address: address.to_string(), - role: Role::Tutor, - } - } - - fn create_student(address: &str) -> UserIdentity { - UserIdentity { - address: address.to_string(), - role: Role::Student, - } - } - - fn create_admin(address: &str) -> UserIdentity { - UserIdentity { - address: address.to_string(), - role: Role::Admin, - } - } - - #[test] - fn tutor_can_submit_proposal() { - let mut dao = GovernanceDAO::new(); - let tutor = create_tutor("0xTUTOR"); - - let result = dao.submit_proposal( - &tutor, - "Proposal Title".to_string(), - "Proposal Description".to_string(), - ); - - assert!(result.is_ok()); - assert_eq!(dao.proposals.len(), 1); - assert_eq!(dao.proposals[0].proposer, "0xTUTOR"); - } - - #[test] - fn admin_can_submit_proposal() { - let mut dao = GovernanceDAO::new(); - let admin = create_admin("0xADMIN"); - - let result = dao.submit_proposal( - &admin, - "Admin Proposal".to_string(), - "Admin Proposal Description".to_string(), - ); - - assert!(result.is_ok()); - assert_eq!(dao.proposals.len(), 1); - assert_eq!(dao.proposals[0].proposer, "0xADMIN"); - } - - #[test] - fn student_cannot_submit_proposal() { - let mut dao = GovernanceDAO::new(); - let student = create_student("0xSTUDENT"); - - let result = dao.submit_proposal( - &student, - "Student Proposal".to_string(), - "Student Proposal Description".to_string(), - ); - - assert!(result.is_err()); - assert_eq!(dao.proposals.len(), 0); - } - - #[test] - fn student_can_vote_on_proposal() { - let mut dao = GovernanceDAO::new(); - let tutor = create_tutor("0xTUTOR"); - let student = create_student("0xSTUDENT"); - - dao.submit_proposal( - &tutor, - "Voting Proposal".to_string(), - "Some description".to_string(), - ) - .unwrap(); - - let result = dao.vote_on_proposal(&student, 1, true); - - assert!(result.is_ok()); - assert_eq!(dao.proposals[0].votes_for, 1); - assert_eq!(dao.proposals[0].votes_against, 0); - } - - #[test] - fn tutor_can_vote_on_proposal() { - let mut dao = GovernanceDAO::new(); - let tutor = create_tutor("0xTUTOR"); - - dao.submit_proposal( - &tutor, - "Proposal to Vote".to_string(), - "Description".to_string(), - ) - .unwrap(); - - let another_tutor = create_tutor("0xTUTOR2"); - - let result = dao.vote_on_proposal(&another_tutor, 1, false); - - assert!(result.is_ok()); - assert_eq!(dao.proposals[0].votes_for, 0); - assert_eq!(dao.proposals[0].votes_against, 1); - } - - #[test] - fn admin_cannot_vote_on_proposal() { - let mut dao = GovernanceDAO::new(); - let tutor = create_tutor("0xTUTOR"); - let admin = create_admin("0xADMIN"); - - dao.submit_proposal( - &tutor, - "Admin Voting Attempt".to_string(), - "Description".to_string(), - ) - .unwrap(); - - let result = dao.vote_on_proposal(&admin, 1, true); - - assert!(result.is_err()); - assert_eq!(dao.proposals[0].votes_for, 0); - assert_eq!(dao.proposals[0].votes_against, 0); - } - - #[test] - fn cannot_vote_on_nonexistent_proposal() { - let mut dao = GovernanceDAO::new(); - let student = create_student("0xSTUDENT"); - - let result = dao.vote_on_proposal(&student, 99, true); // Nonexistent ID - - assert!(matches!(result, Err(GovernanceError::ProposalNotFound))); - } -} diff --git a/contracts/dao-governance/src/lib.rs b/contracts/dao-governance/src/lib.rs index 94c7872..997ba71 100644 --- a/contracts/dao-governance/src/lib.rs +++ b/contracts/dao-governance/src/lib.rs @@ -1,14 +1,27 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(test)] +pub mod governance; + +#[allow(unexpected_cfgs)] #[ink::contract] mod dao_governance { - use ink_storage::Mapping; + use ink::storage::traits::StorageLayout; + use ink::storage::Mapping; use ink_prelude::{string::String, vec::Vec}; use scale::{Encode, Decode}; use scale_info::TypeInfo; - use serde::{Serialize, Deserialize}; - #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, Serialize, Deserialize)] + #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)] + #[cfg_attr(feature = "std", derive(StorageLayout))] + pub enum Role { + Tutor, + Student, + Admin, + } + + #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)] + #[cfg_attr(feature = "std", derive(StorageLayout))] pub enum ProposalStatus { Active, Accepted, @@ -16,88 +29,201 @@ mod dao_governance { Expired, } - #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, Serialize, Deserialize)] + #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)] + #[cfg_attr(feature = "std", derive(StorageLayout))] pub struct Proposal { pub id: u64, - pub content: String, + pub title: String, + pub description: String, + pub proposer: AccountId, pub yes_votes: u32, pub no_votes: u32, pub status: ProposalStatus, pub end_block: u64, } + #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)] + #[cfg_attr(feature = "std", derive(StorageLayout))] + pub enum GovernanceError { + Unauthorized, + ProposalNotFound, + VotingClosed, + VotingExpired, + AlreadyVoted, + InvalidEndBlock, + } + + #[ink(event)] + pub struct ProposalCreated { + #[ink(topic)] + pub proposal_id: u64, + pub title: String, + pub proposer: AccountId, + } + + #[ink(event)] + pub struct Voted { + #[ink(topic)] + pub proposal_id: u64, + pub voter: AccountId, + pub support: bool, + } + + #[ink(event)] + pub struct ProposalFinalized { + #[ink(topic)] + pub proposal_id: u64, + pub status: ProposalStatus, + } + #[ink(storage)] pub struct DaoGovernance { proposals: Mapping, proposal_count: u64, + user_roles: Mapping, + votes: Mapping<(u64, AccountId), bool>, + quorum: u32, } impl DaoGovernance { #[ink(constructor)] - pub fn new() -> Self { + pub fn new(quorum: u32) -> Self { Self { proposals: Mapping::default(), proposal_count: 0, + user_roles: Mapping::default(), + votes: Mapping::default(), + quorum, } } - #[ink(event)] - pub struct ProposalCreated { - #[ink(topic)] - pub proposal_id: u64, - pub content: String, + #[ink(message)] + pub fn set_role(&mut self, user: AccountId, role: Role) -> Result<(), GovernanceError> { + let caller = self.env().caller(); + if !self.user_roles.get(caller).map_or(false, |r| r == Role::Admin) { + return Err(GovernanceError::Unauthorized); + } + self.user_roles.insert(user, &role); + Ok(()) } #[ink(message)] - pub fn create_proposal(&mut self, content: String, end_block: u64) { + pub fn create_proposal(&mut self, title: String, description: String, end_block: u64) -> Result<(), GovernanceError> { + let caller = self.env().caller(); + let role = self.user_roles.get(caller).ok_or(GovernanceError::Unauthorized)?; + if !matches!(role, Role::Tutor | Role::Admin) { + return Err(GovernanceError::Unauthorized); + } + + let current_block = u64::from(self.env().block_number()); + if end_block <= current_block { + return Err(GovernanceError::InvalidEndBlock); + } + let proposal = Proposal { id: self.proposal_count, - content, + title: title.clone(), + description, + proposer: caller, yes_votes: 0, no_votes: 0, status: ProposalStatus::Active, end_block, }; self.proposals.insert(self.proposal_count, &proposal); + self.env().emit_event(ProposalCreated { + proposal_id: self.proposal_count, + title, + proposer: caller, + }); self.proposal_count += 1; + Ok(()) } #[ink(message)] - pub fn vote(&mut self, proposal_id: u64, support: bool) { - let mut proposal = self.proposals.get(proposal_id).expect("Proposal not found"); - assert_eq!(proposal.status, ProposalStatus::Active, "Voting closed"); + pub fn vote(&mut self, proposal_id: u64, support: bool) -> Result<(), GovernanceError> { + let caller = self.env().caller(); + let role = self.user_roles.get(caller).ok_or(GovernanceError::Unauthorized)?; + if !matches!(role, Role::Student | Role::Tutor) { + return Err(GovernanceError::Unauthorized); + } - let block_number = Self::env().block_number(); - assert!(block_number <= proposal.end_block, "Voting period expired"); + let mut proposal = self.proposals.get(proposal_id).ok_or(GovernanceError::ProposalNotFound)?; + if proposal.status != ProposalStatus::Active { + return Err(GovernanceError::VotingClosed); + } + + let current_block = u64::from(self.env().block_number()); + if current_block > proposal.end_block { + return Err(GovernanceError::VotingExpired); + } + + let vote_key = (proposal_id, caller); + if self.votes.get(vote_key).is_some() { + return Err(GovernanceError::AlreadyVoted); + } if support { proposal.yes_votes += 1; } else { proposal.no_votes += 1; } - + self.votes.insert(vote_key, &support); self.proposals.insert(proposal_id, &proposal); + self.env().emit_event(Voted { + proposal_id, + voter: caller, + support, + }); + Ok(()) } #[ink(message)] - pub fn finalize_proposal(&mut self, proposal_id: u64) { - let mut proposal = self.proposals.get(proposal_id).expect("Proposal not found"); - - let block_number = Self::env().block_number(); - assert!(block_number > proposal.end_block, "Voting not ended"); + pub fn finalize_proposal(&mut self, proposal_id: u64) -> Result<(), GovernanceError> { + let mut proposal = self.proposals.get(proposal_id).ok_or(GovernanceError::ProposalNotFound)?; + let current_block = u64::from(self.env().block_number()); + if current_block <= proposal.end_block { + return Err(GovernanceError::VotingExpired); + } if proposal.status != ProposalStatus::Active { - return; + return Err(GovernanceError::VotingClosed); } - proposal.status = if proposal.yes_votes > proposal.no_votes { + let total_votes = proposal.yes_votes + proposal.no_votes; + proposal.status = if total_votes >= self.quorum && proposal.yes_votes > proposal.no_votes { ProposalStatus::Accepted } else { ProposalStatus::Rejected }; self.proposals.insert(proposal_id, &proposal); + self.env().emit_event(ProposalFinalized { + proposal_id, + status: proposal.status.clone(), + }); + Ok(()) } + #[ink(message)] + pub fn get_proposal(&self, proposal_id: u64) -> Option { + self.proposals.get(proposal_id) + } + + #[ink(message)] + pub fn get_role(&self, user: AccountId) -> Option { + self.user_roles.get(user) + } + + #[ink(message)] + pub fn list_proposals(&self) -> Vec { + let mut proposals = Vec::new(); + for id in 0..self.proposal_count { + if let Some(proposal) = self.proposals.get(id) { + proposals.push(proposal); + } + } + proposals + } } } diff --git a/contracts/dao-governance/tests/dao_tests.rs b/contracts/dao-governance/tests/dao_tests.rs new file mode 100644 index 0000000..f5bcfe8 --- /dev/null +++ b/contracts/dao-governance/tests/dao_tests.rs @@ -0,0 +1,191 @@ +#[cfg(test)] +pub mod dao_tests { + use super::dao_governance::*; + use ink::env::test::*; + + fn set_up_contract() -> DaoGovernance { + let accounts = default_accounts(); + set_caller::(accounts.alice); + let mut contract = DaoGovernance::new(2); + contract.set_role(accounts.alice, Role::Admin).unwrap(); + contract.set_role(accounts.bob, Role::Tutor).unwrap(); + contract.set_role(accounts.charlie, Role::Student).unwrap(); + contract + } + + #[ink::test] + fn create_proposal_works() { + let mut contract = set_up_contract(); + set_block_number::(100); + + let result = contract.create_proposal( + "Test Proposal".into(), + "Test Description".into(), + 200, + ); + assert!(result.is_ok()); + let proposal = contract.get_proposal(0).unwrap(); + assert_eq!(proposal.title, "Test Proposal"); + assert_eq!(proposal.description, "Test Description"); + assert_eq!(proposal.proposer, default_accounts().alice); + assert_eq!(proposal.status, ProposalStatus::Active); + assert_eq!(proposal.end_block, 200); + } + + #[ink::test] + fn create_proposal_unauthorized() { + let mut contract = set_up_contract(); + set_caller::(default_accounts().charlie); // Student + let result = contract.create_proposal( + "Test Proposal".into(), + "Test Description".into(), + 200, + ); + assert_eq!(result, Err(GovernanceError::Unauthorized)); + } + + #[ink::test] + fn create_proposal_invalid_end_block() { + let mut contract = set_up_contract(); + set_block_number::(100); + let result = contract.create_proposal( + "Test Proposal".into(), + "Test Description".into(), + 50, + ); + assert_eq!(result, Err(GovernanceError::InvalidEndBlock)); + } + + #[ink::test] + fn vote_works() { + let mut contract = set_up_contract(); + set_block_number::(100); + contract.create_proposal("Test Proposal".into(), "Test Description".into(), 200).unwrap(); + + set_caller::(default_accounts().charlie); // Student + let result = contract.vote(0, true); + assert!(result.is_ok()); + let proposal = contract.get_proposal(0).unwrap(); + assert_eq!(proposal.yes_votes, 1); + assert_eq!(proposal.no_votes, 0); + } + + #[ink::test] + fn vote_unauthorized() { + let mut contract = set_up_contract(); + set_block_number::(100); + contract.create_proposal("Test Proposal".into(), "Test Description".into(), 200).unwrap(); + + set_caller::(default_accounts().alice); // Admin + let result = contract.vote(0, true); + assert_eq!(result, Err(GovernanceError::Unauthorized)); + } + + #[ink::test] + fn vote_double_voting() { + let mut contract = set_up_contract(); + set_block_number::(100); + contract.create_proposal("Test Proposal".into(), "Test Description".into(), 200).unwrap(); + + set_caller::(default_accounts().charlie); + contract.vote(0, true).unwrap(); + let result = contract.vote(0, false); + assert_eq!(result, Err(GovernanceError::AlreadyVoted)); + } + + #[ink::test] + fn vote_expired() { + let mut contract = set_up_contract(); + set_block_number::(100); + contract.create_proposal("Test Proposal".into(), "Test Description".into(), 150).unwrap(); + + set_block_number::(200); + set_caller::(default_accounts().charlie); + let result = contract.vote(0, true); + assert_eq!(result, Err(GovernanceError::VotingExpired)); + } + + #[ink::test] + fn vote_nonexistent_proposal() { + let mut contract = set_up_contract(); + set_caller::(default_accounts().charlie); + let result = contract.vote(99, true); + assert_eq!(result, Err(GovernanceError::ProposalNotFound)); + } + + #[ink::test] + fn finalize_proposal_accepted() { + let mut contract = set_up_contract(); + set_block_number::(100); + contract.create_proposal("Test Proposal".into(), "Test Description".into(), 200).unwrap(); + + set_caller::(default_accounts().bob); + contract.vote(0, true).unwrap(); + set_caller::(default_accounts().charlie); + contract.vote(0, true).unwrap(); + + set_block_number::(201); + let result = contract.finalize_proposal(0); + assert!(result.is_ok()); + let proposal = contract.get_proposal(0).unwrap(); + assert_eq!(proposal.status, ProposalStatus::Accepted); + } + + #[ink::test] + fn finalize_proposal_rejected_quorum_not_met() { + let mut contract = set_up_contract(); + set_block_number::(100); + contract.create_proposal("Test Proposal".into(), "Test Description".into(), 200).unwrap(); + + set_caller::(default_accounts().bob); + contract.vote(0, true).unwrap(); + + set_block_number::(201); + let result = contract.finalize_proposal(0); + assert!(result.is_ok()); + let proposal = contract.get_proposal(0).unwrap(); + assert_eq!(proposal.status, ProposalStatus::Rejected); + } + + #[ink::test] + fn finalize_proposal_rejected_more_no_votes() { + let mut contract = set_up_contract(); + set_block_number::(100); + contract.create_proposal("Test Proposal".into(), "Test Description".into(), 200).unwrap(); + + set_caller::(default_accounts().bob); + contract.vote(0, false).unwrap(); + set_caller::(default_accounts().charlie); + contract.vote(0, false).unwrap(); + + set_block_number::(201); + let result = contract.finalize_proposal(0); + assert!(result.is_ok()); + let proposal = contract.get_proposal(0).unwrap(); + assert_eq!(proposal.status, ProposalStatus::Rejected); + } + + #[ink::test] + fn finalize_before_end_block() { + let mut contract = set_up_contract(); + set_block_number::(100); + contract.create_proposal("Test Proposal".into(), "Test Description".into(), 200).unwrap(); + + set_block_number::(150); + let result = contract.finalize_proposal(0); + assert_eq!(result, Err(GovernanceError::VotingExpired)); + } + + #[ink::test] + fn list_proposals() { + let mut contract = set_up_contract(); + set_block_number::(100); + contract.create_proposal("Proposal 1".into(), "Desc 1".into(), 200).unwrap(); + contract.create_proposal("Proposal 2".into(), "Desc 2".into(), 200).unwrap(); + + let proposals = contract.list_proposals(); + assert_eq!(proposals.len(), 2); + assert_eq!(proposals[0].title, "Proposal 1"); + assert_eq!(proposals[1].title, "Proposal 2"); + } +} \ No newline at end of file diff --git a/contracts/dao-governance/tests/governance_test.rs b/contracts/dao-governance/tests/governance_test.rs new file mode 100644 index 0000000..1f15373 --- /dev/null +++ b/contracts/dao-governance/tests/governance_test.rs @@ -0,0 +1,166 @@ +#[cfg(test)] +pub mod tests { + use super::*; + + fn create_user(address: &str, role: Role) -> UserIdentity { + UserIdentity { + address: address.to_string(), + role, + } + } + + #[test] + fn admin_can_set_role() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let tutor = create_user("0xTUTOR", Role::Tutor); + + let result = dao.set_role(&admin, tutor.clone()); + assert!(result.is_ok()); + assert_eq!(dao.user_roles.get("0xTUTOR"), Some(&Role::Tutor)); + } + + #[test] + fn non_admin_cannot_set_role() { + let mut dao = GovernanceDAO::new(2); + let tutor = create_user("0xTUTOR", Role::Tutor); + let student = create_user("0xSTUDENT", Role::Student); + + let result = dao.set_role(&tutor, student); + assert_eq!(result, Err(GovernanceError::Unauthorized)); + } + + #[test] + fn tutor_can_submit_proposal() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let tutor = create_user("0xTUTOR", Role::Tutor); + dao.set_role(&admin, tutor.clone()).unwrap(); + + let result = dao.submit_proposal(&tutor, "Proposal Title".to_string(), "Description".to_string(), 1000, 500); + assert!(result.is_ok()); + assert_eq!(dao.proposals.len(), 1); + assert_eq!(dao.proposals[0].proposer, "0xTUTOR"); + assert_eq!(dao.proposals[0].status, ProposalStatus::Active); + } + + #[test] + fn student_cannot_submit_proposal() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let student = create_user("0xSTUDENT", Role::Student); + dao.set_role(&admin, student.clone()).unwrap(); + + let result = dao.submit_proposal(&student, "Proposal Title".to_string(), "Description".to_string(), 1000, 500); + assert_eq!(result, Err(GovernanceError::Unauthorized)); + } + + #[test] + fn invalid_end_time_fails() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let tutor = create_user("0xTUTOR", Role::Tutor); + dao.set_role(&admin, tutor.clone()).unwrap(); + + let result = dao.submit_proposal(&tutor, "Proposal Title".to_string(), "Description".to_string(), 400, 500); + assert_eq!(result, Err(GovernanceError::InvalidEndTime)); + } + + #[test] + fn student_can_vote() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let tutor = create_user("0xTUTOR", Role::Tutor); + let student = create_user("0xSTUDENT", Role::Student); + dao.set_role(&admin, tutor.clone()).unwrap(); + dao.set_role(&admin, student.clone()).unwrap(); + + dao.submit_proposal(&tutor, "Proposal Title".to_string(), "Description".to_string(), 1000, 500).unwrap(); + let result = dao.vote_on_proposal(&student, 1, true, 500); + assert!(result.is_ok()); + assert_eq!(dao.proposals[0].votes_for, 1); + } + + #[test] + fn admin_cannot_vote() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let tutor = create_user("0xTUTOR", Role::Tutor); + dao.set_role(&admin, tutor.clone()).unwrap(); + + dao.submit_proposal(&tutor, "Proposal Title".to_string(), "Description".to_string(), 1000, 500).unwrap(); + let result = dao.vote_on_proposal(&admin, 1, true, 500); + assert_eq!(result, Err(GovernanceError::Unauthorized)); + } + + #[test] + fn double_voting_fails() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let tutor = create_user("0xTUTOR", Role::Tutor); + dao.set_role(&admin, tutor.clone()).unwrap(); + + dao.submit_proposal(&tutor, "Proposal Title".to_string(), "Description".to_string(), 1000, 500).unwrap(); + dao.vote_on_proposal(&tutor, 1, true, 500).unwrap(); + let result = dao.vote_on_proposal(&tutor, 1, false, 500); + assert_eq!(result, Err(GovernanceError::AlreadyVoted)); + } + + #[test] + fn vote_on_expired_proposal_fails() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let tutor = create_user("0xTUTOR", Role::Tutor); + dao.set_role(&admin, tutor.clone()).unwrap(); + + dao.submit_proposal(&tutor, "Proposal Title".to_string(), "Description".to_string(), 1000, 500).unwrap(); + let result = dao.vote_on_proposal(&tutor, 1, true, 1500); + assert_eq!(result, Err(GovernanceError::VotingExpired)); + } + + #[test] + fn finalize_proposal_accepted() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let tutor = create_user("0xTUTOR", Role::Tutor); + let student = create_user("0xSTUDENT", Role::Student); + dao.set_role(&admin, tutor.clone()).unwrap(); + dao.set_role(&admin, student.clone()).unwrap(); + + dao.submit_proposal(&tutor, "Proposal Title".to_string(), "Description".to_string(), 1000, 500).unwrap(); + dao.vote_on_proposal(&tutor, 1, true, 500).unwrap(); + dao.vote_on_proposal(&student, 1, true, 500).unwrap(); + let result = dao.finalize_proposal(1, 1500); + assert!(result.is_ok()); + assert_eq!(dao.proposals[0].status, ProposalStatus::Accepted); + } + + #[test] + fn finalize_proposal_rejected() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let tutor = create_user("0xTUTOR", Role::Tutor); + let student = create_user("0xSTUDENT", Role::Student); + dao.set_role(&admin, tutor.clone()).unwrap(); + dao.set_role(&admin, student.clone()).unwrap(); + + dao.submit_proposal(&tutor, "Proposal Title".to_string(), "Description".to_string(), 1000, 500).unwrap(); + dao.vote_on_proposal(&tutor, 1, false, 500).unwrap(); + dao.vote_on_proposal(&student, 1, false, 500).unwrap(); + let result = dao.finalize_proposal(1, 1500); + assert!(result.is_ok()); + assert_eq!(dao.proposals[0].status, ProposalStatus::Rejected); + } + + #[test] + fn finalize_before_end_time_fails() { + let mut dao = GovernanceDAO::new(2); + let admin = create_user("0xADMIN", Role::Admin); + let tutor = create_user("0xTUTOR", Role::Tutor); + dao.set_role(&admin, tutor.clone()).unwrap(); + + dao.submit_proposal(&tutor, "Proposal Title".to_string(), "Description".to_string(), 1000, 500).unwrap(); + let result = dao.finalize_proposal(1, 500); + assert_eq!(result, Err(GovernanceError::VotingExpired)); + } +} \ No newline at end of file