diff --git a/Cargo.lock b/Cargo.lock index 232c102310..e55136f00a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,7 +131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d0864d84b8e07b145449be9a8537db86bf9de5ce03b913214694643b4743502" dependencies = [ "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -159,9 +159,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.45" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" +checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" dependencies = [ "backtrace-sys", "cfg-if", @@ -171,9 +171,9 @@ dependencies = [ [[package]] name = "backtrace-sys" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" +checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" dependencies = [ "cc", "libc", @@ -230,9 +230,13 @@ checksum = "5da9b3d9f6f585199287a473f4f8dfab6566cf827d15c00c219f53c645687ead" [[package]] name = "bitvec" -version = "0.15.2" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6" +checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" +dependencies = [ + "either", + "radium", +] [[package]] name = "blake2" @@ -309,9 +313,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" +checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" [[package]] name = "byte-slice-cast" @@ -459,7 +463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f1af9ac737b2dd2d577701e59fd09ba34822f6f2ebdb30a7647405d9e55e16a" dependencies = [ "const-random-macro", - "proc-macro-hack 0.5.12", + "proc-macro-hack 0.5.15", ] [[package]] @@ -469,7 +473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a" dependencies = [ "getrandom", - "proc-macro-hack 0.5.12", + "proc-macro-hack 0.5.15", ] [[package]] @@ -776,9 +780,9 @@ checksum = "516aa8d7a71cb00a1c4146f0798549b93d083d4f189b3ced8f3de6b8f11ee6c4" [[package]] name = "erased-serde" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7d80305c9bd8cd78e3c753eb9fb110f83621e5211f1a3afffcc812b104daf9" +checksum = "d88b6d1705e16a4d62e05ea61cc0496c2bd190f4fa8e5c1f11ce747be6bcf3d1" dependencies = [ "serde", ] @@ -809,9 +813,9 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", "synstructure", ] @@ -1045,10 +1049,10 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" dependencies = [ - "proc-macro-hack 0.5.12", - "proc-macro2 1.0.9", + "proc-macro-hack 0.5.15", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -1108,7 +1112,7 @@ dependencies = [ "futures-task", "memchr", "pin-utils", - "proc-macro-hack 0.5.12", + "proc-macro-hack 0.5.15", "proc-macro-nested", "slab", ] @@ -1269,9 +1273,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" +checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" dependencies = [ "libc", ] @@ -1305,7 +1309,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "961de220ec9a91af2e1e5bd80d02109155695e516771762381ef8581317066e0" dependencies = [ "hex-literal-impl 0.2.1", - "proc-macro-hack 0.5.12", + "proc-macro-hack 0.5.15", ] [[package]] @@ -1323,7 +1327,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d4c5c844e2fee0bf673d54c2c177f1713b3d2af2ff6e666b49cb7572e6cf42d" dependencies = [ - "proc-macro-hack 0.5.12", + "proc-macro-hack 0.5.15", ] [[package]] @@ -1487,9 +1491,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef5550a42e3740a0e71f909d4c861056a284060af885ae7aa6242820f920d9d" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -1655,9 +1659,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cb931d43e71f560c81badb0191596562bafad2be06a3f9025b845c847c60df5" +checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" dependencies = [ "wasm-bindgen", ] @@ -1720,9 +1724,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8609af8f63b626e8e211f52441fcdb6ec54f1a446606b10d5c89ae9bf8a20058" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -2357,8 +2361,8 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e37c5d4cd9473c5f4c9c111f033f15d4df9bd378fdf615944e360a4f55a05f0b" dependencies = [ - "proc-macro2 1.0.9", - "syn 1.0.16", + "proc-macro2 1.0.10", + "syn 1.0.17", "synstructure", ] @@ -2504,9 +2508,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5a615a1ad92048ad5d9633251edb7492b8abc057d7a679a9898476aef173935" dependencies = [ "cfg-if", - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -2755,9 +2759,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f509c5e67ca0605ee17dcd3f91ef41cadd685c75a298fb6261b781a5acb3f910" +checksum = "329c8f7f4244ddb5c37c103641027a76c530e65e8e4b8240b29f81ea40508b17" dependencies = [ "arrayvec 0.5.1", "bitvec", @@ -2773,9 +2777,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a0ec292e92e8ec7c58e576adacc1e3f399c597c8f263c42f18420abe58e7245" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -2926,24 +2930,24 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e1afe738d71b1ebab5f1207c055054015427dbfc7bbe9ee1266894156ec046" +checksum = "ab4fb1930692d1b6a9cfabdde3d06ea0a7d186518e2f4d67660d8970e2fa647a" dependencies = [ "paste-impl", - "proc-macro-hack 0.5.12", + "proc-macro-hack 0.5.15", ] [[package]] name = "paste-impl" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4dc4a7f6f743211c5aab239640a65091535d97d43d92a52bca435a640892bb" +checksum = "a62486e111e571b1e93b710b61e8f493c0013be39629b714cb166bdb06aa5a8a" dependencies = [ - "proc-macro-hack 0.5.12", - "proc-macro2 1.0.9", + "proc-macro-hack 0.5.15", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -3063,9 +3067,9 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -3079,14 +3083,9 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.12" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f918f2b601f93baa836c1c2945faef682ba5b6d4828ecb45eeb7cc3c71b811b4" -dependencies = [ - "proc-macro2 1.0.9", - "quote 1.0.3", - "syn 1.0.16", -] +checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" [[package]] name = "proc-macro-hack-impl" @@ -3111,9 +3110,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" dependencies = [ "unicode-xid 0.2.0", ] @@ -3197,9 +3196,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", ] +[[package]] +name = "radium" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" + [[package]] name = "rand" version = "0.3.23" @@ -3424,9 +3429,9 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "regex" -version = "1.3.5" +version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8900ebc1363efa7ea1c399ccc32daed870b4002651e0bed86e72d501ebbe0048" +checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" dependencies = [ "aho-corasick", "memchr", @@ -3451,9 +3456,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.11" +version = "0.16.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "741ba1704ae21999c00942f9f5944f801e977f54302af346b596287599ad1862" +checksum = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c" dependencies = [ "cc", "lazy_static", @@ -3606,29 +3611,29 @@ checksum = "a0eddf2e8f50ced781f288c19f18621fa72a3779e3cb58dbf23b07469b0abeb4" [[package]] name = "serde" -version = "1.0.104" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.104" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" +checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] name = "serde_json" -version = "1.0.48" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" +checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" dependencies = [ "itoa", "ryu", @@ -3706,7 +3711,7 @@ dependencies = [ [[package]] name = "slog-async" version = "2.3.0" -source = "git+https://github.com/paritytech/slog-async#107848e7ded5e80dc43f6296c2b96039eb92c0a5" +source = "git+https://github.com/paritytech/slog-async#0329dc74feb3afe93d0cd2533a472b7ceab44aaf" dependencies = [ "crossbeam-channel", "slog", @@ -3810,9 +3815,9 @@ source = "git+https://github.com/paritytech/substrate.git?rev=c37bb08535c49a1232 dependencies = [ "blake2-rfc", "proc-macro-crate", - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -4129,9 +4134,9 @@ version = "2.0.0" source = "git+https://github.com/paritytech/substrate.git?rev=c37bb08535c49a12320af7facfd555ce05cce2e8#c37bb08535c49a12320af7facfd555ce05cce2e8" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -4174,11 +4179,11 @@ name = "srml-support-procedural" version = "2.0.0" source = "git+https://github.com/paritytech/substrate.git?rev=c37bb08535c49a12320af7facfd555ce05cce2e8#c37bb08535c49a12320af7facfd555ce05cce2e8" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", "sr-api-macros", "srml-support-procedural-tools", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -4187,10 +4192,10 @@ version = "2.0.0" source = "git+https://github.com/paritytech/substrate.git?rev=c37bb08535c49a12320af7facfd555ce05cce2e8#c37bb08535c49a12320af7facfd555ce05cce2e8" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", "srml-support-procedural-tools-derive", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -4198,9 +4203,9 @@ name = "srml-support-procedural-tools-derive" version = "2.0.0" source = "git+https://github.com/paritytech/substrate.git?rev=c37bb08535c49a12320af7facfd555ce05cce2e8#c37bb08535c49a12320af7facfd555ce05cce2e8" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -4323,9 +4328,9 @@ checksum = "ea692d40005b3ceba90a9fe7a78fa8d4b82b0ce627eebbffc329aab850f3410e" dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -4441,9 +4446,9 @@ version = "2.0.0" source = "git+https://github.com/paritytech/substrate.git?rev=c37bb08535c49a12320af7facfd555ce05cce2e8#c37bb08535c49a12320af7facfd555ce05cce2e8" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -4648,6 +4653,24 @@ dependencies = [ "substrate-primitives", ] +[[package]] +name = "substrate-content-directory-module" +version = "1.0.1" +dependencies = [ + "hex-literal 0.1.4", + "parity-scale-codec", + "quote 0.6.13", + "serde", + "sr-io", + "sr-primitives", + "sr-std", + "srml-support", + "srml-support-procedural", + "srml-system", + "srml-timestamp", + "substrate-primitives", +] + [[package]] name = "substrate-content-working-group-module" version = "1.0.0" @@ -4678,9 +4701,9 @@ name = "substrate-debug-derive" version = "2.0.0" source = "git+https://github.com/paritytech/substrate.git?rev=c37bb08535c49a12320af7facfd555ce05cce2e8#c37bb08535c49a12320af7facfd555ce05cce2e8" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", ] [[package]] @@ -5478,11 +5501,11 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", "unicode-xid 0.2.0", ] @@ -5493,9 +5516,9 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", "unicode-xid 0.2.0", ] @@ -6072,9 +6095,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.59" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3557c397ab5a8e347d434782bcd31fc1483d927a6826804cec05cc792ee2519d" +checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -6082,16 +6105,16 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.59" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8" +checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" dependencies = [ "bumpalo", "lazy_static", "log", - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", "wasm-bindgen-shared", ] @@ -6110,9 +6133,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.59" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6fde1d36e75a714b5fe0cffbb78978f222ea6baebb726af13c78869fdb4205" +checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" dependencies = [ "quote 1.0.3", "wasm-bindgen-macro-support", @@ -6120,22 +6143,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.59" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bda4168030a6412ea8a047e27238cadf56f0e53516e1e83fec0a8b7c786f6d" +checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.59" +version = "0.2.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc9f36ad51f25b0219a3d4d13b90eb44cd075dff8b6280cca015775d7acaddd8" +checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" [[package]] name = "wasm-timer" @@ -6176,9 +6199,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721c6263e2c66fd44501cc5efbfa2b7dfa775d13e4ea38c46299646ed1f9c70a" +checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" dependencies = [ "js-sys", "wasm-bindgen", @@ -6252,9 +6275,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" +checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" dependencies = [ "winapi 0.3.8", ] @@ -6354,8 +6377,8 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", "synstructure", ] diff --git a/Cargo.toml b/Cargo.toml index f2195200f3..0b9bbaf16e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,11 @@ members = [ "runtime-modules/token-minting", "runtime-modules/versioned-store", "runtime-modules/versioned-store-permissions", + "runtime-modules/content-directory", "node", "utils/chain-spec-builder/" ] [profile.release] # Substrate runtime requires unwinding. -panic = "unwind" \ No newline at end of file +panic = "unwind" diff --git a/runtime-modules/content-directory/Cargo.toml b/runtime-modules/content-directory/Cargo.toml new file mode 100755 index 0000000000..1be0f7f5af --- /dev/null +++ b/runtime-modules/content-directory/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = 'substrate-content-directory-module' +version = '1.0.1' +authors = ['Joystream contributors'] +edition = '2018' + +[dependencies] +hex-literal = '0.1.0' +codec = { package = 'parity-scale-codec', version = '1.0.0', default-features = false, features = ['derive'] } +rstd = { package = 'sr-std', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'} +runtime-primitives = { package = 'sr-primitives', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'} +srml-support = { package = 'srml-support', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'} +srml-support-procedural = { package = 'srml-support-procedural', git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'} +system = { package = 'srml-system', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'} +timestamp = { package = 'srml-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'} +runtime-io = { package = 'sr-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'} +# https://users.rust-lang.org/t/failure-derive-compilation-error/39062 +quote = '<=1.0.2' + +[dependencies.serde] +features = ['derive'] +optional = true +version = '1.0.101' + +[dev-dependencies] +runtime-io = { package = 'sr-io', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'} +primitives = { package = 'substrate-primitives', git = 'https://github.com/paritytech/substrate.git', rev = 'c37bb08535c49a12320af7facfd555ce05cce2e8'} + +[features] +default = ['std'] +std = [ + 'serde', + 'codec/std', + 'rstd/std', + 'runtime-io/std', + 'runtime-primitives/std', + 'srml-support/std', + 'system/std', + 'timestamp/std', +] diff --git a/runtime-modules/content-directory/src/errors.rs b/runtime-modules/content-directory/src/errors.rs new file mode 100644 index 0000000000..33ee7e208e --- /dev/null +++ b/runtime-modules/content-directory/src/errors.rs @@ -0,0 +1,114 @@ +// Validation errors +// -------------------------------------- + +pub const ERROR_PROPERTY_NAME_TOO_SHORT: &str = "Property name is too short"; +pub const ERROR_PROPERTY_NAME_TOO_LONG: &str = "Property name is too long"; +pub const ERROR_PROPERTY_DESCRIPTION_TOO_SHORT: &str = "Property description is too long"; +pub const ERROR_PROPERTY_DESCRIPTION_TOO_LONG: &str = "Property description is too long"; + +pub const ERROR_CLASS_NAME_TOO_SHORT: &str = "Class name is too short"; +pub const ERROR_CLASS_NAME_TOO_LONG: &str = "Class name is too long"; +pub const ERROR_CLASS_DESCRIPTION_TOO_SHORT: &str = "Class description is too long"; +pub const ERROR_CLASS_DESCRIPTION_TOO_LONG: &str = "Class description is too long"; + +pub const ERROR_CLASS_LIMIT_REACHED: &str = "Maximum number of classes limit reached"; +pub const ERROR_CLASS_SCHEMAS_LIMIT_REACHED: &str = + "Maximum number of given class schemas limit reached"; +pub const ERROR_CLASS_PROPERTIES_LIMIT_REACHED: &str = + "Maximum number of properties in schema limit reached"; +pub const ERROR_PER_CONTROLLER_ENTITIES_CREATION_LIMIT_EXCEEDS_OVERALL_LIMIT: &str = + "Entities creation limit per controller should be less than overall entities creation limit"; +pub const ERROR_ENTITIES_NUMBER_PER_CLASS_CONSTRAINT_VIOLATED: &str = + "Number of entities per class is to big"; +pub const ERROR_NUMBER_OF_CLASS_ENTITIES_PER_ACTOR_CONSTRAINT_VIOLATED: &str = + "Number of class entities per actor constraint violated"; +pub const ERROR_INDIVIDUAL_NUMBER_OF_CLASS_ENTITIES_PER_ACTOR_IS_TOO_BIG: &str = + "Individual number of class entities per actor is too big"; +pub const ERROR_NEW_ENTITIES_MAX_COUNT_IS_LESS_THAN_NUMBER_OF_ALREADY_CREATED: &str = + "Cannot set voucher entities count to be less than number of already created entities"; +pub const ERROR_MAX_NUMBER_OF_OPERATIONS_DURING_ATOMIC_BATCHING_LIMIT_REACHED: &str = + "Number of operations during atomic batching limit reached"; +pub const ERROR_TEXT_PROP_IS_TOO_LONG: &str = "Text property is too long"; +pub const ERROR_VEC_PROP_IS_TOO_LONG: &str = "Vector property is too long"; +pub const ERROR_ENTITY_PROP_VALUE_VECTOR_IS_TOO_LONG: &str = + "Propery value vector can`t contain more values"; +pub const ERROR_ENTITY_PROP_VALUE_VECTOR_INDEX_IS_OUT_OF_RANGE: &str = + "Given property value vector index is out of range"; + +// Main logic errors +// -------------------------------------- + +pub const ERROR_CLASS_NOT_FOUND: &str = "Class was not found by id"; +pub const ERROR_UNKNOWN_CLASS_SCHEMA_ID: &str = "Unknown class schema id"; +pub const ERROR_CLASS_SCHEMA_NOT_ACTIVE: &str = "Given class schema is not active"; +pub const ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_PROP_INDEX: &str = + "New class schema refers to an unknown property index"; +pub const ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_CLASS: &str = + "New class schema refers to an unknown class id"; +pub const ERROR_NO_PROPS_IN_CLASS_SCHEMA: &str = + "Cannot add a class schema with an empty list of properties"; +pub const ERROR_ENTITY_NOT_FOUND: &str = "Entity was not found by id"; +pub const ERROR_SCHEMA_ALREADY_ADDED_TO_ENTITY: &str = + "Cannot add a schema that is already added to this entity"; +pub const ERROR_PROP_VALUE_DONT_MATCH_TYPE: &str = + "Some of the provided property values don't match the expected property type"; +pub const ERROR_PROP_VALUE_DONT_MATCH_VEC_TYPE: &str = + "Property value don't match the expected vector property type"; +pub const ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR: &str = + "Property value under given index is not a vector"; +pub const ERROR_PROP_VALUE_VEC_NONCES_DOES_NOT_MATCH: &str = + "Current property value vector nonce does not equal to provided one"; +pub const ERROR_PROP_NAME_NOT_UNIQUE_IN_A_CLASS: &str = + "Property name is not unique within its class"; +pub const ERROR_MISSING_REQUIRED_PROP: &str = + "Some required property was not found when adding schema support to entity"; +pub const ERROR_UNKNOWN_ENTITY_PROP_ID: &str = "Some of the provided property ids cannot be found on the current list of propery values of this entity"; +pub const ERROR_PROP_VALUE_TYPE_DOESNT_MATCH_INTERNAL_ENTITY_VECTOR_TYPE: &str = + "Propery value type does not match internal entity vector type"; +pub const ERROR_PROP_DOES_NOT_MATCH_ITS_CLASS: &str = "Internal property does not match its class"; +pub const ERROR_ENTITY_RC_DOES_NOT_EQUAL_TO_ZERO: &str = + "Entity removal can`t be completed, as there are some property values pointing to given entity"; +pub const ERROR_ENTITY_INBOUND_SAME_OWNER_RC_DOES_NOT_EQUAL_TO_ZERO: &str = + "Entity ownership transfer can`t be completed, as there are some property values pointing to given entity with same owner flag set"; +pub const ERROR_CLASS_PROP_NOT_FOUND: &str = "Class property under given index not found"; +pub const ERROR_CURATOR_GROUP_REMOVAL_FORBIDDEN: &str = + "Curator group can`t be removed, as it currently maintains at least one class"; + +// Permission errors + +pub const ERROR_ALL_PROP_WERE_LOCKED_ON_CLASS_LEVEL: &str = + "All property values, related to a given entity were locked on class level"; +pub const ERROR_CURATOR_IS_NOT_A_MEMBER_OF_A_GIVEN_CURATOR_GROUP: &str = + "Curator under provided curator id is not a member of curaror group under given id"; +pub const ERROR_CURATOR_GROUP_DOES_NOT_EXIST: &str = "Given curator group does not exist"; +pub const ERROR_SAME_CONTROLLER_CONSTRAINT_VIOLATION: &str = + "Entity should be referenced from the entity, owned by the same controller"; +pub const ERROR_MAINTAINER_DOES_NOT_EXIST: &str = "Given maintainer does not exist"; +pub const ERROR_MAINTAINER_ALREADY_EXISTS: &str = "Given maintainer already exist"; +pub const ERROR_ACTOR_CAN_NOT_CREATE_ENTITIES: &str = + "Provided actor can`t create entities of given class"; +pub const ERROR_MAX_NUMBER_OF_ENTITIES_PER_CLASS_LIMIT_REACHED: &str = + "Maximum numbers of entities per class limit reached"; +pub const ERROR_ENTITY_CREATION_BLOCKED: &str = "Current class entities creation blocked"; +pub const ERROR_VOUCHER_LIMIT_REACHED: &str = "Entities voucher limit reached"; +pub const ERROR_LEAD_AUTH_FAILED: &str = "Lead authentication failed"; +pub const ERROR_MEMBER_AUTH_FAILED: &str = "Member authentication failed"; +pub const ERROR_CURATOR_AUTH_FAILED: &str = "Curator authentication failed"; +pub const ERROR_BAD_ORIGIN: &str = "Expected root or signed origin"; +pub const ERROR_ENTITY_REMOVAL_ACCESS_DENIED: &str = "Entity removal access denied"; +pub const ERROR_ENTITY_ADD_SCHEMA_SUPPORT_ACCESS_DENIED: &str = + "Add entity schema support access denied"; +pub const ERROR_CLASS_ACCESS_DENIED: &str = "Class access denied"; +pub const ERROR_ENTITY_ACCESS_DENIED: &str = "Entity access denied"; +pub const ERROR_ENTITY_CAN_NOT_BE_REFRENCED: &str = "Given entity can`t be referenced"; +pub const ERROR_CLASS_PROPERTY_TYPE_IS_LOCKED_FOR_GIVEN_ACTOR: &str = + "Given class property type is locked for updating"; +pub const ERROR_NUMBER_OF_MAINTAINERS_PER_CLASS_LIMIT_REACHED: &str = + "Class maintainers limit reached"; +pub const ERROR_NUMBER_OF_CURATORS_PER_GROUP_LIMIT_REACHED: &str = + "Max number of curators per group limit reached"; +pub const ERROR_CURATOR_GROUP_IS_NOT_ACTIVE: &str = "Curator group is not active"; +pub const ERROR_ORIGIN_CANNOT_BE_MADE_INTO_RAW_ORIGIN: &str = + "Origin cannot be made into raw origin"; +pub const ERROR_PROPERTY_VALUE_SHOULD_BE_UNIQUE: &str = + "Provided property value should be unique across all entity property values"; diff --git a/runtime-modules/content-directory/src/lib.rs b/runtime-modules/content-directory/src/lib.rs new file mode 100755 index 0000000000..f53db99b31 --- /dev/null +++ b/runtime-modules/content-directory/src/lib.rs @@ -0,0 +1,1999 @@ +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "256"] + +use codec::{Codec, Decode, Encode}; +use rstd::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; +use rstd::prelude::*; +use runtime_primitives::traits::{MaybeSerializeDeserialize, Member, One, SimpleArithmetic, Zero}; +use srml_support::{ + decl_event, decl_module, decl_storage, dispatch, ensure, traits::Get, Parameter, + StorageDoubleMap, +}; +use std::hash::Hash; +use system::ensure_signed; + +#[cfg(feature = "std")] +pub use serde::{Deserialize, Serialize}; + +mod errors; +mod mock; +mod operations; +mod permissions; +mod schema; +mod tests; + +use core::fmt::Debug; +pub use errors::*; +pub use operations::*; +pub use permissions::*; +pub use schema::*; + +type MaxNumber = u32; + +/// Type, respresenting inbound entities rc for each entity +type ReferenceCounter = u32; + +pub trait Trait: system::Trait + ActorAuthenticator + Debug + Clone { + /// The overarching event type. + type Event: From> + Into<::Event>; + + /// Nonce type is used to avoid data race update conditions, when performing property value vector operations + type Nonce: Parameter + + Member + + SimpleArithmetic + + Codec + + Default + + Copy + + Clone + + One + + Zero + + MaybeSerializeDeserialize + + Eq + + PartialEq + + Ord + + From; + + /// Type of identifier for classes + type ClassId: Parameter + + Member + + SimpleArithmetic + + Codec + + Default + + Copy + + Clone + + One + + Zero + + MaybeSerializeDeserialize + + Eq + + PartialEq + + Ord; + + /// Type of identifier for entities + type EntityId: Parameter + + Member + + SimpleArithmetic + + Codec + + Default + + Copy + + Clone + + Hash + + One + + Zero + + MaybeSerializeDeserialize + + Eq + + PartialEq + + Ord; + + /// Security/configuration constraints + + /// Type, representing min & max property name length constraints + type PropertyNameLengthConstraint: Get; + + /// Type, representing min & max property description length constraints + type PropertyDescriptionLengthConstraint: Get; + + /// Type, representing min & max class name length constraints + type ClassNameLengthConstraint: Get; + + /// Type, representing min & max class description length constraints + type ClassDescriptionLengthConstraint: Get; + + /// The maximum number of classes + type MaxNumberOfClasses: Get; + + /// The maximum number of maintainers per class constraint + type MaxNumberOfMaintainersPerClass: Get; + + /// The maximum number of curators per group constraint + type MaxNumberOfCuratorsPerGroup: Get; + + /// The maximum number of schemas per class constraint + type NumberOfSchemasPerClass: Get; + + /// The maximum number of properties per class constraint + type MaxNumberOfPropertiesPerClass: Get; + + /// The maximum number of operations during single invocation of `transaction` + type MaxNumberOfOperationsDuringAtomicBatching: Get; + + /// The maximum length of vector property value constarint + type VecMaxLengthConstraint: Get; + + /// The maximum length of text property value constarint + type TextMaxLengthConstraint: Get; + + /// Entities creation constraint per class + type MaxNumberOfEntitiesPerClass: Get; + + /// Entities creation constraint per individual + type IndividualEntitiesCreationLimit: Get; +} + +/// Length constraint for input validation +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Default, Clone, Copy, PartialEq, Eq, Debug)] +pub struct InputValidationLengthConstraint { + /// Minimum length + pub min: u16, + + /// Difference between minimum length and max length. + /// While having max would have been more direct, this + /// way makes max < min unrepresentable semantically, + /// which is safer. + pub max_min_diff: u16, +} + +/// Structure, representing `inbound_entity_rcs` & `inbound_same_owner_entity_rcs` mappings to their respective count for each referenced entity id +pub struct EntitiesRc { + /// Entities, which inbound same owner rc should be changed + pub inbound_entity_rcs: BTreeMap, + + /// Entities, which rc should be changed (only includes entity ids, which are not in inbound_entity_rcs already) + pub inbound_same_owner_entity_rcs: BTreeMap, +} + +impl Default for EntitiesRc { + fn default() -> Self { + Self { + inbound_entity_rcs: BTreeMap::default(), + inbound_same_owner_entity_rcs: BTreeMap::default(), + } + } +} + +impl EntitiesRc { + /// Fill in one of inbound entity rcs mappings, based on `same_owner` flag provided + fn fill_in_entity_rcs(&mut self, entity_ids: Vec, same_owner: bool) { + let inbound_entity_rcs = if same_owner { + &mut self.inbound_same_owner_entity_rcs + } else { + &mut self.inbound_entity_rcs + }; + + for entity_id in entity_ids { + *inbound_entity_rcs.entry(entity_id).or_insert(0) += 1; + } + } + + /// Traverse `inbound_entity_rcs` & `inbound_same_owner_entity_rcs`, + /// increasing each `Entity` respective reference counters + fn increase_entity_rcs(self) { + self.inbound_same_owner_entity_rcs + .iter() + .for_each(|(entity_id, rc)| { + Module::::increase_entity_rcs(entity_id, *rc, true); + }); + self.inbound_entity_rcs.iter().for_each(|(entity_id, rc)| { + Module::::increase_entity_rcs(entity_id, *rc, false); + }); + } + + /// Traverse `inbound_entity_rcs` & `inbound_same_owner_entity_rcs`, + /// decreasing each `Entity` respective reference counters + fn decrease_entity_rcs(self) { + self.inbound_same_owner_entity_rcs + .iter() + .for_each(|(entity_id, rc)| { + Module::::decrease_entity_rcs(entity_id, *rc, true); + }); + self.inbound_entity_rcs.iter().for_each(|(entity_id, rc)| { + Module::::decrease_entity_rcs(entity_id, *rc, false); + }); + } +} + +impl InputValidationLengthConstraint { + pub fn new(min: u16, max_min_diff: u16) -> Self { + Self { min, max_min_diff } + } + + /// Helper for computing max + pub fn max(self) -> u16 { + self.min + self.max_min_diff + } + + pub fn ensure_valid( + self, + len: usize, + too_short_msg: &'static str, + too_long_msg: &'static str, + ) -> Result<(), &'static str> { + let length = len as u16; + if length < self.min { + Err(too_short_msg) + } else if length > self.max() { + Err(too_long_msg) + } else { + Ok(()) + } + } +} + +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub struct Class { + /// Permissions for an instance of a Class. + class_permissions: ClassPermissions, + /// All properties that have been used on this class across different class schemas. + /// Unlikely to be more than roughly 20 properties per class, often less. + /// For Person, think "height", "weight", etc. + pub properties: Vec>, + + /// All schemas that are available for this class, think v0.0 Person, v.1.0 Person, etc. + pub schemas: Vec, + + pub name: Vec, + + pub description: Vec, + + /// The maximum number of entities which can be created. + maximum_entities_count: T::EntityId, + + /// The current number of entities which exist. + current_number_of_entities: T::EntityId, + + /// How many entities a given controller may create at most. + default_entity_creation_voucher_upper_bound: T::EntityId, +} + +impl Default for Class { + fn default() -> Self { + Self { + class_permissions: ClassPermissions::::default(), + properties: vec![], + schemas: vec![], + name: vec![], + description: vec![], + maximum_entities_count: T::EntityId::default(), + current_number_of_entities: T::EntityId::default(), + default_entity_creation_voucher_upper_bound: T::EntityId::default(), + } + } +} + +impl Class { + /// Create new `Class` with provided parameters + fn new( + class_permissions: ClassPermissions, + name: Vec, + description: Vec, + maximum_entities_count: T::EntityId, + default_entity_creation_voucher_upper_bound: T::EntityId, + ) -> Self { + Self { + class_permissions, + properties: vec![], + schemas: vec![], + name, + description, + maximum_entities_count, + current_number_of_entities: T::EntityId::zero(), + default_entity_creation_voucher_upper_bound, + } + } + + /// Used to update `Schema` status under given `schema_index` + fn update_schema_status(&mut self, schema_index: SchemaId, schema_status: bool) { + // Such indexing is safe, when length bounds were previously checked + self.schemas[schema_index as usize].set_status(schema_status); + } + + /// Used to update `Class` permissions + fn update_permissions(&mut self, permissions: ClassPermissions) { + self.class_permissions = permissions + } + + /// Increment number of entities, associated with this class + fn increment_entities_count(&mut self) { + self.current_number_of_entities += T::EntityId::one(); + } + + /// Decrement number of entities, associated with this class + fn decrement_entities_count(&mut self) { + self.current_number_of_entities -= T::EntityId::one(); + } + + /// Retrieve `ClassPermissions` by mutable reference + fn get_permissions_mut(&mut self) -> &mut ClassPermissions { + &mut self.class_permissions + } + + /// Retrieve `ClassPermissions` by reference + fn get_permissions_ref(&self) -> &ClassPermissions { + &self.class_permissions + } + + /// Retrieve `ClassPermissions` by value + fn get_permissions(self) -> ClassPermissions { + self.class_permissions + } + + /// Retrieve `Class` properties by reference + fn get_properties_ref(&self) -> &[Property] { + &self.properties + } + + /// Get per controller `Class`- specific limit + pub fn get_default_entity_creation_voucher_upper_bound(&self) -> T::EntityId { + self.default_entity_creation_voucher_upper_bound + } + + /// Retrive the maximum entities count, which can be created for given `Class` + pub fn get_maximum_entities_count(&self) -> T::EntityId { + self.maximum_entities_count + } + + /// Ensure `Class` `Schema` under given index exist, return corresponding `Schema` + fn ensure_schema_exists(&self, schema_index: SchemaId) -> Result<&Schema, &'static str> { + self.schemas + .get(schema_index as usize) + .map(|schema| schema) + .ok_or(ERROR_UNKNOWN_CLASS_SCHEMA_ID) + } + + /// Ensure `schema_id` is a valid index of `Class` schemas vector + pub fn ensure_schema_id_exists(&self, schema_id: SchemaId) -> dispatch::Result { + ensure!( + schema_id < self.schemas.len() as SchemaId, + ERROR_UNKNOWN_CLASS_SCHEMA_ID + ); + Ok(()) + } + + /// Ensure `Schema`s limit per `Class` not reached + pub fn ensure_schemas_limit_not_reached(&self) -> dispatch::Result { + ensure!( + T::NumberOfSchemasPerClass::get() < self.schemas.len() as MaxNumber, + ERROR_CLASS_SCHEMAS_LIMIT_REACHED + ); + Ok(()) + } + + /// Ensure properties limit per `Class` not reached + pub fn ensure_properties_limit_not_reached( + &self, + new_properties: &[Property], + ) -> dispatch::Result { + ensure!( + T::MaxNumberOfPropertiesPerClass::get() + <= (self.properties.len() + new_properties.len()) as MaxNumber, + ERROR_CLASS_PROPERTIES_LIMIT_REACHED + ); + Ok(()) + } + + /// Ensure `Class` specific entities limit not reached + pub fn ensure_maximum_entities_count_limit_not_reached(&self) -> dispatch::Result { + ensure!( + self.current_number_of_entities < self.maximum_entities_count, + ERROR_MAX_NUMBER_OF_ENTITIES_PER_CLASS_LIMIT_REACHED + ); + Ok(()) + } + + /// Ensure `Property` under given `PropertyId` is unlocked from actor with given `EntityAccessLevel` + /// return corresponding `Property` by value + pub fn ensure_class_property_type_unlocked_from( + &self, + in_class_schema_property_id: PropertyId, + entity_access_level: EntityAccessLevel, + ) -> Result, &'static str> { + self.ensure_property_values_unlocked()?; + + // Get class-level information about this `Property` + let class_property = self + .properties + .get(in_class_schema_property_id as usize) + // Throw an error if a property was not found on class + // by an in-class index of a property. + .ok_or(ERROR_CLASS_PROP_NOT_FOUND)?; + + class_property.ensure_unlocked_from(entity_access_level)?; + + Ok(class_property.to_owned()) + } + + /// Ensure property values were not locked on `Class` level + pub fn ensure_property_values_unlocked(&self) -> dispatch::Result { + ensure!( + !self + .get_permissions_ref() + .all_entity_property_values_locked(), + ERROR_ALL_PROP_WERE_LOCKED_ON_CLASS_LEVEL + ); + Ok(()) + } +} + +/// Represents `Entity`, related to a specific `Class` +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +pub struct Entity { + /// Permissions for an instance of an Entity. + pub entity_permissions: EntityPermissions, + + /// The class id of this entity. + pub class_id: T::ClassId, + + /// What schemas under which this entity of a class is available, think + /// v.2.0 Person schema for John, v3.0 Person schema for John + /// Unlikely to be more than roughly 20ish, assuming schemas for a given class eventually stableize, or that very old schema are eventually removed. + pub supported_schemas: BTreeSet, // indices of schema in corresponding class + + /// Values for properties on class that are used by some schema used by this entity! + /// Length is no more than Class.properties. + pub values: BTreeMap>, + + /// Number of property values referencing current entity + pub reference_count: ReferenceCounter, + + /// Number of inbound references from another entities with `SameOwner`flag set + pub inbound_same_owner_references_from_other_entities_count: ReferenceCounter, +} + +impl Default for Entity { + fn default() -> Self { + Self { + entity_permissions: EntityPermissions::::default(), + class_id: T::ClassId::default(), + supported_schemas: BTreeSet::new(), + values: BTreeMap::new(), + reference_count: 0, + inbound_same_owner_references_from_other_entities_count: 0, + } + } +} + +impl Entity { + /// Create new `Entity` instance, related to a given `class_id` with provided parameters, + fn new( + controller: EntityController, + class_id: T::ClassId, + supported_schemas: BTreeSet, + values: BTreeMap>, + ) -> Self { + Self { + entity_permissions: EntityPermissions::::default_with_controller(controller), + class_id, + supported_schemas, + values, + reference_count: 0, + inbound_same_owner_references_from_other_entities_count: 0, + } + } + + /// Get `values` by reference + fn get_values_ref(&self) -> &BTreeMap> { + &self.values + } + + /// Get mutable `EntityPermissions` reference, related to given `Entity` + fn get_permissions_mut(&mut self) -> &mut EntityPermissions { + &mut self.entity_permissions + } + + /// Get `EntityPermissions` reference, related to given `Entity` + fn get_permissions_ref(&self) -> &EntityPermissions { + &self.entity_permissions + } + + /// Get `EntityPermissions`, related to given `Entity` by value + fn get_permissions(self) -> EntityPermissions { + self.entity_permissions + } + + /// Update existing `EntityPermissions` with newly provided + pub fn update_permissions(&mut self, permissions: EntityPermissions) { + self.entity_permissions = permissions + } + + /// Ensure `Schema` under given id is not yet added to given `Entity` + pub fn ensure_schema_id_is_not_added(&self, schema_id: SchemaId) -> dispatch::Result { + let schema_not_added = !self.supported_schemas.contains(&schema_id); + ensure!(schema_not_added, ERROR_SCHEMA_ALREADY_ADDED_TO_ENTITY); + Ok(()) + } + + /// Ensure PropertyValue under given `in_class_schema_property_id` is Vector + fn ensure_property_value_is_vec( + &self, + in_class_schema_property_id: PropertyId, + ) -> Result<&VecPropertyValue, &'static str> { + self.values + .get(&in_class_schema_property_id) + // Throw an error if a property was not found on entity + // by an in-class index of a property. + .ok_or(ERROR_UNKNOWN_ENTITY_PROP_ID)? + .as_vec_property_value() + // Ensure prop value under given class schema property id is vector + .ok_or(ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR) + } + + /// Ensure any `PropertyValue` from external entity does not point to the given `Entity` + pub fn ensure_rc_is_zero(&self) -> dispatch::Result { + ensure!( + self.reference_count == 0, + ERROR_ENTITY_RC_DOES_NOT_EQUAL_TO_ZERO + ); + Ok(()) + } + + /// Ensure any inbound `PropertyValue` points to the given `Entity` + pub fn ensure_inbound_same_owner_rc_is_zero(&self) -> dispatch::Result { + ensure!( + self.inbound_same_owner_references_from_other_entities_count == 0, + ERROR_ENTITY_RC_DOES_NOT_EQUAL_TO_ZERO + ); + Ok(()) + } +} + +decl_storage! { + trait Store for Module as ContentDirectory { + + /// Map, representing CuratorGroupId -> CuratorGroup relation + pub CuratorGroupById get(curator_group_by_id): map T::CuratorGroupId => CuratorGroup; + + /// Map, representing ClassId -> Class relation + pub ClassById get(class_by_id) config(): linked_map T::ClassId => Class; + + /// Map, representing EntityId -> Entity relation + pub EntityById get(entity_by_id) config(): map T::EntityId => Entity; + + /// Next runtime storage values used to maintain next id value, used on creation of respective curator groups, classes and entities + + pub NextCuratorGroupId get(next_curator_group_id) config(): T::CuratorGroupId; + + pub NextClassId get(next_class_id) config(): T::ClassId; + + pub NextEntityId get(next_entity_id) config(): T::EntityId; + + // The voucher associated with entity creation for a given class and controller. + // Is updated whenever an entity is created in a given class by a given controller. + // Constraint is updated by Root, an initial value comes from `ClassPermissions::default_entity_creation_voucher_upper_bound`. + pub EntityCreationVouchers get(entity_creation_vouchers): double_map hasher(blake2_128) T::ClassId, blake2_128(EntityController) => EntityCreationVoucher; + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + + // ====== + // Next set of extrinsics can only be invoked by lead. + // ====== + + // Initializing events + fn deposit_event() = default; + + /// Add new curator group to runtime storage + pub fn add_curator_group( + origin, + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + // + // == MUTATION SAFE == + // + + let curator_group_id = Self::next_curator_group_id(); + + // Insert empty curator group with `active` parameter set to false + >::insert(curator_group_id, CuratorGroup::::default()); + + // Increment the next curator curator_group_id: + >::mutate(|n| *n += T::CuratorGroupId::one()); + + // Trigger event + Self::deposit_event(RawEvent::CuratorGroupAdded(curator_group_id)); + Ok(()) + } + + /// Remove curator group under given `curator_group_id` from runtime storage + pub fn remove_curator_group( + origin, + curator_group_id: T::CuratorGroupId, + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + let curator_group = Self::ensure_curator_group_exists(&curator_group_id)?; + + curator_group.ensure_curator_group_maintains_no_classes()?; + + // + // == MUTATION SAFE == + // + + + // Remove curator group under given curator group id from runtime storage + >::remove(curator_group_id); + + // Trigger event + Self::deposit_event(RawEvent::CuratorGroupRemoved(curator_group_id)); + Ok(()) + } + + /// Set `is_active` status for curator group under given `curator_group_id` + pub fn set_curator_group_status( + origin, + curator_group_id: T::CuratorGroupId, + is_active: bool, + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + Self::ensure_curator_group_under_given_id_exists(&curator_group_id)?; + + // + // == MUTATION SAFE == + // + + // Mutate curator group status + >::mutate(curator_group_id, |curator_group| { + curator_group.set_status(is_active) + }); + + // Trigger event + Self::deposit_event(RawEvent::CuratorGroupStatusSet(is_active)); + Ok(()) + } + + /// Add curator to curator group under given `curator_group_id` + pub fn add_curator_to_group( + origin, + curator_group_id: T::CuratorGroupId, + curator_id: T::CuratorId, + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + let curator_group = Self::ensure_curator_group_exists(&curator_group_id)?; + + curator_group.ensure_max_number_of_curators_limit_not_reached()?; + + // + // == MUTATION SAFE == + // + + // Insert curator_id into curator_group under given curator_group_id + >::mutate(curator_group_id, |curator_group| { + curator_group.get_curators_mut().insert(curator_id); + }); + + // Trigger event + Self::deposit_event(RawEvent::CuratorAdded(curator_group_id, curator_id)); + Ok(()) + } + + /// Remove curator from a given curator group + pub fn remove_curator_from_group( + origin, + curator_group_id: T::CuratorGroupId, + curator_id: T::CuratorId, + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + let curator_group = Self::ensure_curator_group_exists(&curator_group_id)?; + + curator_group.ensure_curator_in_group_exists(&curator_id)?; + + // + // == MUTATION SAFE == + // + + // Remove curator_id from curator_group under given curator_group_id + >::mutate(curator_group_id, |curator_group| { + curator_group.get_curators_mut().remove(&curator_id); + }); + + // Trigger event + Self::deposit_event(RawEvent::CuratorRemoved(curator_group_id, curator_id)); + Ok(()) + } + + /// Add curator group under given `curator_group_id` as `Class` maintainer + pub fn add_maintainer_to_class( + origin, + class_id: T::ClassId, + curator_group_id: T::CuratorGroupId, + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + let class = Self::ensure_known_class_id(class_id)?; + + Self::ensure_curator_group_under_given_id_exists(&curator_group_id)?; + + let class_permissions = class.get_permissions_ref(); + + Self::ensure_maintainers_limit_not_reached(class_permissions.get_maintainers())?; + + class_permissions.ensure_maintainer_does_not_exist(&curator_group_id)?; + + // + // == MUTATION SAFE == + // + + // Insert `curator_group_id` into `maintainers` set, associated with given `Class` + >::mutate(class_id, |class| + class.get_permissions_mut().get_maintainers_mut().insert(curator_group_id) + ); + + // Increment the number of classes, curator group under given `curator_group_id` maintains + >::mutate(curator_group_id, |curator_group| { + curator_group.increment_number_of_classes_maintained_count(); + }); + + // Trigger event + Self::deposit_event(RawEvent::MaintainerAdded(class_id, curator_group_id)); + Ok(()) + } + + /// Remove curator group under given `curator_group_id` from `Class` maintainers set + pub fn remove_maintainer_from_class( + origin, + class_id: T::ClassId, + curator_group_id: T::CuratorGroupId, + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + let class = Self::ensure_known_class_id(class_id)?; + + class.get_permissions_ref().ensure_maintainer_exists(&curator_group_id)?; + + // + // == MUTATION SAFE == + // + + // Remove `curator_group_id` from `maintainers` set, associated with given `Class` + >::mutate(class_id, |class| + class.get_permissions_mut().get_maintainers_mut().remove(&curator_group_id) + ); + + // Decrement the number of classes, curator group under given `curator_group_id` maintains + >::mutate(curator_group_id, |curator_group| { + curator_group.decrement_number_of_classes_maintained_count(); + }); + + // Trigger event + Self::deposit_event(RawEvent::MaintainerRemoved(class_id, curator_group_id)); + Ok(()) + } + + /// Updates or creates new `EntityCreationVoucher` for given `EntityController` with individual limit + pub fn update_entity_creation_voucher( + origin, + class_id: T::ClassId, + controller: EntityController, + maximum_entities_count: T::EntityId + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + Self::ensure_known_class_id(class_id)?; + + // Check voucher existance + let voucher_exists = >::exists(class_id, &controller); + + + Self::ensure_valid_number_of_class_entities_per_actor_constraint(maximum_entities_count)?; + + // + // == MUTATION SAFE == + // + + if voucher_exists { + >::mutate(class_id, &controller, |entity_creation_voucher| { + entity_creation_voucher.set_maximum_entities_count(maximum_entities_count); + + // Trigger event + Self::deposit_event(RawEvent::EntityCreationVoucherUpdated(controller.clone(), entity_creation_voucher.to_owned())) + }); + } else { + let entity_creation_voucher = EntityCreationVoucher::new(maximum_entities_count); + + // Add newly created `EntityCreationVoucher` into `EntityCreationVouchers` runtime storage under given `class_id`, `controller` key + >::insert(class_id, controller.clone(), entity_creation_voucher.clone()); + + // Trigger event + Self::deposit_event(RawEvent::EntityCreationVoucherCreated(controller, entity_creation_voucher)); + } + + Ok(()) + } + + /// Create new `Class` with provided parameters + pub fn create_class( + origin, + name: Vec, + description: Vec, + class_permissions: ClassPermissions, + maximum_entities_count: T::EntityId, + default_entity_creation_voucher_upper_bound: T::EntityId + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + Self::ensure_entities_creation_limits_are_valid(maximum_entities_count, default_entity_creation_voucher_upper_bound)?; + + Self::ensure_class_limit_not_reached()?; + + Self::ensure_class_name_is_valid(&name)?; + Self::ensure_class_description_is_valid(&description)?; + Self::ensure_class_permissions_are_valid(&class_permissions)?; + + let class_id = Self::next_class_id(); + + let class = Class::new(class_permissions, name, description, maximum_entities_count, default_entity_creation_voucher_upper_bound); + + // + // == MUTATION SAFE == + // + + // Add new `Class` to runtime storage + >::insert(&class_id, class); + + // Increment the next class id: + >::mutate(|n| *n += T::ClassId::one()); + + // Trigger event + Self::deposit_event(RawEvent::ClassCreated(class_id)); + Ok(()) + } + + /// Update `ClassPermissions` under specific `class_id` + pub fn update_class_permissions( + origin, + class_id: T::ClassId, + updated_any_member: Option, + updated_entity_creation_blocked: Option, + updated_all_entity_property_values_locked: Option, + updated_maintainers: Option>, + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + let class = Self::ensure_known_class_id(class_id)?; + + let mut class_permissions = class.get_permissions(); + + if let Some(ref updated_maintainers) = updated_maintainers { + Self::ensure_curator_groups_exist(updated_maintainers)?; + Self::ensure_maintainers_limit_not_reached(updated_maintainers)?; + } + + // + // == MUTATION SAFE == + // + + // If no update performed, there is no purpose to emit event + let mut updated = false; + + if let Some(updated_any_member) = updated_any_member { + class_permissions.set_any_member_status(updated_any_member); + updated = true; + } + + if let Some(updated_entity_creation_blocked) = updated_entity_creation_blocked { + class_permissions.set_entity_creation_blocked(updated_entity_creation_blocked); + updated = true; + } + + if let Some(updated_all_entity_property_values_locked) = updated_all_entity_property_values_locked { + class_permissions.set_all_entity_property_values_locked(updated_all_entity_property_values_locked); + updated = true; + } + + if let Some(updated_maintainers) = updated_maintainers { + class_permissions.set_maintainers(updated_maintainers); + updated = true; + } + + if updated { + + // Update `class_permissions` under given class id + >::mutate(class_id, |class| { + class.update_permissions(class_permissions) + }); + + // Trigger event + Self::deposit_event(RawEvent::ClassPermissionsUpdated(class_id)); + } + + Ok(()) + } + + /// Create new class schema from existing property ids and new properties + pub fn add_class_schema( + origin, + class_id: T::ClassId, + existing_properties: BTreeSet, + new_properties: Vec> + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + let class = Self::ensure_known_class_id(class_id)?; + + class.ensure_schemas_limit_not_reached()?; + + Self::ensure_non_empty_schema(&existing_properties, &new_properties)?; + + class.ensure_properties_limit_not_reached(&new_properties)?; + + Self::ensure_all_properties_are_valid(&new_properties)?; + + let class_properties = class.get_properties_ref(); + + Self::ensure_all_property_names_are_unique(class_properties, &new_properties)?; + + // Create new Schema with existing properies provided + let mut schema = Schema::new(existing_properties); + + schema.ensure_schema_properties_are_valid_indices(class_properties)?; + + // Represents class properties after new `Schema` added + let mut updated_class_props = class.properties; + + new_properties.into_iter().for_each(|prop| { + + // Add new property ids to `Schema` + let prop_id = updated_class_props.len() as PropertyId; + + schema.get_properties_mut().insert(prop_id); + + // Update existing class properties + updated_class_props.push(prop); + }); + + // + // == MUTATION SAFE == + // + + // Update class properties and schemas + >::mutate(class_id, |class| { + class.properties = updated_class_props; + class.schemas.push(schema); + + let schema_id = class.schemas.len() - 1; + + // Trigger event + Self::deposit_event(RawEvent::ClassSchemaAdded(class_id, schema_id as SchemaId)); + }); + + Ok(()) + } + + /// Update `schema_status` under specific `schema_id` in `Class` + pub fn update_class_schema_status( + origin, + class_id: T::ClassId, + schema_id: SchemaId, + schema_status: bool + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + let class = Self::ensure_known_class_id(class_id)?; + + class.ensure_schema_id_exists(schema_id)?; + + // + // == MUTATION SAFE == + // + + // Update class schema status + >::mutate(class_id, |class| { + class.update_schema_status(schema_id, schema_status) + }); + + // Trigger event + Self::deposit_event(RawEvent::ClassSchemaStatusUpdated(class_id, schema_id, schema_status)); + Ok(()) + } + + /// Update entity permissions. + pub fn update_entity_permissions( + origin, + entity_id: T::EntityId, + updated_frozen_for_controller: Option, + updated_referenceable: Option + ) -> dispatch::Result { + + // Ensure given origin is lead + ensure_is_lead::(origin)?; + + let entity = Self::ensure_known_entity_id(entity_id)?; + + let mut entity_permissions = entity.get_permissions(); + + // + // == MUTATION SAFE == + // + + // If no update performed, there is no purpose to emit event + let mut updated = false; + + if let Some(updated_frozen_for_controller) = updated_frozen_for_controller { + entity_permissions.set_frozen(updated_frozen_for_controller); + updated = true; + } + + if let Some(updated_referenceable) = updated_referenceable { + entity_permissions.set_referencable(updated_referenceable); + updated = true; + } + + if updated { + + // Update entity permissions under given entity id + >::mutate(entity_id, |entity| { + entity.update_permissions(entity_permissions) + }); + + // Trigger event + Self::deposit_event(RawEvent::EntityPermissionsUpdated(entity_id)); + } + Ok(()) + } + + /// Transfer ownership to new `EntityController` for `Entity` under given `entity_id` + /// If `Entity` has `PropertyValue` references with `SameOwner` flag activated, each `Entity` ownership + /// will be transfered to `new_controller` + pub fn transfer_entity_ownership( + origin, + entity_id: T::EntityId, + new_controller: EntityController, + ) -> dispatch::Result { + + ensure_is_lead::(origin)?; + + let (entity, class) = Self::ensure_entity_and_class(entity_id)?; + + entity.ensure_inbound_same_owner_rc_is_zero()?; + + // + // == MUTATION SAFE == + // + + // Set of all entities, which controller should be updated after ownership transfer performed + let mut entities = BTreeSet::new(); + + // Insert root entity_id into entities set + entities.insert(entity_id); + + Self::retrieve_all_entities_to_perform_ownership_transfer(&class, entity, &mut entities); + + // Perform ownership transfer of all involved entities + entities.into_iter().for_each(|involved_entity_id| { + >::mutate(involved_entity_id, |inner_entity| + inner_entity.get_permissions_mut().set_conroller(new_controller.clone()) + ); + }); + + // Trigger event + Self::deposit_event(RawEvent::EntityOwnershipTransfered(entity_id, new_controller)); + + Ok(()) + } + + // ====== + // The next set of extrinsics can be invoked by anyone who can properly sign for provided value of `Actor`. + // ====== + + /// Create an entity. + /// If someone is making an entity of this class for first time, + /// then a voucher is also added with the class limit as the default limit value. + /// class limit default value. + pub fn create_entity( + origin, + class_id: T::ClassId, + actor: Actor, + ) -> dispatch::Result { + + let account_id = ensure_signed(origin)?; + + let class = Self::ensure_class_exists(class_id)?; + + // Ensure maximum entities limit per class not reached + class.ensure_maximum_entities_count_limit_not_reached()?; + + let class_permissions = class.get_permissions_ref(); + + // Ensure actor can create entities + + class_permissions.ensure_entity_creation_not_blocked()?; + class_permissions.ensure_can_create_entities(&account_id, &actor)?; + + let entity_controller = EntityController::from_actor(&actor); + + // Check if entity creation voucher exists + let voucher_exists = if >::exists(class_id, &entity_controller) { + // Ensure voucher limit not reached + Self::entity_creation_vouchers(class_id, &entity_controller).ensure_voucher_limit_not_reached()?; + true + } else { + false + }; + + // + // == MUTATION SAFE == + // + + // Create voucher, update if exists + + if voucher_exists { + // Increment number of created entities count, if voucher already exist + >::mutate(class_id, &entity_controller, |entity_creation_voucher| { + entity_creation_voucher.increment_created_entities_count() + }); + } else { + // Create new voucher for given entity creator with default limit and increment created entities count + let mut entity_creation_voucher = EntityCreationVoucher::new(class.get_default_entity_creation_voucher_upper_bound()); + entity_creation_voucher.increment_created_entities_count(); + >::insert(class_id, entity_controller.clone(), entity_creation_voucher); + } + + // Create new entity + + let entity_id = Self::next_entity_id(); + + let new_entity = Entity::::new( + entity_controller, + class_id, + BTreeSet::new(), + BTreeMap::new(), + ); + + // Save newly created entity: + EntityById::insert(entity_id, new_entity); + + // Increment the next entity id: + >::mutate(|n| *n += T::EntityId::one()); + + >::mutate(class_id, |class| { + class.increment_entities_count(); + }); + + // Trigger event + Self::deposit_event(RawEvent::EntityCreated(actor, entity_id)); + Ok(()) + } + + /// Remove `Entity` under provided `entity_id` + pub fn remove_entity( + origin, + actor: Actor, + entity_id: T::EntityId, + ) -> dispatch::Result { + + let (_, entity, access_level) = Self::ensure_class_entity_and_access_level(origin, entity_id, &actor)?; + + EntityPermissions::::ensure_group_can_remove_entity(access_level)?; + + entity.ensure_rc_is_zero()?; + + // + // == MUTATION SAFE == + // + + // Remove entity + >::remove(entity_id); + + // Decrement class entities counter + >::mutate(entity.class_id, |class| class.decrement_entities_count()); + + let entity_controller = EntityController::::from_actor(&actor); + + // Decrement entity_creation_voucher after entity removal perfomed + >::mutate(entity.class_id, entity_controller, |entity_creation_voucher| { + entity_creation_voucher.decrement_created_entities_count(); + }); + + // Trigger event + Self::deposit_event(RawEvent::EntityRemoved(actor, entity_id)); + Ok(()) + } + + /// Add schema support to entity under given shema_id and provided `property_values` + pub fn add_schema_support_to_entity( + origin, + actor: Actor, + entity_id: T::EntityId, + schema_id: SchemaId, + property_values: BTreeMap> + ) -> dispatch::Result { + + let (class, entity, _) = Self::ensure_class_entity_and_access_level(origin, entity_id, &actor)?; + + entity.ensure_schema_id_is_not_added(schema_id)?; + + let schema = class.ensure_schema_exists(schema_id)?; + + schema.ensure_is_active()?; + + let entity_values = entity.get_values_ref(); + + // Updated entity values, after new schema support added + let mut entity_values_updated = entity.values.clone(); + + // Entities, which rc should be incremented + let mut entity_ids_to_increase_rcs = EntitiesRc::::default(); + + for prop_id in schema.get_properties().iter() { + if entity_values.contains_key(prop_id) { + // A property is already added to the entity and cannot be updated + // while adding a schema support to this entity. + continue; + } + + // Indexing is safe, class shoud always maintain such constistency + let class_property = &class.properties[*prop_id as usize]; + + Self::add_new_property_value( + class_property, &entity, *prop_id, + &property_values, &mut entity_ids_to_increase_rcs, &mut entity_values_updated + )?; + } + + // + // == MUTATION SAFE == + // + + // Add schema support to `Entity` under given `entity_id` + >::mutate(entity_id, |entity| { + + // Add a new schema to the list of schemas supported by this entity. + entity.supported_schemas.insert(schema_id); + + // Update entity values only if new properties have been added. + if entity_values_updated.len() > entity.values.len() { + entity.values = entity_values_updated; + } + }); + + entity_ids_to_increase_rcs.increase_entity_rcs(); + + // Trigger event + Self::deposit_event(RawEvent::EntitySchemaSupportAdded(actor, entity_id, schema_id)); + Ok(()) + } + + /// Update `Entity` `PropertyValue`'s with provided ones + pub fn update_entity_property_values( + origin, + actor: Actor, + entity_id: T::EntityId, + new_property_values: BTreeMap> + ) -> dispatch::Result { + + let (class, entity, access_level) = Self::ensure_class_entity_and_access_level(origin, entity_id, &actor)?; + + class.ensure_property_values_unlocked()?; + + // Get current property values of an entity, + // so we can update them if new values provided present in new_property_values. + let mut updated_values = entity.values.clone(); + let mut updated = false; + + // Entities, which rc should be incremented + let mut entity_ids_to_increase_rcs = EntitiesRc::::default(); + + // Entities, which rc should be decremented + let mut entity_ids_to_decrease_rcs = EntitiesRc::::default(); + + // Iterate over a vector of new values and update corresponding properties + // of this entity if new values are valid. + for (id, new_value) in new_property_values.into_iter() { + + // Try to find a current property value in the entity + // by matching its id to the id of a property with an updated value. + let current_prop_value = updated_values + .get_mut(&id) + + // Throw an error if a property was not found on entity + // by an in-class index of a property update. + .ok_or(ERROR_UNKNOWN_ENTITY_PROP_ID)?; + + // Skip update if new value is equal to the current one or class property type + // is locked for update from current actor + if new_value != *current_prop_value { + + // Get class-level information about this property + if let Some(class_property) = class.properties.get(id as usize) { + + class_property.ensure_unlocked_from(access_level)?; + + class_property.ensure_property_value_to_update_is_valid( + &new_value, + entity.get_permissions_ref().get_controller(), + )?; + + Self::fill_in_involved_entity_ids_rcs( + &new_value, current_prop_value, class_property.property_type.same_controller_status(), + &mut entity_ids_to_increase_rcs, &mut entity_ids_to_decrease_rcs + ); + + current_prop_value.update(new_value); + + updated = true; + } + } + } + + // + // == MUTATION SAFE == + // + + // If property values should be updated + if updated { + + >::mutate(entity_id, |entity| { + entity.values = updated_values; + }); + + entity_ids_to_increase_rcs.increase_entity_rcs(); + + entity_ids_to_decrease_rcs.decrease_entity_rcs(); + + // Trigger event + Self::deposit_event(RawEvent::EntityPropertyValuesUpdated(actor, entity_id)); + } + + Ok(()) + } + + /// Clear `PropertyValueVec` under given `entity_id` & `in_class_schema_property_id` + pub fn clear_entity_property_vector( + origin, + actor: Actor, + entity_id: T::EntityId, + in_class_schema_property_id: PropertyId + ) -> dispatch::Result { + + let (class, entity, access_level) = Self::ensure_class_entity_and_access_level(origin, entity_id, &actor)?; + + let current_property_value_vec = + entity.ensure_property_value_is_vec(in_class_schema_property_id)?; + + let property = class.ensure_class_property_type_unlocked_from( + in_class_schema_property_id, + access_level, + )?; + + let entity_ids_to_decrease_rcs = current_property_value_vec + .get_vec_value() + .get_involved_entities(); + + // + // == MUTATION SAFE == + // + + // Clear property value vector + >::mutate(entity_id, |entity| { + if let Some(PropertyValue::Vector(current_property_value_vec)) = + entity.values.get_mut(&in_class_schema_property_id) + { + current_property_value_vec.vec_clear(); + } + + if let Some(entity_ids_to_decrease_rcs) = entity_ids_to_decrease_rcs { + Self::count_entities(entity_ids_to_decrease_rcs).iter() + .for_each(|(entity_id, rc)| Self::decrease_entity_rcs( + entity_id, *rc, property.property_type.same_controller_status() + ) + ); + } + }); + + // Trigger event + Self::deposit_event(RawEvent::EntityPropertyValueVectorCleared(actor, entity_id, in_class_schema_property_id)); + + Ok(()) + } + + /// Remove value at given `index_in_property_vec` + /// from `PropertyValueVec` under in_class_schema_property_id + pub fn remove_at_entity_property_vector( + origin, + actor: Actor, + entity_id: T::EntityId, + in_class_schema_property_id: PropertyId, + index_in_property_vec: VecMaxLength, + nonce: T::Nonce + ) -> dispatch::Result { + + let (class, entity, access_level) = Self::ensure_class_entity_and_access_level(origin, entity_id, &actor)?; + + let current_property_value_vec = + entity.ensure_property_value_is_vec(in_class_schema_property_id)?; + + let property = class.ensure_class_property_type_unlocked_from( + in_class_schema_property_id, + access_level, + )?; + + current_property_value_vec.ensure_nonce_equality(nonce)?; + + current_property_value_vec + .ensure_index_in_property_vector_is_valid(index_in_property_vec)?; + + // + // == MUTATION SAFE == + // + + let involved_entity_id = current_property_value_vec + .get_vec_value() + .get_involved_entities() + .map(|involved_entities| involved_entities[index_in_property_vec as usize]); + + // Remove value at in_class_schema_property_id in property value vector + >::mutate(entity_id, |entity| { + if let Some(PropertyValue::Vector(current_prop_value)) = + entity.values.get_mut(&in_class_schema_property_id) + { + current_prop_value.vec_remove_at(index_in_property_vec); + + // Trigger event + Self::deposit_event( + RawEvent::RemovedAtEntityPropertyValueVectorIndex( + actor, entity_id, in_class_schema_property_id, index_in_property_vec, nonce + ) + ) + } + }); + + if let Some(involved_entity_id) = involved_entity_id { + Self::decrease_entity_rcs(&involved_entity_id, 1, property.property_type.same_controller_status()); + } + + Ok(()) + } + + /// Insert `SinglePropertyValue` at given `index_in_property_vec` + /// into `PropertyValueVec` under in_class_schema_property_id + pub fn insert_at_entity_property_vector( + origin, + actor: Actor, + entity_id: T::EntityId, + in_class_schema_property_id: PropertyId, + index_in_property_vec: VecMaxLength, + property_value: SinglePropertyValue, + nonce: T::Nonce + ) -> dispatch::Result { + + let (class, entity, access_level) = Self::ensure_class_entity_and_access_level(origin, entity_id, &actor)?; + + let current_property_value_vec = + entity.ensure_property_value_is_vec(in_class_schema_property_id)?; + + let class_property = class.ensure_class_property_type_unlocked_from( + in_class_schema_property_id, + access_level, + )?; + + current_property_value_vec.ensure_nonce_equality(nonce)?; + + class_property.ensure_prop_value_can_be_inserted_at_prop_vec( + &property_value, + current_property_value_vec, + index_in_property_vec, + entity.get_permissions_ref().get_controller(), + )?; + + // + // == MUTATION SAFE == + // + + // Insert SinglePropertyValue at in_class_schema_property_id into property value vector + >::mutate(entity_id, |entity| { + let value = property_value.get_value(); + if let Some(entity_rc_to_increment) = value.get_involved_entity() { + Self::increase_entity_rcs(&entity_rc_to_increment, 1, class_property.property_type.same_controller_status()); + } + if let Some(PropertyValue::Vector(current_prop_value)) = + entity.values.get_mut(&in_class_schema_property_id) + { + current_prop_value.vec_insert_at(index_in_property_vec, value); + + // Trigger event + Self::deposit_event( + RawEvent::InsertedAtEntityPropertyValueVectorIndex( + actor, entity_id, in_class_schema_property_id, index_in_property_vec, nonce + ) + ) + } + }); + + Ok(()) + } + + pub fn transaction(origin, actor: Actor, operations: Vec>) -> dispatch::Result { + Self::ensure_number_of_operations_during_atomic_batching_limit_not_reached(&operations)?; + + // This Vec holds the T::EntityId of the entity created as a result of executing a `CreateEntity` `Operation` + let mut entity_created_in_operation = vec![]; + let raw_origin = origin.into().map_err(|_| ERROR_ORIGIN_CANNOT_BE_MADE_INTO_RAW_ORIGIN)?; + + for operation_type in operations.into_iter() { + let origin = T::Origin::from(raw_origin.clone()); + let actor = actor.clone(); + match operation_type { + OperationType::CreateEntity(create_entity_operation) => { + Self::create_entity(origin, create_entity_operation.class_id, actor)?; + // entity id of newly created entity + let entity_id = Self::next_entity_id() - T::EntityId::one(); + entity_created_in_operation.push(entity_id); + }, + OperationType::UpdatePropertyValues(update_property_values_operation) => { + let entity_id = operations::parametrized_entity_to_entity_id( + &entity_created_in_operation, update_property_values_operation.entity_id + )?; + let property_values = operations::parametrized_property_values_to_property_values( + &entity_created_in_operation, update_property_values_operation.new_parametrized_property_values + )?; + Self::update_entity_property_values(origin, actor, entity_id, property_values)?; + }, + OperationType::AddSchemaSupportToEntity(add_schema_support_to_entity_operation) => { + let entity_id = operations::parametrized_entity_to_entity_id( + &entity_created_in_operation, add_schema_support_to_entity_operation.entity_id + )?; + let schema_id = add_schema_support_to_entity_operation.schema_id; + let property_values = operations::parametrized_property_values_to_property_values( + &entity_created_in_operation, add_schema_support_to_entity_operation.parametrized_property_values + )?; + Self::add_schema_support_to_entity(origin, actor, entity_id, schema_id, property_values)?; + } + } + } + + // Trigger event + Self::deposit_event(RawEvent::TransactionCompleted(actor)); + + Ok(()) + } + } +} + +impl Module { + /// Increases corresponding `Entity` references count by rc. + /// Depends on `same_owner` flag provided + fn increase_entity_rcs(entity_id: &T::EntityId, rc: u32, same_owner: bool) { + >::mutate(entity_id, |entity| { + if same_owner { + entity.inbound_same_owner_references_from_other_entities_count += rc; + entity.reference_count += rc + } else { + entity.reference_count += rc + } + }) + } + + /// Decreases corresponding `Entity` references count by rc. + /// Depends on `same_owner` flag provided + fn decrease_entity_rcs(entity_id: &T::EntityId, rc: u32, same_owner: bool) { + >::mutate(entity_id, |entity| { + if same_owner { + entity.inbound_same_owner_references_from_other_entities_count -= rc; + entity.reference_count -= rc + } else { + entity.reference_count -= rc + } + }) + } + + /// Returns the stored `Class` if exist, error otherwise. + fn ensure_class_exists(class_id: T::ClassId) -> Result, &'static str> { + ensure!(>::exists(class_id), ERROR_CLASS_NOT_FOUND); + Ok(Self::class_by_id(class_id)) + } + + /// Add new `PropertyValue`, if it was not already provided under `PropertyId` of this `Schema` + fn add_new_property_value( + class_property: &Property, + entity: &Entity, + prop_id: PropertyId, + property_values: &BTreeMap>, + entity_ids_to_increase_rcs: &mut EntitiesRc, + entity_values_updated: &mut BTreeMap>, + ) -> Result<(), &'static str> { + if let Some(new_value) = property_values.get(&prop_id) { + class_property.ensure_property_value_to_update_is_valid( + new_value, + entity.get_permissions_ref().get_controller(), + )?; + + class_property.ensure_unique_option_satisfied(new_value, entity_values_updated)?; + + if let Some(entity_rcs_to_increment) = new_value.get_involved_entities() { + entity_ids_to_increase_rcs.fill_in_entity_rcs( + entity_rcs_to_increment, + class_property.property_type.same_controller_status(), + ); + } + + entity_values_updated.insert(prop_id, new_value.to_owned()); + } else { + // All required prop values should be provided + ensure!(!class_property.required, ERROR_MISSING_REQUIRED_PROP); + + // Add all missing non required schema prop values as PropertyValue::default() + entity_values_updated.insert(prop_id, PropertyValue::default()); + } + Ok(()) + } + + /// Fill in `entity_ids_to_increase_rcs` & `entity_ids_to_decrease_rcs`, + /// based on entities involved into update process + pub fn fill_in_involved_entity_ids_rcs( + new_value: &PropertyValue, + current_prop_value: &PropertyValue, + same_controller: bool, + entity_ids_to_increase_rcs: &mut EntitiesRc, + entity_ids_to_decrease_rcs: &mut EntitiesRc, + ) { + // Retrieve unique entity ids to update rc + if let (Some(entities_rc_to_increment_vec), Some(entities_rc_to_decrement_vec)) = ( + new_value.get_involved_entities(), + current_prop_value.get_involved_entities(), + ) { + let (entities_rc_to_decrement_vec, entities_rc_to_increment_vec): ( + Vec, + Vec, + ) = entities_rc_to_decrement_vec + .into_iter() + .zip(entities_rc_to_increment_vec.into_iter()) + // Do not count entity_ids, that should be incremented and decremented simultaneously + .filter(|(entity_rc_to_decrement, entity_rc_to_increment)| { + entity_rc_to_decrement != entity_rc_to_increment + }) + .unzip(); + + entity_ids_to_increase_rcs + .fill_in_entity_rcs(entities_rc_to_increment_vec, same_controller); + + entity_ids_to_decrease_rcs + .fill_in_entity_rcs(entities_rc_to_decrement_vec, same_controller); + } + } + + /// Returns class and entity under given id, if exists, and correspnding `origin` `EntityAccessLevel`, if permitted + fn ensure_class_entity_and_access_level( + origin: T::Origin, + entity_id: T::EntityId, + actor: &Actor, + ) -> Result<(Class, Entity, EntityAccessLevel), &'static str> { + let account_id = ensure_signed(origin)?; + + let entity = Self::ensure_known_entity_id(entity_id)?; + + let class = Self::class_by_id(entity.class_id); + + let access_level = EntityAccessLevel::derive( + &account_id, + entity.get_permissions_ref(), + class.get_permissions_ref(), + actor, + )?; + Ok((class, entity, access_level)) + } + + /// Ensure `Entity` under given `entity_id` exists, retrieve corresponding `Entity` & `Class` + pub fn ensure_entity_and_class( + entity_id: T::EntityId, + ) -> Result<(Entity, Class), &'static str> { + let entity = Self::ensure_known_entity_id(entity_id)?; + + let class = ClassById::get(entity.class_id); + Ok((entity, class)) + } + + /// Retrieve all `entity_id`'s, depending on current `Entity` (the tree of referenced entities with `SameOwner` flag set) + pub fn retrieve_all_entities_to_perform_ownership_transfer( + class: &Class, + entity: Entity, + entities: &mut BTreeSet, + ) { + for (id, value) in entity.values.iter() { + // Check, that property_type of class_property under given index is reference with `SameOwner` flag set + match class.properties.get(*id as usize) { + Some(class_property) if class_property.property_type.same_controller_status() => { + // Always safe + let class_id = class_property + .property_type + .get_referenced_class_id() + .unwrap(); + + // If property class_id is not equal to current one, retrieve corresponding Class from runtime storage + if class_id != entity.class_id { + let new_class = Self::class_by_id(class_id); + + Self::get_all_same_owner_entities(&new_class, value, entities) + } else { + Self::get_all_same_owner_entities(&class, value, entities) + } + } + _ => (), + } + } + } + + /// Get all referenced entities from corresponding property with `SameOwner` flag set, + /// call `retrieve_all_entities_to_perform_ownership_transfer` recursively to complete tree traversal + pub fn get_all_same_owner_entities( + class: &Class, + value: &PropertyValue, + entities: &mut BTreeSet, + ) { + if let Some(entity_ids) = value.get_involved_entities() { + entity_ids.into_iter().for_each(|entity_id| { + // If new entity with `SameOwner` flag set found + if !entities.contains(&entity_id) { + entities.insert(entity_id); + let new_entity = Self::entity_by_id(entity_id); + Self::retrieve_all_entities_to_perform_ownership_transfer( + &class, new_entity, entities, + ); + } + }) + } + } + + /// Ensure `Class` under given id exists, return corresponding one + pub fn ensure_known_class_id(class_id: T::ClassId) -> Result, &'static str> { + ensure!(>::exists(class_id), ERROR_CLASS_NOT_FOUND); + Ok(Self::class_by_id(class_id)) + } + + /// Ensure `Entity` under given id exists, return corresponding one + pub fn ensure_known_entity_id(entity_id: T::EntityId) -> Result, &'static str> { + ensure!(>::exists(entity_id), ERROR_ENTITY_NOT_FOUND); + Ok(Self::entity_by_id(entity_id)) + } + + /// Ensure `CuratorGroup` under given id exists + pub fn ensure_curator_group_under_given_id_exists( + curator_group_id: &T::CuratorGroupId, + ) -> dispatch::Result { + ensure!( + >::exists(curator_group_id), + ERROR_CURATOR_GROUP_DOES_NOT_EXIST + ); + Ok(()) + } + + /// Ensure `CuratorGroup` under given id exists, return corresponding one + pub fn ensure_curator_group_exists( + curator_group_id: &T::CuratorGroupId, + ) -> Result, &'static str> { + Self::ensure_curator_group_under_given_id_exists(curator_group_id)?; + Ok(Self::curator_group_by_id(curator_group_id)) + } + + /// Ensure `MaxNumberOfMaintainersPerClass` constraint satisfied + pub fn ensure_maintainers_limit_not_reached( + curator_groups: &BTreeSet, + ) -> Result<(), &'static str> { + ensure!( + curator_groups.len() < T::MaxNumberOfMaintainersPerClass::get() as usize, + ERROR_NUMBER_OF_MAINTAINERS_PER_CLASS_LIMIT_REACHED + ); + Ok(()) + } + + /// Ensure all curator groups under given id exist + pub fn ensure_curator_groups_exist( + curator_groups: &BTreeSet, + ) -> dispatch::Result { + for curator_group in curator_groups { + Self::ensure_curator_group_exists(curator_group)?; + } + Ok(()) + } + + /// Perform security checks to ensure provided `ClassPermissions` are valid + pub fn ensure_class_permissions_are_valid( + class_permissions: &ClassPermissions, + ) -> dispatch::Result { + Self::ensure_maintainers_limit_not_reached(class_permissions.get_maintainers())?; + Self::ensure_curator_groups_exist(class_permissions.get_maintainers())?; + Ok(()) + } + + /// Ensure new `Schema` is not empty + pub fn ensure_non_empty_schema( + existing_properties: &BTreeSet, + new_properties: &[Property], + ) -> dispatch::Result { + let non_empty_schema = !existing_properties.is_empty() || !new_properties.is_empty(); + ensure!(non_empty_schema, ERROR_NO_PROPS_IN_CLASS_SCHEMA); + Ok(()) + } + + /// Ensure `ClassNameLengthConstraint` conditions satisfied + pub fn ensure_class_name_is_valid(text: &[u8]) -> dispatch::Result { + T::ClassNameLengthConstraint::get().ensure_valid( + text.len(), + ERROR_CLASS_NAME_TOO_SHORT, + ERROR_CLASS_NAME_TOO_LONG, + ) + } + + /// Ensure `ClassDescriptionLengthConstraint` conditions satisfied + pub fn ensure_class_description_is_valid(text: &[u8]) -> dispatch::Result { + T::ClassDescriptionLengthConstraint::get().ensure_valid( + text.len(), + ERROR_CLASS_DESCRIPTION_TOO_SHORT, + ERROR_CLASS_DESCRIPTION_TOO_LONG, + ) + } + + /// Ensure `MaxNumberOfClasses` constraint satisfied + pub fn ensure_class_limit_not_reached() -> dispatch::Result { + ensure!( + T::MaxNumberOfClasses::get() < >::enumerate().count() as MaxNumber, + ERROR_CLASS_LIMIT_REACHED + ); + Ok(()) + } + + /// Ensure `MaxNumberOfEntitiesPerClass` constraint satisfied + pub fn ensure_valid_number_of_entities_per_class( + maximum_entities_count: T::EntityId, + ) -> dispatch::Result { + ensure!( + maximum_entities_count < T::MaxNumberOfEntitiesPerClass::get(), + ERROR_ENTITIES_NUMBER_PER_CLASS_CONSTRAINT_VIOLATED + ); + Ok(()) + } + + /// Ensure `IndividualEntitiesCreationLimit` constraint satisfied + pub fn ensure_valid_number_of_class_entities_per_actor_constraint( + number_of_class_entities_per_actor: T::EntityId, + ) -> dispatch::Result { + ensure!( + number_of_class_entities_per_actor < T::IndividualEntitiesCreationLimit::get(), + ERROR_NUMBER_OF_CLASS_ENTITIES_PER_ACTOR_CONSTRAINT_VIOLATED + ); + Ok(()) + } + + /// Ensure, that all entities creation limits, defined for a given `Class`, are valid + pub fn ensure_entities_creation_limits_are_valid( + maximum_entities_count: T::EntityId, + default_entity_creation_voucher_upper_bound: T::EntityId, + ) -> dispatch::Result { + // Ensure `per_controller_entities_creation_limit` does not exceed + ensure!( + default_entity_creation_voucher_upper_bound < maximum_entities_count, + ERROR_PER_CONTROLLER_ENTITIES_CREATION_LIMIT_EXCEEDS_OVERALL_LIMIT + ); + Self::ensure_valid_number_of_entities_per_class(maximum_entities_count)?; + Self::ensure_valid_number_of_class_entities_per_actor_constraint( + default_entity_creation_voucher_upper_bound, + ) + } + + /// Ensure maximum number of operations during atomic batching constraint satisfied + pub fn ensure_number_of_operations_during_atomic_batching_limit_not_reached( + operations: &[OperationType], + ) -> dispatch::Result { + ensure!( + operations.len() <= T::MaxNumberOfOperationsDuringAtomicBatching::get() as usize, + ERROR_MAX_NUMBER_OF_OPERATIONS_DURING_ATOMIC_BATCHING_LIMIT_REACHED + ); + Ok(()) + } + + /// Complete all checks to ensure each `Property` is valid + pub fn ensure_all_properties_are_valid(new_properties: &[Property]) -> dispatch::Result { + for new_property in new_properties.iter() { + new_property.ensure_name_is_valid()?; + new_property.ensure_description_is_valid()?; + new_property.ensure_property_type_size_is_valid()?; + new_property.ensure_property_type_reference_is_valid()?; + } + Ok(()) + } + + /// Ensure all `Property` names are unique within `Class` + pub fn ensure_all_property_names_are_unique( + class_properties: &[Property], + new_properties: &[Property], + ) -> dispatch::Result { + // Used to ensure all property names are unique within class + let mut unique_prop_names = BTreeSet::new(); + + for property in class_properties.iter() { + unique_prop_names.insert(property.name.to_owned()); + } + + for new_property in new_properties { + // Ensure name of a new property is unique within its class. + ensure!( + !unique_prop_names.contains(&new_property.name), + ERROR_PROP_NAME_NOT_UNIQUE_IN_A_CLASS + ); + + unique_prop_names.insert(new_property.name.to_owned()); + } + + Ok(()) + } + + /// Counts the number of repetetive entities and returns `BTreeMap`, + /// where T::EntityId - unique entity_id, ReferenceCounter - related counter + pub fn count_entities(entity_ids: Vec) -> BTreeMap { + let mut result = BTreeMap::new(); + + for entity_id in entity_ids { + *result.entry(entity_id).or_insert(0) += 1; + } + + result + } +} + +decl_event!( + pub enum Event + where + CuratorGroupId = ::CuratorGroupId, + CuratorId = ::CuratorId, + ClassId = ::ClassId, + EntityId = ::EntityId, + EntityController = EntityController, + EntityCreationVoucher = EntityCreationVoucher, + Status = bool, + Actor = Actor, + Nonce = ::Nonce, + { + CuratorGroupAdded(CuratorGroupId), + CuratorGroupRemoved(CuratorGroupId), + CuratorGroupStatusSet(Status), + CuratorAdded(CuratorGroupId, CuratorId), + CuratorRemoved(CuratorGroupId, CuratorId), + MaintainerAdded(ClassId, CuratorGroupId), + MaintainerRemoved(ClassId, CuratorGroupId), + EntityCreationVoucherUpdated(EntityController, EntityCreationVoucher), + EntityCreationVoucherCreated(EntityController, EntityCreationVoucher), + ClassCreated(ClassId), + ClassPermissionsUpdated(ClassId), + ClassSchemaAdded(ClassId, SchemaId), + ClassSchemaStatusUpdated(ClassId, SchemaId, Status), + EntityPermissionsUpdated(EntityId), + EntityCreated(Actor, EntityId), + EntityRemoved(Actor, EntityId), + EntitySchemaSupportAdded(Actor, EntityId, SchemaId), + EntityPropertyValuesUpdated(Actor, EntityId), + EntityPropertyValueVectorCleared(Actor, EntityId, PropertyId), + RemovedAtEntityPropertyValueVectorIndex(Actor, EntityId, PropertyId, VecMaxLength, Nonce), + InsertedAtEntityPropertyValueVectorIndex(Actor, EntityId, PropertyId, VecMaxLength, Nonce), + TransactionCompleted(Actor), + EntityOwnershipTransfered(EntityId, EntityController), + } +); diff --git a/runtime-modules/content-directory/src/mock.rs b/runtime-modules/content-directory/src/mock.rs new file mode 100644 index 0000000000..603354494b --- /dev/null +++ b/runtime-modules/content-directory/src/mock.rs @@ -0,0 +1,466 @@ +// #![cfg(test)] + +// use crate::*; + +// use crate::InputValidationLengthConstraint; +// use primitives::H256; +// use runtime_primitives::{ +// testing::Header, +// traits::{BlakeTwo256, IdentityLookup}, +// Perbill, +// }; +// use srml_support::{assert_err, assert_ok, impl_outer_origin, parameter_types}; +// use std::cell::RefCell; + +// pub const MEMBER_ONE_WITH_CREDENTIAL_ZERO: u64 = 100; +// pub const MEMBER_TWO_WITH_CREDENTIAL_ZERO: u64 = 101; +// pub const MEMBER_ONE_WITH_CREDENTIAL_ONE: u64 = 102; +// pub const MEMBER_TWO_WITH_CREDENTIAL_ONE: u64 = 103; + +// pub const UNKNOWN_CLASS_ID: ::ClassId = 111; +// pub const UNKNOWN_ENTITY_ID: ::EntityId = 222; +// pub const UNKNOWN_PROP_ID: PropertyId = 333; +// pub const UNKNOWN_SCHEMA_ID: SchemaId = 444; + +// pub const SCHEMA_ID_0: SchemaId = 0; +// pub const SCHEMA_ID_1: SchemaId = 1; + +// pub const ZERO_NONCE: ::Nonce = 0; +// pub const FIRST_NONCE: ::Nonce = 1; +// pub const SECOND_NONCE: ::Nonce = 2; + +// pub const VALID_PROPERTY_VEC_INDEX: VecMaxLength = 0; +// pub const INVALID_PROPERTY_VEC_INDEX: VecMaxLength = 5; + +// pub const PROP_ID_BOOL: PropertyId = 0; +// pub const PROP_ID_REFERENCE_VEC: PropertyId = 1; +// pub const PROP_ID_U32: PropertyId = 1; +// pub const PROP_ID_REFERENCE: PropertyId = 2; +// pub const PROP_ID_U32_VEC: PropertyId = 3; +// pub const PROP_ID_U32_VEC_MAX_LEN: PropertyId = 20; + +// pub const PRINCIPAL_GROUP_MEMBERS: [[u64; 2]; 2] = [ +// [ +// MEMBER_ONE_WITH_CREDENTIAL_ZERO, +// MEMBER_TWO_WITH_CREDENTIAL_ZERO, +// ], +// [ +// MEMBER_ONE_WITH_CREDENTIAL_ONE, +// MEMBER_TWO_WITH_CREDENTIAL_ONE, +// ], +// ]; + +// pub const CLASS_PERMISSIONS_CREATOR1: u64 = 200; +// pub const CLASS_PERMISSIONS_CREATOR2: u64 = 300; +// pub const UNAUTHORIZED_CLASS_PERMISSIONS_CREATOR: u64 = 50; + +// const CLASS_PERMISSIONS_CREATORS: [u64; 2] = +// [CLASS_PERMISSIONS_CREATOR1, CLASS_PERMISSIONS_CREATOR2]; + +// impl_outer_origin! { +// pub enum Origin for Runtime {} +// } + +// // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. +// #[derive(Clone, Default, PartialEq, Eq, Debug)] +// pub struct Runtime; +// parameter_types! { +// pub const BlockHashCount: u64 = 250; +// pub const MaximumBlockWeight: u32 = 1024; +// pub const MaximumBlockLength: u32 = 2 * 1024; +// pub const AvailableBlockRatio: Perbill = Perbill::one(); +// pub const MinimumPeriod: u64 = 5; +// } + +// impl system::Trait for Runtime { +// type Origin = Origin; +// type Index = u64; +// type BlockNumber = u64; +// type Call = (); +// type Hash = H256; +// type Hashing = BlakeTwo256; +// type AccountId = u64; +// type Lookup = IdentityLookup; +// type Header = Header; +// type Event = (); +// type BlockHashCount = BlockHashCount; +// type MaximumBlockWeight = MaximumBlockWeight; +// type MaximumBlockLength = MaximumBlockLength; +// type AvailableBlockRatio = AvailableBlockRatio; +// type Version = (); +// } + +// impl timestamp::Trait for Runtime { +// type Moment = u64; +// type OnTimestampSet = (); +// type MinimumPeriod = MinimumPeriod; +// } + +// thread_local! { +// static PROPERTY_NAME_CONSTRAINT: RefCell = RefCell::new(InputValidationLengthConstraint::default()); +// static PROPERTY_DESCRIPTION_CONSTRAINT: RefCell = RefCell::new(InputValidationLengthConstraint::default()); +// static CLASS_NAME_CONSTRAINT: RefCell = RefCell::new(InputValidationLengthConstraint::default()); +// static CLASS_DESCRIPTION_CONSTRAINT: RefCell = RefCell::new(InputValidationLengthConstraint::default()); +// } + +// pub struct PropertyNameConstraint; +// impl Get for PropertyNameConstraint { +// fn get() -> InputValidationLengthConstraint { +// PROPERTY_NAME_CONSTRAINT.with(|v| *v.borrow()) +// } +// } + +// pub struct PropertyDescriptionConstraint; +// impl Get for PropertyDescriptionConstraint { +// fn get() -> InputValidationLengthConstraint { +// PROPERTY_DESCRIPTION_CONSTRAINT.with(|v| *v.borrow()) +// } +// } + +// pub struct ClassNameConstraint; +// impl Get for ClassNameConstraint { +// fn get() -> InputValidationLengthConstraint { +// CLASS_NAME_CONSTRAINT.with(|v| *v.borrow()) +// } +// } + +// pub struct ClassDescriptionConstraint; +// impl Get for ClassDescriptionConstraint { +// fn get() -> InputValidationLengthConstraint { +// CLASS_DESCRIPTION_CONSTRAINT.with(|v| *v.borrow()) +// } +// } + +// impl Trait for Runtime { +// type Credential = u64; +// type Nonce = u64; +// type ClassId = u64; +// type EntityId = u64; +// type PropertyNameConstraint = PropertyNameConstraint; +// type PropertyDescriptionConstraint = PropertyDescriptionConstraint; +// type ClassNameConstraint = ClassNameConstraint; +// type ClassDescriptionConstraint = ClassDescriptionConstraint; +// } + +// impl ActorAuthenticator for Runtime { +// type ActorId = u64; +// type GroupId = u64; + +// fn authenticate_authority(account_id: &Self::AccountId) -> bool { +// true +// } + +// fn authenticate_actor_in_group( +// account_id: &Self::AccountId, +// group_id: Self::GroupId, +// actor_id: Self::ActorId, +// ) -> bool { +// true +// } +// } + +// pub struct ExtBuilder { +// property_name_constraint: InputValidationLengthConstraint, +// property_description_constraint: InputValidationLengthConstraint, +// class_name_constraint: InputValidationLengthConstraint, +// class_description_constraint: InputValidationLengthConstraint, +// } + +// impl Default for ExtBuilder { +// fn default() -> Self { +// Self { +// property_name_constraint: InputValidationLengthConstraint::new(1, 49), +// property_description_constraint: InputValidationLengthConstraint::new(0, 500), +// class_name_constraint: InputValidationLengthConstraint::new(1, 49), +// class_description_constraint: InputValidationLengthConstraint::new(0, 500), +// } +// } +// } + +// impl ExtBuilder { +// pub fn post_title_max_length( +// mut self, +// property_name_constraint: InputValidationLengthConstraint, +// ) -> Self { +// self.property_name_constraint = property_name_constraint; +// self +// } + +// pub fn post_body_max_length( +// mut self, +// property_description_constraint: InputValidationLengthConstraint, +// ) -> Self { +// self.property_description_constraint = property_description_constraint; +// self +// } + +// pub fn reply_max_length( +// mut self, +// class_name_constraint: InputValidationLengthConstraint, +// ) -> Self { +// self.class_name_constraint = class_name_constraint; +// self +// } + +// pub fn posts_max_number( +// mut self, +// class_description_constraint: InputValidationLengthConstraint, +// ) -> Self { +// self.class_description_constraint = class_description_constraint; +// self +// } + +// pub fn set_associated_consts(&self) { +// PROPERTY_NAME_CONSTRAINT.with(|v| *v.borrow_mut() = self.property_name_constraint); +// PROPERTY_DESCRIPTION_CONSTRAINT +// .with(|v| *v.borrow_mut() = self.property_description_constraint); +// CLASS_NAME_CONSTRAINT.with(|v| *v.borrow_mut() = self.class_name_constraint); +// CLASS_DESCRIPTION_CONSTRAINT.with(|v| *v.borrow_mut() = self.class_description_constraint); +// } + +// pub fn build(self, config: GenesisConfig) -> runtime_io::TestExternalities { +// self.set_associated_consts(); +// let mut t = system::GenesisConfig::default() +// .build_storage::() +// .unwrap(); +// config.assimilate_storage(&mut t).unwrap(); +// t.into() +// } +// } + +// // This function basically just builds a genesis storage key/value store according to +// // our desired mockup. + +// fn default_content_directory_genesis_config() -> GenesisConfig { +// GenesisConfig { +// class_by_id: vec![], +// entity_by_id: vec![], +// next_class_id: 1, +// next_entity_id: 1, +// } +// } + +// pub fn with_test_externalities R>(f: F) -> R { +// let default_genesis_config = default_content_directory_genesis_config(); +// ExtBuilder::default() +// .build(default_genesis_config) +// .execute_with(f) +// } + +// impl Property { +// pub fn required(mut self) -> Self { +// self.required = true; +// self +// } +// } + +// pub fn assert_class_props( +// class_id: ::ClassId, +// expected_props: Vec>, +// ) { +// let class = TestModule::class_by_id(class_id); +// assert_eq!(class.properties, expected_props); +// } + +// pub fn assert_class_schemas( +// class_id: ::ClassId, +// expected_schema_prop_ids: Vec>, +// ) { +// let class = TestModule::class_by_id(class_id); +// let schemas: Vec<_> = expected_schema_prop_ids +// .iter() +// .map(|prop_ids| Schema::new(prop_ids.to_owned())) +// .collect(); +// assert_eq!(class.schemas, schemas); +// } + +// pub fn assert_entity_not_found(result: dispatch::Result) { +// assert_err!(result, ERROR_ENTITY_NOT_FOUND); +// } + +// pub fn simple_test_schema() -> Vec> { +// vec![Property { +// property_type: PropertyType::Int64(PropertyLockingPolicy::default()), +// required: false, +// name: b"field1".to_vec(), +// description: b"Description field1".to_vec(), +// }] +// } + +// pub fn simple_test_entity_property_values() -> BTreeMap> { +// let mut property_values = BTreeMap::new(); +// property_values.insert(0, PropertyValue::Int64(1337)); +// property_values +// } + +// pub fn create_simple_class(permissions: ClassPermissions) -> ::ClassId { +// let class_id = TestModule::next_class_id(); +// assert_ok!(TestModule::create_class( +// Origin::signed(CLASS_PERMISSIONS_CREATOR1), +// b"class_name_1".to_vec(), +// b"class_description_1".to_vec(), +// permissions +// )); +// class_id +// } + +// pub fn create_simple_class_with_default_permissions() -> ::ClassId { +// create_simple_class(Default::default()) +// } + +// pub fn class_minimal() -> ClassPermissions { +// ClassPermissions::default() +// } + +// // pub fn class_minimal_with_admins(admins: Vec<::Credential>) -> ClassPermissions { +// // ClassPermissions { ..class_minimal() } +// // } + +// pub fn next_entity_id() -> ::EntityId { +// TestModule::next_entity_id() +// } + +// // pub fn create_entity_of_class( +// // class_id: ::ClassId, +// // ) -> ::EntityId { +// // let entity_id = TestModule::next_entity_id(); +// // assert_eq!(TestModule::perform_entity_creation(class_id,), entity_id); +// // entity_id +// // } + +// // pub fn create_entity_with_schema_support() -> ::EntityId { +// // let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); +// // let mut property_values = BTreeMap::new(); +// // property_values.insert(PROP_ID_BOOL, PropertyValue::Bool(true)); +// // property_values.insert( +// // PROP_ID_U32_VEC, +// // PropertyValue::Uint32Vec(vec![123, 234, 44], ::Nonce::default()), +// // ); +// // assert_ok!(TestModule::add_entity_schema_support( +// // entity_id, +// // schema_id, +// // property_values +// // )); +// // entity_id +// // } + +// // pub fn create_class_with_schema() -> (::ClassId, SchemaId) { +// // let class_id = create_simple_class_with_default_permissions(); +// // let schema_id = TestModule::append_class_schema( +// // class_id, +// // vec![], +// // vec![ +// // good_prop_bool().required(), +// // good_prop_u32(), +// // new_reference_class_prop(class_id), +// // good_prop_u32_vec(), +// // ], +// // ) +// // .expect("This should not happen"); +// // (class_id, schema_id) +// // } + +// // pub fn create_class_with_schema_and_entity() -> ( +// // ::ClassId, +// // SchemaId, +// // ::EntityId, +// // ) { +// // let (class_id, schema_id) = create_class_with_schema(); +// // let entity_id = create_entity_of_class(class_id); +// // (class_id, schema_id, entity_id) +// // } + +// pub fn good_prop_bool() -> Property { +// Property { +// property_type: PropertyType::Bool(PropertyLockingPolicy::default()), +// required: false, +// name: b"Name of a bool property".to_vec(), +// description: b"Description of a bool property".to_vec(), +// } +// } + +// pub fn good_prop_u32() -> Property { +// Property { +// property_type: PropertyType::Uint32(PropertyLockingPolicy::default()), +// required: false, +// name: b"Name of a u32 property".to_vec(), +// description: b"Description of a u32 property".to_vec(), +// } +// } + +// pub fn good_prop_u32_vec() -> Property { +// Property { +// property_type: PropertyType::Uint32Vec(PROP_ID_U32_VEC_MAX_LEN, PropertyLockingPolicy::default()), +// required: false, +// name: b"Name of a u32 vec property".to_vec(), +// description: b"Description of a u32 vec property".to_vec(), +// } +// } + +// pub fn good_prop_text() -> Property { +// Property { +// property_type: PropertyType::Text(20, PropertyLockingPolicy::default()), +// required: false, +// name: b"Name of a text property".to_vec(), +// description: b"Description of a text property".to_vec(), +// } +// } + +// pub fn new_reference_class_prop(class_id: ::ClassId) -> Property { +// Property { +// property_type: PropertyType::Reference(class_id, PropertyLockingPolicy::default(), false), +// required: false, +// name: b"Name of a internal property".to_vec(), +// description: b"Description of a internal property".to_vec(), +// } +// } + +// pub fn new_reference_class_prop_vec(class_id: ::ClassId) -> Property { +// Property { +// property_type: PropertyType::ReferenceVec( +// PROP_ID_U32_VEC_MAX_LEN, +// class_id, +// PropertyLockingPolicy::default(), +// false, +// ), +// required: false, +// name: b"Name of a internal property".to_vec(), +// description: b"Description of a internal property".to_vec(), +// } +// } + +// pub fn good_class_name() -> Vec { +// b"Name of a class".to_vec() +// } + +// pub fn good_class_description() -> Vec { +// b"Description of a class".to_vec() +// } + +// pub fn good_props() -> Vec> { +// vec![good_prop_bool(), good_prop_u32()] +// } + +// pub fn good_prop_ids() -> Vec { +// vec![0, 1] +// } + +// pub fn bool_prop_value() -> BTreeMap> { +// let mut property_values = BTreeMap::new(); +// property_values.insert(0, PropertyValue::Bool(true)); +// property_values +// } + +// pub fn prop_value( +// index: PropertyId, +// value: PropertyValue, +// ) -> BTreeMap> { +// let mut property_values = BTreeMap::new(); +// property_values.insert(index, value); +// property_values +// } + +// // pub type System = system::Module; + +// /// Export module on a test runtime +// pub type TestModule = Module; diff --git a/runtime-modules/content-directory/src/operations.rs b/runtime-modules/content-directory/src/operations.rs new file mode 100644 index 0000000000..b43b44d197 --- /dev/null +++ b/runtime-modules/content-directory/src/operations.rs @@ -0,0 +1,126 @@ +use crate::{ + PropertyId, PropertyValue, SchemaId, SinglePropertyValue, Trait, Value, VecPropertyValue, + VecValue, +}; +use codec::{Decode, Encode}; +use rstd::collections::btree_map::BTreeMap; +use rstd::prelude::*; + +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub enum ParametrizedPropertyValue { + /// Same fields as normal PropertyValue + PropertyValue(PropertyValue), + + /// This is the index of an operation creating an entity in the transaction/batch operations + InternalEntityJustAdded(u32), // should really be usize but it doesn't have Encode/Decode support + + /// Vector of mix of Entities already existing and just added in a recent operation + InternalEntityVec(Vec>), +} + +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub enum ParameterizedEntity { + InternalEntityJustAdded(u32), + ExistingEntity(T::EntityId), +} + +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub struct ParametrizedClassPropertyValue { + /// Index is into properties vector of class. + pub in_class_index: PropertyId, + + /// Value of property with index `in_class_index` in a given class. + pub value: ParametrizedPropertyValue, +} + +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub struct CreateEntityOperation { + pub class_id: T::ClassId, +} + +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub struct UpdatePropertyValuesOperation { + pub entity_id: ParameterizedEntity, + pub new_parametrized_property_values: Vec>, +} + +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub struct AddSchemaSupportToEntityOperation { + pub entity_id: ParameterizedEntity, + pub schema_id: SchemaId, + pub parametrized_property_values: Vec>, +} + +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub enum OperationType { + CreateEntity(CreateEntityOperation), + UpdatePropertyValues(UpdatePropertyValuesOperation), + AddSchemaSupportToEntity(AddSchemaSupportToEntityOperation), +} + +pub fn parametrized_entity_to_entity_id( + created_entities: &[T::EntityId], + entity: ParameterizedEntity, +) -> Result { + match entity { + ParameterizedEntity::ExistingEntity(entity_id) => Ok(entity_id), + ParameterizedEntity::InternalEntityJustAdded(op_index_u32) => { + let op_index = op_index_u32 as usize; + Ok(*created_entities + .get(op_index) + .ok_or("EntityNotCreatedByOperation")?) + } + } +} + +pub fn parametrized_property_values_to_property_values( + created_entities: &[T::EntityId], + parametrized_property_values: Vec>, +) -> Result>, &'static str> { + let mut class_property_values = BTreeMap::new(); + + for parametrized_class_property_value in parametrized_property_values.into_iter() { + let property_value = match parametrized_class_property_value.value { + ParametrizedPropertyValue::PropertyValue(value) => value, + ParametrizedPropertyValue::InternalEntityJustAdded( + entity_created_in_operation_index, + ) => { + // Verify that referenced entity was indeed created created + let op_index = entity_created_in_operation_index as usize; + let entity_id = created_entities + .get(op_index) + .ok_or("EntityNotCreatedByOperation")?; + PropertyValue::Single(SinglePropertyValue::new(Value::Reference(*entity_id))) + } + ParametrizedPropertyValue::InternalEntityVec(parametrized_entities) => { + let mut entities: Vec = vec![]; + + for parametrized_entity in parametrized_entities.into_iter() { + match parametrized_entity { + ParameterizedEntity::ExistingEntity(id) => entities.push(id), + ParameterizedEntity::InternalEntityJustAdded( + entity_created_in_operation_index, + ) => { + let op_index = entity_created_in_operation_index as usize; + let entity_id = created_entities + .get(op_index) + .ok_or("EntityNotCreatedByOperation")?; + entities.push(*entity_id); + } + } + } + PropertyValue::Vector(VecPropertyValue::new( + VecValue::Reference(entities), + T::Nonce::default(), + )) + } + }; + + class_property_values.insert( + parametrized_class_property_value.in_class_index, + property_value, + ); + } + + Ok(class_property_values) +} diff --git a/runtime-modules/content-directory/src/permissions.rs b/runtime-modules/content-directory/src/permissions.rs new file mode 100644 index 0000000000..e80a6339b6 --- /dev/null +++ b/runtime-modules/content-directory/src/permissions.rs @@ -0,0 +1,569 @@ +use crate::errors::*; +use crate::*; +use codec::{Codec, Decode, Encode}; +use core::fmt::Debug; +use runtime_primitives::traits::{MaybeSerializeDeserialize, Member, SimpleArithmetic}; + +#[cfg(feature = "std")] +pub use serde::{Deserialize, Serialize}; +use srml_support::{dispatch, ensure, Parameter}; + +/// Model of authentication manager. +pub trait ActorAuthenticator: system::Trait + Debug { + /// Curator identifier + type CuratorId: Parameter + + Member + + SimpleArithmetic + + Codec + + Default + + Copy + + Clone + + MaybeSerializeDeserialize + + Eq + + PartialEq + + Ord; + + /// Member identifier + type MemberId: Parameter + + Member + + SimpleArithmetic + + Codec + + Default + + Copy + + Clone + + MaybeSerializeDeserialize + + Eq + + PartialEq + + Ord; + + /// Curator group identifier + type CuratorGroupId: Parameter + + Member + + SimpleArithmetic + + Codec + + One + + Default + + Copy + + Clone + + MaybeSerializeDeserialize + + Eq + + PartialEq + + Ord; + + /// Authorize actor as lead + fn is_lead(account_id: &Self::AccountId) -> bool; + + /// Authorize actor as curator + fn is_curator(curator_id: &Self::CuratorId, account_id: &Self::AccountId) -> bool; + + /// Authorize actor as member + fn is_member(member_id: &Self::MemberId, account_id: &Self::AccountId) -> bool; +} + +pub fn ensure_curator_auth_success( + curator_id: &T::CuratorId, + account_id: &T::AccountId, +) -> dispatch::Result { + ensure!( + T::is_curator(curator_id, account_id), + ERROR_CURATOR_AUTH_FAILED + ); + Ok(()) +} + +pub fn ensure_member_auth_success( + member_id: &T::MemberId, + account_id: &T::AccountId, +) -> dispatch::Result { + ensure!( + T::is_member(member_id, account_id), + ERROR_MEMBER_AUTH_FAILED + ); + Ok(()) +} + +pub fn ensure_lead_auth_success( + account_id: &T::AccountId, +) -> dispatch::Result { + ensure!(T::is_lead(account_id), ERROR_LEAD_AUTH_FAILED); + Ok(()) +} + +/// Ensure given `Origin` is lead +pub fn ensure_is_lead(origin: T::Origin) -> dispatch::Result { + let account_id = ensure_signed(origin)?; + ensure_lead_auth_success::(&account_id) +} + +/// Authorize curator, performing all checks to ensure curator can act +pub fn perform_curator_in_group_auth( + curator_id: &T::CuratorId, + curator_group_id: &T::CuratorGroupId, + account_id: &T::AccountId, +) -> dispatch::Result { + ensure_curator_auth_success::(curator_id, account_id)?; + + let curator_group = Module::::ensure_curator_group_exists(curator_group_id)?; + + ensure!(curator_group.is_active(), ERROR_CURATOR_GROUP_IS_NOT_ACTIVE); + CuratorGroup::::ensure_curator_in_group_exists(&curator_group, curator_id)?; + Ok(()) +} + +/// A group, that consists of `curators` set +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub struct CuratorGroup { + /// Curators set, associated with a iven curator group + curators: BTreeSet, + + /// When `false`, curator in a given group is forbidden to act + active: bool, + + /// Used to count the number of `Class`(es), given curator group maintains + number_of_classes_maintained: ReferenceCounter, +} + +impl Default for CuratorGroup { + fn default() -> Self { + Self { + curators: BTreeSet::new(), + active: false, + number_of_classes_maintained: 0, + } + } +} + +impl CuratorGroup { + /// Check if `CuratorGroup` contains curator under given `curator_id` + pub fn is_curator(&self, curator_id: &T::CuratorId) -> bool { + self.curators.contains(curator_id) + } + + /// Check if `CuratorGroup` is active + pub fn is_active(&self) -> bool { + self.active + } + + /// Set `CuratorGroup` status as provided + pub fn set_status(&mut self, is_active: bool) { + self.active = is_active + } + + /// Retrieve set of all curator_ids related to `CuratorGroup` by reference + pub fn get_curators(&self) -> &BTreeSet { + &self.curators + } + + /// Retrieve set of all curator_ids related to `CuratorGroup` by mutable reference + pub fn get_curators_mut(&mut self) -> &mut BTreeSet { + &mut self.curators + } + + /// Increment number of classes `CuratorGroup` maintains + pub fn increment_number_of_classes_maintained_count(&mut self) { + self.number_of_classes_maintained += 1; + } + + /// Decrement number of classes `CuratorGroup` maintains + pub fn decrement_number_of_classes_maintained_count(&mut self) { + self.number_of_classes_maintained -= 1; + } + + /// Ensure curator group does not maintain any class + pub fn ensure_curator_group_maintains_no_classes(&self) -> dispatch::Result { + ensure!( + self.number_of_classes_maintained == 0, + ERROR_CURATOR_GROUP_REMOVAL_FORBIDDEN + ); + Ok(()) + } + + /// Ensure `MaxNumberOfCuratorsPerGroup` constraint satisfied + pub fn ensure_max_number_of_curators_limit_not_reached(&self) -> dispatch::Result { + ensure!( + self.curators.len() < T::MaxNumberOfCuratorsPerGroup::get() as usize, + ERROR_NUMBER_OF_CURATORS_PER_GROUP_LIMIT_REACHED + ); + Ok(()) + } + + /// Ensure curator under given `curator_id` exists in `CuratorGroup` + pub fn ensure_curator_in_group_exists(&self, curator_id: &T::CuratorId) -> dispatch::Result { + ensure!( + self.get_curators().contains(curator_id), + ERROR_CURATOR_IS_NOT_A_MEMBER_OF_A_GIVEN_CURATOR_GROUP + ); + Ok(()) + } +} + +/// A voucher for `Entity` creation +#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq)] +pub struct EntityCreationVoucher { + /// How many are allowed in total + pub maximum_entities_count: T::EntityId, + + /// How many have currently been created + pub entities_created: T::EntityId, +} + +impl Default for EntityCreationVoucher { + fn default() -> Self { + Self { + maximum_entities_count: T::EntityId::zero(), + entities_created: T::EntityId::zero(), + } + } +} + +impl EntityCreationVoucher { + /// Create a new instance of `EntityCreationVoucher` with specified limit + pub fn new(maximum_entities_count: T::EntityId) -> Self { + Self { + maximum_entities_count, + entities_created: T::EntityId::zero(), + } + } + + /// Set new `maximum_entities_count` limit + pub fn set_maximum_entities_count(&mut self, maximum_entities_count: T::EntityId) { + self.maximum_entities_count = maximum_entities_count + } + + /// Increase `entities_created` by 1 + pub fn increment_created_entities_count(&mut self) { + self.entities_created += T::EntityId::one(); + } + + /// Decrease `entities_created` by 1 + pub fn decrement_created_entities_count(&mut self) { + self.entities_created -= T::EntityId::one(); + } + + /// Check if `entities_created` is less than `maximum_entities_count` limit set to this `EntityCreationVoucher` + pub fn limit_not_reached(&self) -> bool { + self.entities_created < self.maximum_entities_count + } + + /// Ensure new voucher`s max entities count is less than number of already created entities in this `EntityCreationVoucher` + pub fn ensure_new_max_entities_count_is_valid( + self, + maximum_entities_count: T::EntityId, + ) -> dispatch::Result { + ensure!( + maximum_entities_count >= self.entities_created, + ERROR_NEW_ENTITIES_MAX_COUNT_IS_LESS_THAN_NUMBER_OF_ALREADY_CREATED + ); + Ok(()) + } + + /// Ensure voucher limit not reached + pub fn ensure_voucher_limit_not_reached(&self) -> dispatch::Result { + ensure!(self.limit_not_reached(), ERROR_VOUCHER_LIMIT_REACHED); + Ok(()) + } +} + +/// Enum, representing all possible `Actor`s +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Eq, PartialEq, Clone, Copy, Debug)] +pub enum Actor { + Curator(T::CuratorGroupId, T::CuratorId), + Member(T::MemberId), + Lead, +} + +impl Default for Actor { + fn default() -> Self { + Self::Lead + } +} + +/// Permissions for an instance of a `Class` in the versioned store. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug)] +pub struct ClassPermissions { + /// For this permission, the individual member is allowed to create the entity and become controller. + any_member: bool, + + /// Whether to prevent everyone from creating an entity. + /// + /// This could be useful in order to quickly, and possibly temporarily, block new entity creation, without + /// having to tear down `can_create_entities`. + entity_creation_blocked: bool, + + /// Whether to prevent everyone from updating entity properties. + /// + /// This could be useful in order to quickly, and probably temporarily, block any editing of entities, + /// rather than for example having to set, and later clear. + all_entity_property_values_locked: bool, + + /// Current class maintainer curator groups + maintainers: BTreeSet, +} + +impl Default for ClassPermissions { + fn default() -> Self { + Self { + any_member: false, + entity_creation_blocked: false, + all_entity_property_values_locked: false, + maintainers: BTreeSet::new(), + } + } +} + +impl ClassPermissions { + /// Retieve `all_entity_property_values_locked` status + pub fn all_entity_property_values_locked(&self) -> bool { + self.all_entity_property_values_locked + } + + /// Retieve `any_member` status + pub fn any_member_status(&self) -> bool { + self.any_member + } + + /// Check if given `curator_group_id` is maintainer of current `Class` + pub fn is_maintainer(&self, curator_group_id: &T::CuratorGroupId) -> bool { + self.maintainers.contains(curator_group_id) + } + + /// Get `Class` maintainers by reference + pub fn get_maintainers(&self) -> &BTreeSet { + &self.maintainers + } + + /// Get `Class` maintainers by mutable reference + pub fn get_maintainers_mut(&mut self) -> &mut BTreeSet { + &mut self.maintainers + } + + /// Set `entity_creation_blocked` flag, as provided + pub fn set_entity_creation_blocked(&mut self, entity_creation_blocked: bool) { + self.entity_creation_blocked = entity_creation_blocked + } + + /// Set `all_entity_property_values_locked` flag, as provided + pub fn set_all_entity_property_values_locked( + &mut self, + all_entity_property_values_locked: bool, + ) { + self.all_entity_property_values_locked = all_entity_property_values_locked + } + + /// Set `any_member` flag, as provided + pub fn set_any_member_status(&mut self, any_member: bool) { + self.any_member = any_member; + } + + /// Update `maintainers` set with provided one + pub fn set_maintainers(&mut self, maintainers: BTreeSet) { + self.maintainers = maintainers + } + + /// Ensure provided actor can create entities of current `Class` + pub fn ensure_can_create_entities( + &self, + account_id: &T::AccountId, + actor: &Actor, + ) -> Result<(), &'static str> { + let can_create = match &actor { + Actor::Lead => { + ensure_lead_auth_success::(account_id)?; + true + } + Actor::Member(member_id) if self.any_member => { + ensure_member_auth_success::(member_id, account_id)?; + true + } + Actor::Curator(curator_group_id, curator_id) + if self.maintainers.contains(curator_group_id) => + { + perform_curator_in_group_auth::(curator_id, curator_group_id, account_id)?; + true + } + _ => false, + }; + ensure!(can_create, ERROR_ACTOR_CAN_NOT_CREATE_ENTITIES); + Ok(()) + } + + /// Ensure entities creation is not blocked on `Class` level + pub fn ensure_entity_creation_not_blocked(&self) -> dispatch::Result { + ensure!(self.entity_creation_blocked, ERROR_ENTITY_CREATION_BLOCKED); + Ok(()) + } + + /// Ensure maintainer, associated with given `group_id` is already added to `maintainers` set + pub fn ensure_maintainer_exists(&self, group_id: &T::CuratorGroupId) -> dispatch::Result { + ensure!( + self.maintainers.contains(group_id), + ERROR_MAINTAINER_DOES_NOT_EXIST + ); + Ok(()) + } + + /// Ensure maintainer, associated with given `group_id` is not yet added to `maintainers` set + pub fn ensure_maintainer_does_not_exist( + &self, + group_id: &T::CuratorGroupId, + ) -> dispatch::Result { + ensure!( + !self.maintainers.contains(group_id), + ERROR_MAINTAINER_ALREADY_EXISTS + ); + Ok(()) + } +} + +/// Owner of an `Entity`. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)] +pub enum EntityController { + Maintainers, + Member(T::MemberId), + Lead, +} + +impl EntityController { + /// Create `EntityController` enum representation, using provided `Actor` + pub fn from_actor(actor: &Actor) -> Self { + match &actor { + Actor::Lead => Self::Lead, + Actor::Member(member_id) => Self::Member(*member_id), + Actor::Curator(_, _) => Self::Maintainers, + } + } +} + +impl Default for EntityController { + fn default() -> Self { + Self::Lead + } +} + +/// Permissions for a given entity. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +pub struct EntityPermissions { + /// Current controller, which is initially set based on who created entity + pub controller: EntityController, + + /// Forbid groups to mutate any property value. + /// Can be useful to use in concert with some curation censorship policy + pub frozen: bool, + + /// Prevent from being referenced by any entity (including self-references). + /// Can be useful to use in concert with some curation censorship policy, + /// e.g. to block content from being included in some public playlist. + pub referenceable: bool, +} + +impl Default for EntityPermissions { + fn default() -> Self { + Self { + controller: EntityController::::default(), + frozen: false, + referenceable: true, + } + } +} + +impl EntityPermissions { + /// Create an instance of `EntityPermissions` with `EntityController` equal to provided one + pub fn default_with_controller(controller: EntityController) -> Self { + Self { + controller, + ..EntityPermissions::default() + } + } + + /// Set current `controller` as provided + pub fn set_conroller(&mut self, controller: EntityController) { + self.controller = controller + } + + /// Check if inner `controller` is equal to provided one + pub fn controller_is_equal_to(&self, entity_controller: &EntityController) -> bool { + self.controller == *entity_controller + } + + /// Set `frozen` flag as provided + pub fn set_frozen(&mut self, frozen: bool) { + self.frozen = frozen + } + + /// Set `referenceable` flag as provided + pub fn set_referencable(&mut self, referenceable: bool) { + self.referenceable = referenceable; + } + + /// Retrieve `referenceable` flag + pub fn is_referancable(&self) -> bool { + self.referenceable + } + + /// Get current `controller` by reference + pub fn get_controller(&self) -> &EntityController { + &self.controller + } + + /// Ensure actor with given `EntityAccessLevel` can remove entity + pub fn ensure_group_can_remove_entity(access_level: EntityAccessLevel) -> dispatch::Result { + match access_level { + EntityAccessLevel::EntityController => Ok(()), + EntityAccessLevel::EntityControllerAndMaintainer => Ok(()), + _ => Err(ERROR_ENTITY_REMOVAL_ACCESS_DENIED), + } + } +} + +/// Type, derived from dispatchable call, identifies the caller +#[derive(Encode, Decode, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] +pub enum EntityAccessLevel { + /// Caller identified as the entity maintainer + EntityMaintainer, + + /// Caller identified as the entity controller + EntityController, + + /// Caller, that can act as controller and maintainer simultaneously + /// (can be useful, when controller and maintainer have features, that do not intersect) + EntityControllerAndMaintainer, +} + +impl EntityAccessLevel { + /// Derives the `EntityAccessLevel` for the actor, attempting to act. + pub fn derive( + account_id: &T::AccountId, + entity_permissions: &EntityPermissions, + class_permissions: &ClassPermissions, + actor: &Actor, + ) -> Result { + let controller = EntityController::::from_actor(actor); + match actor { + Actor::Lead if entity_permissions.controller_is_equal_to(&controller) => { + ensure_lead_auth_success::(account_id).map(|_| Self::EntityController) + } + Actor::Member(member_id) if entity_permissions.controller_is_equal_to(&controller) => { + ensure_member_auth_success::(member_id, account_id) + .map(|_| Self::EntityController) + } + Actor::Curator(curator_group_id, curator_id) => { + perform_curator_in_group_auth::(curator_id, curator_group_id, account_id)?; + match ( + entity_permissions.controller_is_equal_to(&controller), + class_permissions.is_maintainer(curator_group_id), + ) { + (true, true) => Ok(Self::EntityControllerAndMaintainer), + (false, true) => Ok(Self::EntityMaintainer), + // Curator cannot be controller, but not maintainer simultaneously + _ => Err(ERROR_ENTITY_ACCESS_DENIED), + } + } + _ => Err(ERROR_ENTITY_ACCESS_DENIED), + } + } +} diff --git a/runtime-modules/content-directory/src/schema.rs b/runtime-modules/content-directory/src/schema.rs new file mode 100644 index 0000000000..0cb4bdc622 --- /dev/null +++ b/runtime-modules/content-directory/src/schema.rs @@ -0,0 +1,955 @@ +use crate::{permissions::EntityAccessLevel, *}; +use codec::{Decode, Encode}; +use core::ops::Deref; +#[cfg(feature = "std")] +pub use serde::{Deserialize, Serialize}; + +/// Type representing max length of vector property type +pub type VecMaxLength = u16; + +/// Type representing max length of text property type +pub type TextMaxLength = u16; + +/// Type identificator for property id +pub type PropertyId = u16; + +/// Type identificator for schema id +pub type SchemaId = u16; + +/// Used to force property values to only reference entities, owned by the same controller +pub type SameController = bool; + +/// Locking policy, representing `Property` locking status for both controller and maintainer +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Default, Decode, Clone, Copy, PartialEq, Eq, Debug)] +pub struct PropertyLockingPolicy { + is_locked_from_maintainer: bool, + is_locked_from_controller: bool, +} + +/// Enum, used for `PropertyType` representation +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)] +pub enum Type { + Bool, + Uint16, + Uint32, + Uint64, + Int16, + Int32, + Int64, + /// Max length of text item. + Text(TextMaxLength), + /// Can reference only specific class id entities + Reference(T::ClassId, SameController), +} + +impl Default for Type { + fn default() -> Self { + Self::Bool + } +} + +/// Vector property type representation +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)] +pub struct VecPropertyType { + vec_type: Type, + /// Max length of vector, corresponding to a given type + max_length: VecMaxLength, +} + +impl Default for VecPropertyType { + fn default() -> Self { + Self { + vec_type: Type::default(), + max_length: 0, + } + } +} + +impl VecPropertyType { + pub fn new(vec_type: Type, max_length: VecMaxLength) -> Self { + Self { + vec_type, + max_length, + } + } + + /// Ensure `Type` spcific `TextMaxLengthConstraint` & `VecMaxLengthConstraint` satisfied + fn ensure_property_type_size_is_valid(&self) -> dispatch::Result { + if let Type::Text(text_max_len) = self.vec_type { + ensure!( + text_max_len <= T::TextMaxLengthConstraint::get(), + ERROR_TEXT_PROP_IS_TOO_LONG + ); + } + ensure!( + self.max_length <= T::VecMaxLengthConstraint::get(), + ERROR_VEC_PROP_IS_TOO_LONG + ); + Ok(()) + } + + pub fn get_vec_type(&self) -> &Type { + &self.vec_type + } + + pub fn get_max_len(&self) -> VecMaxLength { + self.max_length + } +} + +/// `Type` enum wrapper +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)] +pub struct SingleValuePropertyType(Type); + +impl Default for SingleValuePropertyType { + fn default() -> Self { + Self(Type::default()) + } +} + +impl SingleValuePropertyType { + /// Ensure `Type` specific `TextMaxLengthConstraint` satisfied + fn ensure_property_type_size_is_valid(&self) -> dispatch::Result { + if let Type::Text(text_max_len) = self.0 { + ensure!( + text_max_len <= T::TextMaxLengthConstraint::get(), + ERROR_TEXT_PROP_IS_TOO_LONG + ); + } + Ok(()) + } +} + +impl Deref for SingleValuePropertyType { + type Target = Type; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Enum, representing either `SingleValuePropertyType` or `VecPropertyType` +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)] +pub enum PropertyType { + Single(SingleValuePropertyType), + Vector(VecPropertyType), +} + +impl Default for PropertyType { + fn default() -> Self { + Self::Single(SingleValuePropertyType::default()) + } +} + +impl PropertyType { + pub fn as_single_value_type(&self) -> Option<&Type> { + if let PropertyType::Single(single_value_property_type) = self { + Some(single_value_property_type) + } else { + None + } + } + + pub fn as_vec_type(&self) -> Option<&VecPropertyType> { + if let PropertyType::Vector(single_value_property_type) = self { + Some(single_value_property_type) + } else { + None + } + } + + pub fn get_inner_type(&self) -> &Type { + match self { + PropertyType::Single(single_property_type) => single_property_type, + PropertyType::Vector(vec_property_type) => vec_property_type.get_vec_type(), + } + } + + /// Retrives same_controller. + /// Always returns false if `Type` is not a reference, + pub fn same_controller_status(&self) -> SameController { + if let Type::Reference(_, same_controller) = self.get_inner_type() { + *same_controller + } else { + false + } + } + + pub fn get_referenced_class_id(&self) -> Option { + if let Type::Reference(class_id, _) = self.get_inner_type() { + Some(*class_id) + } else { + None + } + } +} + +/// Value enum representation, related to corresponding `SinglePropertyValue` structure +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +pub enum Value { + Bool(bool), + Uint16(u16), + Uint32(u32), + Uint64(u64), + Int16(i16), + Int32(i32), + Int64(i64), + Text(Vec), + Reference(T::EntityId), +} + +impl Default for Value { + fn default() -> Value { + Self::Bool(false) + } +} + +impl Value { + /// Retrieve involved `entity_id`, if current `Value` is reference + pub fn get_involved_entity(&self) -> Option { + if let Value::Reference(entity_id) = self { + Some(*entity_id) + } else { + None + } + } +} + +/// `Value` enum wrapper +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +pub struct SinglePropertyValue { + value: Value, +} + +impl Default for SinglePropertyValue { + fn default() -> Self { + Self { + value: Value::default(), + } + } +} + +impl SinglePropertyValue { + pub fn new(value: Value) -> Self { + Self { value } + } + + pub fn get_value_ref(&self) -> &Value { + &self.value + } + + pub fn get_value(self) -> Value { + self.value + } +} + +/// Vector value enum representation, related to corresponding `VecPropertyValue` structure +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +pub enum VecValue { + Bool(Vec), + Uint16(Vec), + Uint32(Vec), + Uint64(Vec), + Int16(Vec), + Int32(Vec), + Int64(Vec), + Text(Vec>), + Reference(Vec), +} + +impl Default for VecValue { + fn default() -> Self { + Self::Bool(vec![]) + } +} + +impl VecValue { + /// Retrieve all involved `entity_id`'s, if current `VecValue` is reference + pub fn get_involved_entities(&self) -> Option> { + if let Self::Reference(entity_ids) = self { + Some(entity_ids.to_owned()) + } else { + None + } + } +} + +/// Consists of `VecPropertyValue` enum representation and `nonce`, used to avoid vector data race update conditions +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)] +pub struct VecPropertyValue { + vec_value: VecValue, + nonce: T::Nonce, +} + +impl VecPropertyValue { + fn increment_nonce(&mut self) -> T::Nonce { + self.nonce += T::Nonce::one(); + self.nonce + } + + pub fn new(vec_value: VecValue, nonce: T::Nonce) -> Self { + Self { vec_value, nonce } + } + + /// Retrieve `VecValue` by reference + pub fn get_vec_value(&self) -> &VecValue { + &self.vec_value + } + + fn len(&self) -> usize { + match &self.vec_value { + VecValue::Bool(vec) => vec.len(), + VecValue::Uint16(vec) => vec.len(), + VecValue::Uint32(vec) => vec.len(), + VecValue::Uint64(vec) => vec.len(), + VecValue::Int16(vec) => vec.len(), + VecValue::Int32(vec) => vec.len(), + VecValue::Int64(vec) => vec.len(), + VecValue::Text(vec) => vec.len(), + VecValue::Reference(vec) => vec.len(), + } + } + + /// Clear current `vec_value`, increment `nonce` + pub fn vec_clear(&mut self) { + match &mut self.vec_value { + VecValue::Bool(vec) => *vec = vec![], + VecValue::Uint16(vec) => *vec = vec![], + VecValue::Uint32(vec) => *vec = vec![], + VecValue::Uint64(vec) => *vec = vec![], + VecValue::Int16(vec) => *vec = vec![], + VecValue::Int32(vec) => *vec = vec![], + VecValue::Int64(vec) => *vec = vec![], + VecValue::Text(vec) => *vec = vec![], + VecValue::Reference(vec) => *vec = vec![], + } + + self.increment_nonce(); + } + + /// Perform removal at given `index_in_property_vec`, increment `nonce` + pub fn vec_remove_at(&mut self, index_in_property_vec: VecMaxLength) { + fn remove_at_checked(vec: &mut Vec, index_in_property_vec: VecMaxLength) { + if (index_in_property_vec as usize) < vec.len() { + vec.remove(index_in_property_vec as usize); + } + } + + match &mut self.vec_value { + VecValue::Bool(vec) => remove_at_checked(vec, index_in_property_vec), + VecValue::Uint16(vec) => remove_at_checked(vec, index_in_property_vec), + VecValue::Uint32(vec) => remove_at_checked(vec, index_in_property_vec), + VecValue::Uint64(vec) => remove_at_checked(vec, index_in_property_vec), + VecValue::Int16(vec) => remove_at_checked(vec, index_in_property_vec), + VecValue::Int32(vec) => remove_at_checked(vec, index_in_property_vec), + VecValue::Int64(vec) => remove_at_checked(vec, index_in_property_vec), + VecValue::Text(vec) => remove_at_checked(vec, index_in_property_vec), + VecValue::Reference(vec) => remove_at_checked(vec, index_in_property_vec), + } + + self.increment_nonce(); + } + + /// Insert provided `Value` at given `index_in_property_vec`, increment `nonce` + pub fn vec_insert_at(&mut self, index_in_property_vec: VecMaxLength, single_value: Value) { + fn insert_at(vec: &mut Vec, index_in_property_vec: VecMaxLength, value: T) { + if (index_in_property_vec as usize) < vec.len() { + vec.insert(index_in_property_vec as usize, value); + } + } + + match (&mut self.vec_value, single_value) { + (VecValue::Bool(vec), Value::Bool(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (VecValue::Uint16(vec), Value::Uint16(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (VecValue::Uint32(vec), Value::Uint32(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (VecValue::Uint64(vec), Value::Uint64(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (VecValue::Int16(vec), Value::Int16(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (VecValue::Int32(vec), Value::Int32(value)) => { + insert_at(vec, index_in_property_vec, value) + } + (VecValue::Int64(vec), Value::Int64(value)) => { + insert_at(vec, index_in_property_vec, value) + } + + // Match by move, when https://github.com/rust-lang/rust/issues/68354 stableize + (VecValue::Text(vec), Value::Text(ref value)) => { + insert_at(vec, index_in_property_vec, value.to_owned()) + } + (VecValue::Reference(vec), Value::Reference(value)) => { + insert_at(vec, index_in_property_vec, value) + } + _ => return, + } + + self.increment_nonce(); + } + + /// Ensure `VecPropertyValue` nonce is equal to provided one. + /// Used to to avoid possible data races, when performing vector specific operations + pub fn ensure_nonce_equality(&self, new_nonce: T::Nonce) -> dispatch::Result { + ensure!( + self.nonce == new_nonce, + ERROR_PROP_VALUE_VEC_NONCES_DOES_NOT_MATCH + ); + Ok(()) + } + + /// Ensure, provided index is valid index of `VecValue` + pub fn ensure_index_in_property_vector_is_valid( + &self, + index_in_property_vec: VecMaxLength, + ) -> dispatch::Result { + ensure!( + (index_in_property_vec as usize) < self.len(), + ERROR_ENTITY_PROP_VALUE_VECTOR_INDEX_IS_OUT_OF_RANGE + ); + + Ok(()) + } +} + +/// Enum, representing either `SinglePropertyValue` or `VecPropertyValue` +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +pub enum PropertyValue { + Single(SinglePropertyValue), + Vector(VecPropertyValue), +} + +impl PropertyValue { + pub fn as_single_property_value(&self) -> Option<&SinglePropertyValue> { + if let PropertyValue::Single(single_property_value) = self { + Some(single_property_value) + } else { + None + } + } + + pub fn as_vec_property_value(&self) -> Option<&VecPropertyValue> { + if let PropertyValue::Vector(vec_property_value) = self { + Some(vec_property_value) + } else { + None + } + } + + pub fn as_vec_property_value_mut(&mut self) -> Option<&mut VecPropertyValue> { + if let PropertyValue::Vector(vec_property_value) = self { + Some(vec_property_value) + } else { + None + } + } + + /// Update `Self` with provided `PropertyValue` + pub fn update(&mut self, mut new_value: Self) { + if let (Some(vec_property_value), Some(new_vec_property_value)) = ( + self.as_vec_property_value_mut(), + new_value.as_vec_property_value_mut(), + ) { + new_vec_property_value.nonce = vec_property_value.increment_nonce(); + } + *self = new_value; + } + + /// Retrieve all involved `entity_id`'s, if current `PropertyValue` is reference + pub fn get_involved_entities(&self) -> Option> { + match self { + PropertyValue::Single(single_property_value) => { + if let Some(entity_id) = single_property_value.get_value_ref().get_involved_entity() + { + Some(vec![entity_id]) + } else { + None + } + } + PropertyValue::Vector(vector_property_value) => vector_property_value + .get_vec_value() + .get_involved_entities(), + } + } +} + +impl Default for PropertyValue { + fn default() -> Self { + PropertyValue::Single(SinglePropertyValue::default()) + } +} + +/// A schema defines what properties describe an entity +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] +pub struct Schema { + /// Indices into properties vector for the corresponding class. + properties: BTreeSet, + /// If schema can be added to an entity + is_active: bool, +} + +impl Default for Schema { + fn default() -> Self { + Self { + properties: BTreeSet::new(), + // Default schema status + is_active: true, + } + } +} + +impl Schema { + /// Create new schema with provided properties + pub fn new(properties: BTreeSet) -> Self { + Self { + properties, + // Default schema status + is_active: true, + } + } + + /// If `Schema` can be added to an entity + pub fn is_active(&self) -> bool { + self.is_active + } + + /// Ensure schema in `active` status + pub fn ensure_is_active(&self) -> dispatch::Result { + ensure!(self.is_active, ERROR_CLASS_SCHEMA_NOT_ACTIVE); + Ok(()) + } + + /// Ensure `Schema` properties are valid indices of `Class` properties + pub fn ensure_schema_properties_are_valid_indices( + &self, + class_properties: &[Property], + ) -> dispatch::Result { + let has_unknown_properties = self + .get_properties() + .iter() + .any(|&prop_id| prop_id >= class_properties.len() as PropertyId); + ensure!( + !has_unknown_properties, + ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_PROP_INDEX + ); + Ok(()) + } + + /// Get `Schema` `properties` by reference + pub fn get_properties(&self) -> &BTreeSet { + &self.properties + } + + /// Get `Schema` `properties` by mutable reference + pub fn get_properties_mut(&mut self) -> &mut BTreeSet { + &mut self.properties + } + + /// Set `Schema`'s `is_active` flag as provided + pub fn set_status(&mut self, is_active: bool) { + self.is_active = is_active; + } +} + +/// `Property` representation, related to a given `Class` +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)] +pub struct Property { + pub property_type: PropertyType, + /// If property value can be skipped, when adding entity schema support + pub required: bool, + /// Used to enforce uniquness of a property across all entities that have this property + pub unique: bool, + pub name: Vec, + pub description: Vec, + pub locking_policy: PropertyLockingPolicy, +} + +impl Property { + pub fn is_locked_from(&self, access_level: EntityAccessLevel) -> bool { + let is_locked_from_controller = self.locking_policy.is_locked_from_controller; + let is_locked_from_maintainer = self.locking_policy.is_locked_from_maintainer; + match access_level { + EntityAccessLevel::EntityControllerAndMaintainer => { + is_locked_from_controller && is_locked_from_maintainer + } + EntityAccessLevel::EntityController => is_locked_from_controller, + EntityAccessLevel::EntityMaintainer => is_locked_from_maintainer, + } + } + + /// Ensure `Property` is unlocked from `Actor` with given `EntityAccessLevel` + pub fn ensure_unlocked_from(&self, access_level: EntityAccessLevel) -> dispatch::Result { + ensure!( + self.is_locked_from(access_level), + ERROR_CLASS_PROPERTY_TYPE_IS_LOCKED_FOR_GIVEN_ACTOR + ); + Ok(()) + } + + /// Ensure all values are unique except of null non required values + pub fn ensure_unique_option_satisfied( + &self, + new_value: &PropertyValue, + entity_values_updated: &BTreeMap>, + ) -> dispatch::Result { + if self.unique && (*new_value != PropertyValue::default() || self.required) { + ensure!( + entity_values_updated + .iter() + .all(|(_, prop_value)| *prop_value != *new_value), + ERROR_PROPERTY_VALUE_SHOULD_BE_UNIQUE + ); + } + Ok(()) + } + + /// Validate new `PropertyValue` against the type of this property + /// and check any additional constraints + pub fn ensure_property_value_to_update_is_valid( + &self, + value: &PropertyValue, + current_entity_controller: &EntityController, + ) -> dispatch::Result { + self.ensure_prop_value_matches_its_type(value)?; + self.ensure_valid_reference_prop(value, current_entity_controller)?; + self.validate_max_len_if_text_prop(value)?; + self.validate_max_len_if_vec_prop(value)?; + Ok(()) + } + + /// Ensure `SinglePropertyValue` type is equal to the `VecPropertyValue` type + /// and check all constraints + pub fn ensure_prop_value_can_be_inserted_at_prop_vec( + &self, + single_value: &SinglePropertyValue, + vec_value: &VecPropertyValue, + index_in_property_vec: VecMaxLength, + current_entity_controller: &EntityController, + ) -> dispatch::Result { + vec_value.ensure_index_in_property_vector_is_valid(index_in_property_vec)?; + + fn validate_prop_vec_len_after_value_insert( + vec: &[T], + max_len: VecMaxLength, + ) -> dispatch::Result { + ensure!( + vec.len() < max_len as usize, + ERROR_ENTITY_PROP_VALUE_VECTOR_IS_TOO_LONG + ); + Ok(()) + } + + let property_type_vec = self + .property_type + .as_vec_type() + .ok_or(ERROR_PROP_VALUE_TYPE_DOESNT_MATCH_INTERNAL_ENTITY_VECTOR_TYPE)?; + + let max_vec_len = property_type_vec.get_max_len(); + + match ( + single_value.get_value_ref(), + vec_value.get_vec_value(), + property_type_vec.get_vec_type(), + ) { + // Single values + (Value::Bool(_), VecValue::Bool(vec), Type::Bool) => { + validate_prop_vec_len_after_value_insert(vec, max_vec_len) + } + (Value::Uint16(_), VecValue::Uint16(vec), Type::Uint16) => { + validate_prop_vec_len_after_value_insert(vec, max_vec_len) + } + (Value::Uint32(_), VecValue::Uint32(vec), Type::Uint32) => { + validate_prop_vec_len_after_value_insert(vec, max_vec_len) + } + (Value::Uint64(_), VecValue::Uint64(vec), Type::Uint64) => { + validate_prop_vec_len_after_value_insert(vec, max_vec_len) + } + (Value::Int16(_), VecValue::Int16(vec), Type::Int16) => { + validate_prop_vec_len_after_value_insert(vec, max_vec_len) + } + (Value::Int32(_), VecValue::Int32(vec), Type::Int32) => { + validate_prop_vec_len_after_value_insert(vec, max_vec_len) + } + (Value::Int64(_), VecValue::Int64(vec), Type::Int64) => { + validate_prop_vec_len_after_value_insert(vec, max_vec_len) + } + (Value::Text(text_item), VecValue::Text(vec), Type::Text(text_max_len)) => { + Self::validate_max_len_of_text(text_item, *text_max_len)?; + validate_prop_vec_len_after_value_insert(vec, max_vec_len) + } + ( + Value::Reference(entity_id), + VecValue::Reference(vec), + Type::Reference(class_id, same_controller_status), + ) => { + Self::ensure_referancable( + *class_id, + *entity_id, + *same_controller_status, + current_entity_controller, + )?; + validate_prop_vec_len_after_value_insert(vec, max_vec_len) + } + _ => Err(ERROR_PROP_VALUE_TYPE_DOESNT_MATCH_INTERNAL_ENTITY_VECTOR_TYPE), + } + } + + pub fn validate_max_len_if_text_prop(&self, value: &PropertyValue) -> dispatch::Result { + let single_value = value + .as_single_property_value() + .map(|single_prop_value| single_prop_value.get_value_ref()); + match (single_value, &self.property_type.as_single_value_type()) { + (Some(Value::Text(text)), Some(Type::Text(max_len))) => { + Self::validate_max_len_of_text(text, *max_len) + } + _ => Ok(()), + } + } + + pub fn validate_max_len_of_text(text: &[u8], max_len: TextMaxLength) -> dispatch::Result { + ensure!(text.len() <= max_len as usize, ERROR_TEXT_PROP_IS_TOO_LONG); + Ok(()) + } + + fn validate_vec_len(vec: &[V], max_len: VecMaxLength) -> dispatch::Result { + ensure!(vec.len() <= max_len as usize, ERROR_VEC_PROP_IS_TOO_LONG); + Ok(()) + } + + pub fn validate_max_len_if_vec_prop(&self, value: &PropertyValue) -> dispatch::Result { + let (vec_value, vec_property_type) = if let (Some(vec_value), Some(vec_property_type)) = ( + value + .as_vec_property_value() + .map(|vec_property_value| vec_property_value.get_vec_value()), + self.property_type.as_vec_type(), + ) { + (vec_value, vec_property_type) + } else { + return Ok(()); + }; + let max_len = vec_property_type.get_max_len(); + match vec_value { + VecValue::Bool(vec) => Self::validate_vec_len(vec, max_len), + VecValue::Uint16(vec) => Self::validate_vec_len(vec, max_len), + VecValue::Uint32(vec) => Self::validate_vec_len(vec, max_len), + VecValue::Uint64(vec) => Self::validate_vec_len(vec, max_len), + VecValue::Int16(vec) => Self::validate_vec_len(vec, max_len), + VecValue::Int32(vec) => Self::validate_vec_len(vec, max_len), + VecValue::Int64(vec) => Self::validate_vec_len(vec, max_len), + VecValue::Text(vec) => { + Self::validate_vec_len(vec, max_len)?; + if let Type::Text(text_max_len) = vec_property_type.get_vec_type() { + for text_item in vec.iter() { + Self::validate_max_len_of_text(text_item, *text_max_len)?; + } + } + Ok(()) + } + VecValue::Reference(vec) => Self::validate_vec_len(vec, max_len), + } + } + + pub fn ensure_prop_value_matches_its_type(&self, value: &PropertyValue) -> dispatch::Result { + ensure!( + self.does_prop_value_match_type(value), + ERROR_PROP_VALUE_DONT_MATCH_TYPE + ); + Ok(()) + } + + pub fn does_prop_value_match_type(&self, value: &PropertyValue) -> bool { + // A non required property can be updated to Bool(false): + if !self.required && *value == PropertyValue::default() { + return true; + } + match (value, &self.property_type) { + ( + PropertyValue::Single(single_property_value), + PropertyType::Single(ref single_property_type), + ) => { + match ( + single_property_value.get_value_ref(), + single_property_type.deref(), + ) { + (Value::Bool(_), Type::Bool) + | (Value::Uint16(_), Type::Uint16) + | (Value::Uint32(_), Type::Uint32) + | (Value::Uint64(_), Type::Uint64) + | (Value::Int16(_), Type::Int16) + | (Value::Int32(_), Type::Int32) + | (Value::Int64(_), Type::Int64) + | (Value::Text(_), Type::Text(_)) + | (Value::Reference(_), Type::Reference(_, _)) => true, + _ => false, + } + } + ( + PropertyValue::Vector(vec_property_value), + PropertyType::Vector(ref vec_property_type), + ) => { + match ( + vec_property_value.get_vec_value(), + vec_property_type.get_vec_type(), + ) { + (VecValue::Bool(_), Type::Bool) + | (VecValue::Uint16(_), Type::Uint16) + | (VecValue::Uint32(_), Type::Uint32) + | (VecValue::Uint64(_), Type::Uint64) + | (VecValue::Int16(_), Type::Int16) + | (VecValue::Int32(_), Type::Int32) + | (VecValue::Int64(_), Type::Int64) + | (VecValue::Text(_), Type::Text(_)) + | (VecValue::Reference(_), Type::Reference(_, _)) => true, + _ => false, + } + } + _ => false, + } + } + + pub fn ensure_valid_reference_prop( + &self, + value: &PropertyValue, + current_entity_controller: &EntityController, + ) -> dispatch::Result { + match (value, &self.property_type) { + ( + PropertyValue::Single(single_property_value), + PropertyType::Single(single_property_type), + ) => { + if let ( + Value::Reference(entity_id), + Type::Reference(class_id, same_controller_status), + ) = ( + single_property_value.get_value_ref(), + single_property_type.deref(), + ) { + Self::ensure_referancable( + *class_id, + *entity_id, + *same_controller_status, + current_entity_controller, + )?; + } + } + ( + PropertyValue::Vector(vec_property_value), + PropertyType::Vector(vec_property_type), + ) => { + if let ( + VecValue::Reference(entities_vec), + Type::Reference(class_id, same_controller_status), + ) = ( + vec_property_value.get_vec_value(), + vec_property_type.get_vec_type(), + ) { + for entity_id in entities_vec.iter() { + Self::ensure_referancable( + *class_id, + *entity_id, + *same_controller_status, + current_entity_controller, + )?; + } + } + } + _ => (), + } + Ok(()) + } + + pub fn ensure_referancable( + class_id: T::ClassId, + entity_id: T::EntityId, + same_controller_status: bool, + current_entity_controller: &EntityController, + ) -> dispatch::Result { + Module::::ensure_known_class_id(class_id)?; + Module::::ensure_known_entity_id(entity_id)?; + let entity = Module::::entity_by_id(entity_id); + + let entity_permissions = entity.get_permissions_ref(); + + ensure!( + entity_permissions.is_referancable(), + ERROR_ENTITY_CAN_NOT_BE_REFRENCED + ); + + ensure!( + entity.class_id == class_id, + ERROR_PROP_DOES_NOT_MATCH_ITS_CLASS + ); + if same_controller_status { + ensure!( + entity_permissions.controller_is_equal_to(current_entity_controller), + ERROR_SAME_CONTROLLER_CONSTRAINT_VIOLATION + ); + } + Ok(()) + } + + /// Ensure `PropertyNameLengthConstraint` satisfied + pub fn ensure_name_is_valid(&self) -> dispatch::Result { + T::PropertyNameLengthConstraint::get().ensure_valid( + self.name.len(), + ERROR_PROPERTY_NAME_TOO_SHORT, + ERROR_PROPERTY_NAME_TOO_LONG, + ) + } + + /// Ensure `PropertyDescriptionLengthConstraint` satisfied + pub fn ensure_description_is_valid(&self) -> dispatch::Result { + T::PropertyDescriptionLengthConstraint::get().ensure_valid( + self.description.len(), + ERROR_PROPERTY_DESCRIPTION_TOO_SHORT, + ERROR_PROPERTY_DESCRIPTION_TOO_LONG, + ) + } + + /// Ensure `Type` specific constraints satisfied + pub fn ensure_property_type_size_is_valid(&self) -> dispatch::Result { + match &self.property_type { + PropertyType::Single(single_property_type) => { + single_property_type.ensure_property_type_size_is_valid() + } + PropertyType::Vector(vec_property_type) => { + vec_property_type.ensure_property_type_size_is_valid() + } + } + } + + /// If `Type::Reference`, ensure refers to existing `class_id` + pub fn ensure_property_type_reference_is_valid(&self) -> dispatch::Result { + let has_unknown_reference = + if let Type::Reference(other_class_id, _) = self.property_type.get_inner_type() { + !>::exists(other_class_id) + } else { + false + }; + + ensure!( + !has_unknown_reference, + ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_CLASS + ); + + Ok(()) + } +} diff --git a/runtime-modules/content-directory/src/tests.rs b/runtime-modules/content-directory/src/tests.rs new file mode 100644 index 0000000000..186c17fcc1 --- /dev/null +++ b/runtime-modules/content-directory/src/tests.rs @@ -0,0 +1,1692 @@ +// #![cfg(test)] + +// use super::*; +// use crate::mock::*; +// use core::iter::FromIterator; +// use rstd::collections::btree_set::BTreeSet; +// use srml_support::{assert_err, assert_ok}; + +// #[test] +// fn create_class_then_entity_with_default_class() { +// with_test_externalities(|| { +// // Only authorized accounts can create classes +// assert_err!( +// TestModule::create_class_with_default_permissions( +// Origin::signed(UNAUTHORIZED_CLASS_PERMISSIONS_CREATOR), +// b"class_name".to_vec(), +// b"class_description".to_vec(), +// ), +// "NotPermittedToCreateClass" +// ); + +// let class_id = create_simple_class_with_default_permissions(); + +// assert!(>::exists(class_id)); + +// assert_eq!(TestModule::next_class_id(), class_id + 1); + +// // default class permissions have empty add_schema acl +// assert_err!( +// TestModule::add_class_schema( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ZERO), +// Some(0), +// class_id, +// vec![], +// simple_test_schema() +// ), +// "NotInAddSchemasSet" +// ); + +// // attemt to add class schema to nonexistent class +// assert_err!( +// TestModule::add_class_schema( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ZERO), +// Some(0), +// class_id + 1, +// vec![], +// simple_test_schema() +// ), +// ERROR_CLASS_NOT_FOUND +// ); + +// // give members of GROUP_ZERO permission to add schemas +// let add_schema_set = CredentialSet::from(vec![0]); +// assert_ok!(TestModule::set_class_add_schemas_set( +// Origin::ROOT, +// None, +// class_id, +// add_schema_set +// )); + +// // successfully add a new schema +// assert_ok!(TestModule::add_class_schema( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ZERO), +// Some(0), +// class_id, +// vec![], +// simple_test_schema() +// )); + +// // System can always create entities (provided class exists) bypassing any permissions +// let entity_id_1 = next_entity_id(); +// assert_ok!(TestModule::create_entity(Origin::ROOT, None, class_id,)); +// // entities created by system are "un-owned" +// assert!(!>::exists(entity_id_1)); +// assert_eq!( +// TestModule::entity_maintainer_by_entity_id(entity_id_1), +// None +// ); + +// assert_eq!(TestModule::next_entity_id(), entity_id_1 + 1); + +// // default permissions have empty create_entities set and by default no entities can be created +// assert_err!( +// TestModule::create_entity( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// class_id, +// ), +// "EntitiesCannotBeCreated" +// ); + +// assert_ok!(TestModule::set_class_entities_can_be_created( +// Origin::ROOT, +// None, +// class_id, +// true +// )); + +// assert_err!( +// TestModule::create_entity( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// class_id, +// ), +// "NotInCreateEntitiesSet" +// ); + +// // give members of GROUP_ONE permission to create entities +// let create_entities_set = CredentialSet::from(vec![1]); +// assert_ok!(TestModule::set_class_create_entities_set( +// Origin::ROOT, +// None, +// class_id, +// create_entities_set +// )); + +// let entity_id_2 = next_entity_id(); +// assert_ok!(TestModule::create_entity( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// class_id, +// )); + +// assert!(>::exists(entity_id_2)); +// assert_eq!( +// TestModule::entity_maintainer_by_entity_id(entity_id_2), +// Some(1) +// ); + +// assert_eq!(TestModule::next_entity_id(), entity_id_2 + 1); + +// // Updating entity must be authorized +// assert_err!( +// TestModule::add_schema_support_to_entity( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ZERO), +// Some(0), +// false, // not claiming to be entity maintainer +// entity_id_2, +// 0, // first schema created +// simple_test_entity_property_values() +// ), +// "CredentialNotInEntityPermissionssUpdateSet" +// ); + +// // default permissions give entity maintainer permission to update and delete +// assert_ok!(TestModule::add_schema_support_to_entity( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// true, // we are claiming to be the entity maintainer +// entity_id_2, +// 0, +// simple_test_entity_property_values() +// )); +// assert_ok!(TestModule::update_entity_property_values( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// true, // we are claiming to be the entity maintainer +// entity_id_2, +// simple_test_entity_property_values() +// )); +// }) +// } + +// #[test] +// fn cannot_create_class_with_empty_name() { +// with_test_externalities(|| { +// let empty_name = vec![]; +// assert_err!( +// TestModule::create_class_with_default_permissions( +// Origin::signed(CLASS_PERMISSIONS_CREATOR1), +// empty_name, +// good_class_description(), +// ), +// ERROR_CLASS_NAME_TOO_SHORT +// ); +// }) +// } + +// #[test] +// fn create_class_with_empty_description() { +// with_test_externalities(|| { +// let empty_description = vec![]; +// assert_ok!(TestModule::create_class_with_default_permissions( +// Origin::signed(CLASS_PERMISSIONS_CREATOR1), +// good_class_name(), +// empty_description +// )); +// }) +// } + +// #[test] +// fn cannot_create_entity_with_unknown_class_id() { +// with_test_externalities(|| { +// assert_err!( +// TestModule::create_entity( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// UNKNOWN_CLASS_ID, +// ), +// ERROR_CLASS_NOT_FOUND +// ); +// }) +// } + +// #[test] +// fn class_set_admins() { +// with_test_externalities(|| { +// // create a class where all permission sets are empty +// let class_id = create_simple_class(class_minimal()); +// let class = TestModule::class_by_id(class_id); + +// assert!(class.get_permissions_ref().admins.is_empty()); + +// let credential_set = CredentialSet::from(vec![1]); + +// // only root should be able to set admins +// assert_err!( +// TestModule::set_class_admins(Origin::signed(1), class_id, credential_set.clone()), +// "NotRootOrigin" +// ); +// assert_err!( +// TestModule::set_class_admins( +// Origin::NONE, //unsigned inherent? +// class_id, +// credential_set.clone() +// ), +// "BadOrigin:ExpectedRootOrSigned" +// ); + +// // root origin can set admins +// assert_ok!(TestModule::set_class_admins( +// Origin::ROOT, +// class_id, +// credential_set.clone() +// )); + +// let class = TestModule::class_by_id(class_id); +// assert_eq!(class.get_permissions_ref().admins, credential_set); +// }) +// } + +// #[test] +// fn class_set_add_schemas_set() { +// with_test_externalities(|| { +// const ADMIN_ACCOUNT: u64 = MEMBER_ONE_WITH_CREDENTIAL_ZERO; +// // create a class where all permission sets are empty +// let class_id = create_simple_class(class_minimal_with_admins(vec![0])); +// let class = TestModule::class_by_id(class_id); + +// assert!(class.get_permissions_ref().add_schemas.is_empty()); + +// let credential_set1 = CredentialSet::from(vec![1, 2]); +// let credential_set2 = CredentialSet::from(vec![3, 4]); + +// // root +// assert_ok!(TestModule::set_class_add_schemas_set( +// Origin::ROOT, +// None, +// class_id, +// credential_set1.clone() +// )); +// let class = TestModule::class_by_id(class_id); +// assert_eq!(class.get_permissions_ref().add_schemas, credential_set1); + +// // admins +// assert_ok!(TestModule::set_class_add_schemas_set( +// Origin::signed(ADMIN_ACCOUNT), +// Some(0), +// class_id, +// credential_set2.clone() +// )); +// let class = TestModule::class_by_id(class_id); +// assert_eq!(class.get_permissions_ref().add_schemas, credential_set2); + +// // non-admins +// assert_err!( +// TestModule::set_class_add_schemas_set( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// class_id, +// credential_set2.clone() +// ), +// "NotInAdminsSet" +// ); +// }) +// } + +// #[test] +// fn class_set_class_create_entities_set() { +// with_test_externalities(|| { +// const ADMIN_ACCOUNT: u64 = MEMBER_ONE_WITH_CREDENTIAL_ZERO; +// // create a class where all permission sets are empty +// let class_id = create_simple_class(class_minimal_with_admins(vec![0])); +// let class = TestModule::class_by_id(class_id); + +// assert!(class.get_permissions_ref().create_entities.is_empty()); + +// let credential_set1 = CredentialSet::from(vec![1, 2]); +// let credential_set2 = CredentialSet::from(vec![3, 4]); + +// // root +// assert_ok!(TestModule::set_class_create_entities_set( +// Origin::ROOT, +// None, +// class_id, +// credential_set1.clone() +// )); +// let class = TestModule::class_by_id(class_id); +// assert_eq!(class.get_permissions_ref().create_entities, credential_set1); + +// // admins +// assert_ok!(TestModule::set_class_create_entities_set( +// Origin::signed(ADMIN_ACCOUNT), +// Some(0), +// class_id, +// credential_set2.clone() +// )); +// let class = TestModule::class_by_id(class_id); +// assert_eq!(class.get_permissions_ref().create_entities, credential_set2); + +// // non-admins +// assert_err!( +// TestModule::set_class_create_entities_set( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// class_id, +// credential_set2.clone() +// ), +// "NotInAdminsSet" +// ); +// }) +// } + +// #[test] +// fn class_set_class_entities_can_be_created() { +// with_test_externalities(|| { +// const ADMIN_ACCOUNT: u64 = MEMBER_ONE_WITH_CREDENTIAL_ZERO; +// // create a class where all permission sets are empty +// let class_id = create_simple_class(class_minimal_with_admins(vec![0])); +// let class = TestModule::class_by_id(class_id); + +// assert_eq!(class.get_permissions_ref().entity_creation_blocked, false); + +// // root +// assert_ok!(TestModule::set_class_entities_can_be_created( +// Origin::ROOT, +// None, +// class_id, +// true +// )); +// let class = TestModule::class_by_id(class_id); +// assert_eq!(class.get_permissions_ref().entity_creation_blocked, true); + +// // admins +// assert_ok!(TestModule::set_class_entities_can_be_created( +// Origin::signed(ADMIN_ACCOUNT), +// Some(0), +// class_id, +// false +// )); +// let class = TestModule::class_by_id(class_id); +// assert_eq!(class.get_permissions_ref().entity_creation_blocked, false); + +// // non-admins +// assert_err!( +// TestModule::set_class_entities_can_be_created( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// class_id, +// true +// ), +// "NotInAdminsSet" +// ); +// }) +// } + +// #[test] +// fn class_set_class_entity_permissions() { +// with_test_externalities(|| { +// const ADMIN_ACCOUNT: u64 = MEMBER_ONE_WITH_CREDENTIAL_ZERO; +// // create a class where all permission sets are empty +// let class_id = create_simple_class(class_minimal_with_admins(vec![0])); +// let class = TestModule::class_by_id(class_id); + +// assert!(class.get_permissions_ref().entity_permissions.update.is_empty()); + +// let entity_permissions1 = EntityPermissionss { +// update: CredentialSet::from(vec![1]), +// maintainer_has_all_permissions: true, +// }; + +// //root +// assert_ok!(TestModule::set_class_entity_permissions( +// Origin::ROOT, +// None, +// class_id, +// entity_permissions1.clone() +// )); +// let class = TestModule::class_by_id(class_id); +// assert_eq!( +// class.get_permissions_ref().entity_permissions, +// entity_permissions1 +// ); + +// let entity_permissions2 = EntityPermissionss { +// update: CredentialSet::from(vec![4]), +// maintainer_has_all_permissions: true, +// }; +// //admins +// assert_ok!(TestModule::set_class_entity_permissions( +// Origin::signed(ADMIN_ACCOUNT), +// Some(0), +// class_id, +// entity_permissions2.clone() +// )); +// let class = TestModule::class_by_id(class_id); +// assert_eq!( +// class.get_permissions_ref().entity_permissions, +// entity_permissions2 +// ); + +// // non admins +// assert_err!( +// TestModule::set_class_entity_permissions( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// class_id, +// entity_permissions2.clone() +// ), +// "NotInAdminsSet" +// ); +// }) +// } + +// #[test] +// fn class_set_class_reference_constraint() { +// with_test_externalities(|| { +// const ADMIN_ACCOUNT: u64 = MEMBER_ONE_WITH_CREDENTIAL_ZERO; +// // create a class where all permission sets are empty +// let class_id = create_simple_class(class_minimal_with_admins(vec![0])); +// let class = TestModule::class_by_id(class_id); + +// assert_eq!( +// class.get_permissions_ref().reference_constraint, +// Default::default() +// ); + +// let mut constraints_set = BTreeSet::new(); +// constraints_set.insert(PropertyOfClass { +// class_id: 1, +// property_index: 0, +// }); +// let reference_constraint1 = ReferenceConstraint::Restricted(constraints_set); + +// //root +// assert_ok!(TestModule::set_class_reference_constraint( +// Origin::ROOT, +// None, +// class_id, +// reference_constraint1.clone() +// )); +// let class = TestModule::class_by_id(class_id); +// assert_eq!( +// class.get_permissions_ref().reference_constraint, +// reference_constraint1 +// ); + +// let mut constraints_set = BTreeSet::new(); +// constraints_set.insert(PropertyOfClass { +// class_id: 2, +// property_index: 2, +// }); +// let reference_constraint2 = ReferenceConstraint::Restricted(constraints_set); + +// //admins +// assert_ok!(TestModule::set_class_reference_constraint( +// Origin::signed(ADMIN_ACCOUNT), +// Some(0), +// class_id, +// reference_constraint2.clone() +// )); +// let class = TestModule::class_by_id(class_id); +// assert_eq!( +// class.get_permissions_ref().reference_constraint, +// reference_constraint2 +// ); + +// // non admins +// assert_err!( +// TestModule::set_class_reference_constraint( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// Some(1), +// class_id, +// reference_constraint2.clone() +// ), +// "NotInAdminsSet" +// ); +// }) +// } + +// #[test] +// fn batch_transaction_simple() { +// with_test_externalities(|| { +// const CREDENTIAL_ONE: u64 = 1; + +// let new_class_id = create_simple_class(ClassPermissions { +// entity_creation_blocked: true, +// create_entities: vec![CREDENTIAL_ONE].into(), +// reference_constraint: ReferenceConstraint::NoConstraint, +// ..Default::default() +// }); + +// let new_properties = vec![Property { +// property_type: PropertyType::Reference(new_class_id), +// required: true, +// name: b"entity".to_vec(), +// description: b"another entity of same class".to_vec(), +// }]; + +// assert_ok!(TestModule::add_class_schema( +// Origin::ROOT, +// None, +// new_class_id, +// vec![], +// new_properties +// )); + +// let operations = vec![ +// Operation { +// with_credential: Some(CREDENTIAL_ONE), +// as_entity_maintainer: false, +// operation_type: OperationType::CreateEntity(CreateEntityOperation { +// class_id: new_class_id, +// }), +// }, +// Operation { +// with_credential: Some(CREDENTIAL_ONE), +// as_entity_maintainer: true, // in prior operation CREDENTIAL_ONE became the maintainer +// operation_type: OperationType::AddSchemaSupportToEntity( +// AddSchemaSupportToEntityOperation { +// entity_id: ParameterizedEntity::InternalEntityJustAdded(0), // index 0 (prior operation) +// schema_id: 0, +// parametrized_property_values: vec![ParametrizedClassPropertyValue { +// in_class_index: 0, +// value: ParametrizedPropertyValue::InternalEntityJustAdded(0), +// }], +// }, +// ), +// }, +// Operation { +// with_credential: Some(CREDENTIAL_ONE), +// as_entity_maintainer: false, +// operation_type: OperationType::CreateEntity(CreateEntityOperation { +// class_id: new_class_id, +// }), +// }, +// Operation { +// with_credential: Some(CREDENTIAL_ONE), +// as_entity_maintainer: true, // in prior operation CREDENTIAL_ONE became the maintainer +// operation_type: OperationType::UpdatePropertyValues( +// UpdatePropertyValuesOperation { +// entity_id: ParameterizedEntity::InternalEntityJustAdded(0), // index 0 (prior operation) +// new_parametrized_property_values: vec![ParametrizedClassPropertyValue { +// in_class_index: 0, +// value: ParametrizedPropertyValue::InternalEntityJustAdded(2), +// }], +// }, +// ), +// }, +// ]; + +// let entity_id = next_entity_id(); + +// assert_ok!(TestModule::transaction( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// operations +// )); + +// // two entities created +// assert!(>::exists(entity_id)); +// assert!(>::exists(entity_id + 1)); +// }) +// } + +// #[test] +// fn batch_transaction_vector_of_entities() { +// with_test_externalities(|| { +// const CREDENTIAL_ONE: u64 = 1; + +// let new_class_id = create_simple_class(ClassPermissions { +// entity_creation_blocked: true, +// create_entities: vec![CREDENTIAL_ONE].into(), +// reference_constraint: ReferenceConstraint::NoConstraint, +// ..Default::default() +// }); + +// let new_properties = vec![Property { +// property_type: PropertyType::ReferenceVec(10, new_class_id), +// required: true, +// name: b"entities".to_vec(), +// description: b"vector of entities of same class".to_vec(), +// }]; + +// assert_ok!(TestModule::add_class_schema( +// Origin::ROOT, +// None, +// new_class_id, +// vec![], +// new_properties +// )); + +// let operations = vec![ +// Operation { +// with_credential: Some(CREDENTIAL_ONE), +// as_entity_maintainer: false, +// operation_type: OperationType::CreateEntity(CreateEntityOperation { +// class_id: new_class_id, +// }), +// }, +// Operation { +// with_credential: Some(CREDENTIAL_ONE), +// as_entity_maintainer: false, +// operation_type: OperationType::CreateEntity(CreateEntityOperation { +// class_id: new_class_id, +// }), +// }, +// Operation { +// with_credential: Some(CREDENTIAL_ONE), +// as_entity_maintainer: false, +// operation_type: OperationType::CreateEntity(CreateEntityOperation { +// class_id: new_class_id, +// }), +// }, +// Operation { +// with_credential: Some(CREDENTIAL_ONE), +// as_entity_maintainer: true, // in prior operation CREDENTIAL_ONE became the maintainer +// operation_type: OperationType::AddSchemaSupportToEntity( +// AddSchemaSupportToEntityOperation { +// entity_id: ParameterizedEntity::InternalEntityJustAdded(0), +// schema_id: 0, +// parametrized_property_values: vec![ParametrizedClassPropertyValue { +// in_class_index: 0, +// value: ParametrizedPropertyValue::InternalEntityVec(vec![ +// ParameterizedEntity::InternalEntityJustAdded(1), +// ParameterizedEntity::InternalEntityJustAdded(2), +// ]), +// }], +// }, +// ), +// }, +// ]; + +// let entity_id = next_entity_id(); + +// assert_ok!(TestModule::transaction( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ONE), +// operations +// )); + +// // three entities created +// assert!(>::exists(entity_id)); +// assert!(>::exists(entity_id + 1)); +// assert!(>::exists(entity_id + 2)); + +// assert_eq!( +// TestModule::entity_by_id(entity_id), +// Entity::new( +// new_class_id, +// BTreeSet::from_iter(vec![SCHEMA_ID_0].into_iter()), +// prop_value( +// 0, +// PropertyValue::ReferenceVec( +// vec![entity_id + 1, entity_id + 2,], +// ::Nonce::default() +// ) +// ) +// ) +// ); +// }) +// } + +// Add class schema +// -------------------------------------- + +// #[test] +// fn cannot_add_schema_to_unknown_class() { +// with_test_externalities(|| { +// assert_err!( +// TestModule::append_class_schema(UNKNOWN_CLASS_ID, good_prop_ids(), good_props()), +// ERROR_CLASS_NOT_FOUND +// ); +// }) +// } + +// #[test] +// fn cannot_add_class_schema_when_no_props_passed() { +// with_test_externalities(|| { +// let class_id = create_simple_class_with_default_permissions(); +// assert_err!( +// TestModule::append_class_schema(class_id, vec![], vec![]), +// ERROR_NO_PROPS_IN_CLASS_SCHEMA +// ); +// }) +// } + +// #[test] +// fn cannot_add_class_schema_when_it_refers_unknown_prop_index_and_class_has_no_props() { +// with_test_externalities(|| { +// let class_id = create_simple_class_with_default_permissions(); +// assert_err!( +// TestModule::append_class_schema(class_id, vec![UNKNOWN_PROP_ID], vec![]), +// ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_PROP_INDEX +// ); +// }) +// } + +// #[test] +// fn cannot_add_class_schema_when_it_refers_unknown_prop_index() { +// with_test_externalities(|| { +// let class_id = create_simple_class_with_default_permissions(); + +// assert_eq!( +// TestModule::append_class_schema(class_id, vec![], good_props()), +// Ok(SCHEMA_ID_0) +// ); + +// // Try to add a new schema that is based on one valid prop ids +// // plus another prop id is unknown on this class. +// assert_err!( +// TestModule::append_class_schema(class_id, vec![0, UNKNOWN_PROP_ID], vec![]), +// ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_PROP_INDEX +// ); + +// // Verify that class props and schemas remain unchanged: +// assert_class_props(class_id, good_props()); +// assert_class_schemas(class_id, vec![good_prop_ids()]); +// }) +// } + +// #[test] +// fn cannot_add_class_schema_when_it_refers_unknown_internal_id() { +// with_test_externalities(|| { +// let class_id = create_simple_class_with_default_permissions(); +// let bad_internal_prop = new_reference_class_prop(UNKNOWN_CLASS_ID); + +// assert_err!( +// TestModule::append_class_schema( +// class_id, +// vec![], +// vec![good_prop_bool(), bad_internal_prop] +// ), +// ERROR_CLASS_SCHEMA_REFERS_UNKNOWN_CLASS +// ); +// }) +// } + +// #[test] +// fn should_add_class_schema_with_internal_class_prop() { +// with_test_externalities(|| { +// let class_id = create_simple_class_with_default_permissions(); +// let internal_class_prop = new_reference_class_prop(class_id); + +// // Add first schema with new props. +// // No other props on the class at this time. +// assert_eq!( +// TestModule::append_class_schema(class_id, vec![], vec![internal_class_prop.clone()]), +// Ok(SCHEMA_ID_0) +// ); + +// assert_class_props(class_id, vec![internal_class_prop]); +// assert_class_schemas(class_id, vec![vec![SCHEMA_ID_0]]); +// }) +// } + +// #[test] +// fn should_add_class_schema_when_only_new_props_passed() { +// with_test_externalities(|| { +// let class_id = create_simple_class_with_default_permissions(); + +// // Add first schema with new props. +// // No other props on the class at this time. +// assert_eq!( +// TestModule::append_class_schema(class_id, vec![], good_props()), +// Ok(SCHEMA_ID_0) +// ); + +// assert_class_props(class_id, good_props()); +// assert_class_schemas(class_id, vec![good_prop_ids()]); +// }) +// } + +// #[test] +// fn should_add_class_schema_when_only_prop_ids_passed() { +// with_test_externalities(|| { +// let class_id = create_simple_class_with_default_permissions(); + +// // Add first schema with new props. +// // No other props on the class at this time. +// assert_eq!( +// TestModule::append_class_schema(class_id, vec![], good_props()), +// Ok(SCHEMA_ID_0) +// ); + +// // Add a new schema that is based solely on the props ids +// // of the previously added schema. +// assert_eq!( +// TestModule::append_class_schema(class_id, good_prop_ids(), vec![]), +// Ok(SCHEMA_ID_1) +// ); +// }) +// } + +// #[test] +// fn cannot_add_class_schema_when_new_props_have_duplicate_names() { +// with_test_externalities(|| { +// let class_id = create_simple_class_with_default_permissions(); + +// // Add first schema with new props. +// // No other props on the class at this time. +// assert_eq!( +// TestModule::append_class_schema(class_id, vec![], good_props()), +// Ok(SCHEMA_ID_0) +// ); + +// // Add a new schema with not unique property names: +// assert_err!( +// TestModule::append_class_schema(class_id, vec![], good_props()), +// ERROR_PROP_NAME_NOT_UNIQUE_IN_A_CLASS +// ); +// }) +// } + +// #[test] +// fn should_add_class_schema_when_both_prop_ids_and_new_props_passed() { +// with_test_externalities(|| { +// let class_id = create_simple_class_with_default_permissions(); + +// // Add first schema with new props. +// // No other props on the class at this time. +// assert_eq!( +// TestModule::append_class_schema( +// class_id, +// vec![], +// vec![good_prop_bool(), good_prop_u32()] +// ), +// Ok(SCHEMA_ID_0) +// ); + +// // Add a new schema that is based on some prop ids +// // added with previous schema plus some new props, +// // introduced by this new schema. +// assert_eq!( +// TestModule::append_class_schema(class_id, vec![1], vec![good_prop_text()]), +// Ok(SCHEMA_ID_1) +// ); + +// assert_class_props( +// class_id, +// vec![good_prop_bool(), good_prop_u32(), good_prop_text()], +// ); + +// assert_class_schemas(class_id, vec![vec![0, 1], vec![1, 2]]); +// }) +// } + +// Update class schema status +// -------------------------------------- + +// #[test] +// fn update_class_schema_status_success() { +// with_test_externalities(|| { +// let (class_id, schema_id) = create_class_with_schema(); + +// // Check given class schema status before update performed +// assert_eq!( +// TestModule::class_by_id(class_id).is_active_schema(schema_id), +// true +// ); + +// // Give members of GROUP_ZERO permission to add schemas +// let update_schema_set = CredentialSet::from(vec![0]); +// assert_ok!(TestModule::set_class_update_schemas_status_set( +// Origin::ROOT, +// None, +// class_id, +// update_schema_set +// )); + +// // Make class schema under given index inactive. +// assert_ok!(TestModule::update_class_schema_status( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ZERO), +// Some(0), +// class_id, +// schema_id, +// false +// )); + +// // Check given class schema status after update performed +// assert_eq!( +// TestModule::class_by_id(class_id).is_active_schema(schema_id), +// false +// ); +// }) +// } + +// #[test] +// fn update_class_schema_status_class_not_found() { +// with_test_externalities(|| { +// // attemt to update class schema of nonexistent class +// assert_err!( +// TestModule::update_class_schema_status( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ZERO), +// Some(0), +// UNKNOWN_CLASS_ID, +// UNKNOWN_SCHEMA_ID, +// false +// ), +// ERROR_CLASS_NOT_FOUND +// ); +// }) +// } + +// #[test] +// fn update_class_schema_status_not_in_update_class_schema_status_set() { +// with_test_externalities(|| { +// let (class_id, schema_id) = create_class_with_schema(); + +// // Check given class schema status before update performed +// assert_eq!( +// TestModule::class_by_id(class_id).is_active_schema(schema_id), +// true +// ); + +// // attemt to update class schema of nonexistent schema +// assert_err!( +// TestModule::update_class_schema_status( +// Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ZERO), +// Some(0), +// class_id, +// schema_id, +// false +// ), +// "NotInUpdateSchemasStatusSet" +// ); + +// // Check given class schema status after update performed +// assert_eq!( +// TestModule::class_by_id(class_id).is_active_schema(schema_id), +// true +// ); +// }) +// } + +// // #[test] +// // fn update_class_schema_status_schema_not_found() { +// // with_test_externalities(|| { +// // let class_id = create_simple_class_with_default_permissions(); + +// // // give members of GROUP_ZERO permission to update schemas +// // let update_schema_set = CredentialSet::from(vec![0]); +// // assert_ok!(TestModule::set_class_update_schemas_status_set( +// // Origin::ROOT, +// // None, +// // class_id, +// // update_schema_set +// // )); + +// // // attemt to update class schema of nonexistent class +// // assert_err!( +// // TestModule::update_class_schema_status( +// // Origin::signed(MEMBER_ONE_WITH_CREDENTIAL_ZERO), +// // Some(0), +// // class_id, +// // UNKNOWN_SCHEMA_ID, +// // false +// // ), +// // ERROR_UNKNOWN_CLASS_SCHEMA_ID +// // ); +// // }) +// // } + +// // Add schema support to entity +// // -------------------------------------- + +// #[test] +// fn cannot_add_schema_to_entity_when_entity_not_found() { +// with_test_externalities(|| { +// assert_entity_not_found(TestModule::add_entity_schema_support( +// UNKNOWN_ENTITY_ID, +// 1, +// BTreeMap::new(), +// )); +// }) +// } + +// #[test] +// fn cannot_add_schema_to_entity_when_schema_is_not_active() { +// with_test_externalities(|| { +// let (class_id, schema_id, entity_id) = create_class_with_schema_and_entity(); + +// // Firstly we make class schema under given index inactive. +// assert_ok!(TestModule::complete_class_schema_status_update( +// class_id, schema_id, false +// )); + +// // Secondly we try to add support for the same schema. +// assert_err!( +// TestModule::add_entity_schema_support(entity_id, schema_id, bool_prop_value()), +// ERROR_CLASS_SCHEMA_NOT_ACTIVE +// ); +// }) +// } + +// #[test] +// fn cannot_add_schema_to_entity_when_schema_already_added_to_entity() { +// with_test_externalities(|| { +// let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); + +// // Firstly we just add support for a valid class schema. +// assert_ok!(TestModule::add_entity_schema_support( +// entity_id, +// schema_id, +// bool_prop_value() +// )); + +// // Secondly we try to add support for the same schema. +// assert_err!( +// TestModule::add_entity_schema_support(entity_id, schema_id, BTreeMap::new()), +// ERROR_SCHEMA_ALREADY_ADDED_TO_ENTITY +// ); +// }) +// } + +// #[test] +// fn cannot_add_schema_to_entity_when_schema_id_is_unknown() { +// with_test_externalities(|| { +// let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); +// let unknown_schema_id = schema_id + 1; +// assert_err!( +// TestModule::add_entity_schema_support( +// entity_id, +// unknown_schema_id, +// prop_value(0, PropertyValue::Bool(false)) +// ), +// ERROR_UNKNOWN_CLASS_SCHEMA_ID +// ); +// }) +// } + +// #[test] +// fn cannot_add_schema_to_entity_when_prop_value_dont_match_type() { +// with_test_externalities(|| { +// let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); +// let mut prop_values = bool_prop_value(); +// prop_values.insert(PROP_ID_U32, PropertyValue::Bool(true)); +// assert_err!( +// TestModule::add_entity_schema_support(entity_id, schema_id, prop_values), +// ERROR_PROP_VALUE_DONT_MATCH_TYPE +// ); +// }) +// } + +// #[test] +// fn cannot_add_schema_to_entity_when_unknown_internal_entity_id() { +// with_test_externalities(|| { +// let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); +// let mut prop_values = bool_prop_value(); +// prop_values.insert( +// PROP_ID_REFERENCE, +// PropertyValue::Reference(UNKNOWN_ENTITY_ID), +// ); +// assert_err!( +// TestModule::add_entity_schema_support(entity_id, schema_id, prop_values), +// ERROR_ENTITY_NOT_FOUND +// ); +// }) +// } + +// #[test] +// fn cannot_add_schema_to_entity_when_missing_required_prop() { +// with_test_externalities(|| { +// let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); +// assert_err!( +// TestModule::add_entity_schema_support( +// entity_id, +// schema_id, +// prop_value(PROP_ID_U32, PropertyValue::Uint32(456)) +// ), +// ERROR_MISSING_REQUIRED_PROP +// ); +// }) +// } + +// #[test] +// fn should_add_schema_to_entity_when_some_optional_props_provided() { +// with_test_externalities(|| { +// let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); +// let mut prop_values = bool_prop_value(); +// prop_values.insert(PROP_ID_U32, PropertyValue::Uint32(123)); +// assert_ok!(TestModule::add_entity_schema_support( +// entity_id, +// schema_id, +// // Note that an optional internal prop is not provided here. +// prop_values.clone() +// )); + +// let entity = TestModule::entity_by_id(entity_id); +// assert_eq!( +// entity.supported_schemas, +// BTreeSet::from_iter(vec![SCHEMA_ID_0].into_iter()) +// ); +// prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Bool(false)); +// prop_values.insert(PROP_ID_U32_VEC, PropertyValue::Bool(false)); +// assert_eq!(entity.values, prop_values); +// }) +// } + +// // Update entity properties +// // -------------------------------------- + +// #[test] +// fn update_entity_props_successfully() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// let mut prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(true)); +// prop_values.insert(PROP_ID_U32, PropertyValue::Bool(false)); +// prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Bool(false)); +// prop_values.insert( +// PROP_ID_U32_VEC, +// PropertyValue::Uint32Vec(vec![123, 234, 44], ::Nonce::default()), +// ); +// assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); +// prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(false)); +// prop_values.insert(PROP_ID_U32, PropertyValue::Uint32(123)); +// prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Reference(entity_id)); +// prop_values.insert( +// PROP_ID_U32_VEC, +// PropertyValue::Uint32Vec(vec![123, 234, 44, 88, 43], ::Nonce::one()), +// ); +// assert_ok!(TestModule::complete_entity_property_values_update( +// entity_id, +// prop_values.clone() +// )); +// assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); +// }) +// } + +// #[test] +// fn cannot_update_entity_props_when_entity_not_found() { +// with_test_externalities(|| { +// assert_entity_not_found(TestModule::complete_entity_property_values_update( +// UNKNOWN_ENTITY_ID, +// BTreeMap::new(), +// )); +// }) +// } + +// #[test] +// fn cannot_update_entity_props_when_prop_value_dont_match_type() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_entity_property_values_update( +// entity_id, +// prop_value(PROP_ID_BOOL, PropertyValue::Uint32(1)) +// ), +// ERROR_PROP_VALUE_DONT_MATCH_TYPE +// ); +// }) +// } + +// #[test] +// fn cannot_update_entity_props_when_unknown_internal_entity_id() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_entity_property_values_update( +// entity_id, +// prop_value( +// PROP_ID_REFERENCE, +// PropertyValue::Reference(UNKNOWN_ENTITY_ID) +// ) +// ), +// ERROR_ENTITY_NOT_FOUND +// ); +// }) +// } + +// #[test] +// fn cannot_update_entity_props_when_unknown_entity_prop_id() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_entity_property_values_update( +// entity_id, +// prop_value(UNKNOWN_PROP_ID, PropertyValue::Bool(true)) +// ), +// ERROR_UNKNOWN_ENTITY_PROP_ID +// ); +// }) +// } + +// // Entity property vector cleaning +// // -------------------------------------- + +// #[test] +// fn complete_entity_property_vector_cleaning_successfully() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// let mut prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(true)); +// prop_values.insert(PROP_ID_U32, PropertyValue::Bool(false)); +// prop_values.insert( +// PROP_ID_U32_VEC, +// PropertyValue::Uint32Vec(vec![123, 234, 44], ::Nonce::default()), +// ); +// prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Bool(false)); + +// // Check property values runtime storage related to an entity before cleaning of entity property vector value under given schema id +// assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); + +// // Perform cleaning of entity property vector value under given schema id +// assert_ok!(TestModule::complete_entity_property_vector_cleaning( +// entity_id, +// PROP_ID_U32_VEC +// )); + +// // Update entity property values to compare with runtime storage entity value under given schema id +// prop_values.insert( +// PROP_ID_U32_VEC, +// PropertyValue::Uint32Vec(vec![], ::Nonce::one()), +// ); + +// // Check property values runtime storage related to a entity right after +// // cleaning entity property vector under given schema id +// assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); +// }) +// } + +// #[test] +// fn cannot_complete_entity_property_vector_cleaning_when_entity_not_found() { +// with_test_externalities(|| { +// assert_entity_not_found(TestModule::complete_entity_property_vector_cleaning( +// UNKNOWN_ENTITY_ID, +// PROP_ID_U32_VEC, +// )); +// }) +// } + +// #[test] +// fn cannot_complete_entity_property_vector_cleaning_when_unknown_entity_prop_id() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_entity_property_vector_cleaning(entity_id, UNKNOWN_PROP_ID), +// ERROR_UNKNOWN_ENTITY_PROP_ID +// ); +// }) +// } + +// #[test] +// fn cannot_complete_entity_property_vector_cleaning_when_entity_prop_id_is_not_a_vector() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_entity_property_vector_cleaning(entity_id, PROP_ID_U32), +// ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR +// ); +// }) +// } + +// // Remove at entity property vector +// // -------------------------------------- + +// fn complete_remove_at_entity_property_vector() -> ::EntityId { +// let entity_id = create_entity_with_schema_support(); +// let mut prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(true)); +// prop_values.insert(PROP_ID_U32, PropertyValue::Bool(false)); +// prop_values.insert( +// PROP_ID_U32_VEC, +// PropertyValue::Uint32Vec(vec![123, 234, 44], ::Nonce::default()), +// ); +// prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Bool(false)); + +// // Check property values runtime storage related to an entity before removing at given index of entity property vector value +// assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); + +// // Perform removing at given index of entity property vector value +// assert_ok!(TestModule::complete_remove_at_entity_property_vector( +// entity_id, +// PROP_ID_U32_VEC, +// VALID_PROPERTY_VEC_INDEX, +// ZERO_NONCE +// )); + +// // Update entity property values to compare with runtime storage entity value under given schema id +// prop_values.insert( +// PROP_ID_U32_VEC, +// PropertyValue::Uint32Vec(vec![234, 44], ::Nonce::one()), +// ); + +// // Check property values runtime storage related to a entity right after +// // removing at given index of entity property vector value +// assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); +// entity_id +// } + +// #[test] +// fn complete_remove_at_entity_property_vector_successfully() { +// with_test_externalities(|| { +// let entity_id = complete_remove_at_entity_property_vector(); +// // Perform second removal at given index of entity property vector value with new nonce +// assert_ok!(TestModule::complete_remove_at_entity_property_vector( +// entity_id, +// PROP_ID_U32_VEC, +// VALID_PROPERTY_VEC_INDEX, +// FIRST_NONCE +// )); +// }) +// } + +// #[test] +// fn cannot_complete_remove_at_entity_property_vector_when_entity_not_found() { +// with_test_externalities(|| { +// assert_entity_not_found(TestModule::complete_remove_at_entity_property_vector( +// UNKNOWN_ENTITY_ID, +// PROP_ID_U32_VEC, +// VALID_PROPERTY_VEC_INDEX, +// ZERO_NONCE, +// )); +// }) +// } + +// #[test] +// fn cannot_complete_remove_at_entity_property_vector_when_unknown_entity_prop_id() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_remove_at_entity_property_vector( +// entity_id, +// UNKNOWN_PROP_ID, +// VALID_PROPERTY_VEC_INDEX, +// ZERO_NONCE +// ), +// ERROR_UNKNOWN_ENTITY_PROP_ID +// ); +// }) +// } + +// #[test] +// fn cannot_complete_remove_at_entity_property_vector_when_entity_prop_vector_index_out_of_range() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_remove_at_entity_property_vector( +// entity_id, +// PROP_ID_U32, +// INVALID_PROPERTY_VEC_INDEX, +// ZERO_NONCE +// ), +// ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR +// ); +// }) +// } + +// #[test] +// fn cannot_complete_remove_at_entity_property_vector_when_entity_prop_id_is_not_a_vector() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_remove_at_entity_property_vector( +// entity_id, +// PROP_ID_U32, +// VALID_PROPERTY_VEC_INDEX, +// ZERO_NONCE +// ), +// ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR +// ); +// }) +// } + +// #[test] +// fn cannot_complete_remove_at_entity_property_vector_when_already_updated() { +// with_test_externalities(|| { +// let entity_id = complete_remove_at_entity_property_vector(); +// assert_err!( +// TestModule::complete_remove_at_entity_property_vector( +// entity_id, +// PROP_ID_U32_VEC, +// VALID_PROPERTY_VEC_INDEX, +// SECOND_NONCE +// ), +// ERROR_PROP_VALUE_VEC_NONCES_DOES_NOT_MATCH +// ); +// }) +// } + +// #[test] +// fn complete_insert_at_entity_property_vector_successfully() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// let mut prop_values = prop_value(PROP_ID_BOOL, PropertyValue::Bool(true)); +// prop_values.insert(PROP_ID_U32, PropertyValue::Bool(false)); +// prop_values.insert( +// PROP_ID_U32_VEC, +// PropertyValue::Uint32Vec(vec![123, 234, 44], ::Nonce::default()), +// ); +// prop_values.insert(PROP_ID_REFERENCE, PropertyValue::Bool(false)); + +// // Check property values runtime storage related to an entity before inserting at given index of entity property vector value +// assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); + +// // Perform inserting at given index of entity property vector value +// assert_ok!(TestModule::complete_insert_at_entity_property_vector( +// entity_id, +// PROP_ID_U32_VEC, +// VALID_PROPERTY_VEC_INDEX, +// PropertyValue::Uint32(33), +// ZERO_NONCE +// )); + +// // Perform second inserting at given index of entity property vector value with new nonce +// assert_ok!(TestModule::complete_insert_at_entity_property_vector( +// entity_id, +// PROP_ID_U32_VEC, +// VALID_PROPERTY_VEC_INDEX, +// PropertyValue::Uint32(55), +// FIRST_NONCE +// )); + +// // Update entity property values to compare with runtime storage entity value under given schema id +// prop_values.insert( +// PROP_ID_U32_VEC, +// PropertyValue::Uint32Vec(vec![55, 33, 123, 234, 44], 2_u32.into()), +// ); + +// // Check property values runtime storage related to a entity right after +// // inserting at given index of entity property vector value +// assert_eq!(TestModule::entity_by_id(entity_id).values, prop_values); +// }) +// } + +// #[test] +// fn cannot_complete_insert_at_entity_property_vector_when_entity_not_found() { +// with_test_externalities(|| { +// assert_entity_not_found(TestModule::complete_insert_at_entity_property_vector( +// UNKNOWN_ENTITY_ID, +// PROP_ID_U32_VEC, +// VALID_PROPERTY_VEC_INDEX, +// PropertyValue::Uint32(33), +// ZERO_NONCE, +// )); +// }) +// } + +// #[test] +// fn cannot_complete_insert_at_entity_property_vector_when_unknown_entity_prop_id() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_insert_at_entity_property_vector( +// entity_id, +// UNKNOWN_PROP_ID, +// VALID_PROPERTY_VEC_INDEX, +// PropertyValue::Uint32(33), +// ZERO_NONCE +// ), +// ERROR_UNKNOWN_ENTITY_PROP_ID +// ); +// }) +// } + +// #[test] +// fn cannot_complete_insert_at_entity_property_vector_when_entity_prop_id_is_not_a_vector() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_insert_at_entity_property_vector( +// entity_id, +// PROP_ID_U32, +// VALID_PROPERTY_VEC_INDEX, +// PropertyValue::Uint32(17), +// ZERO_NONCE +// ), +// ERROR_PROP_VALUE_UNDER_GIVEN_INDEX_IS_NOT_A_VECTOR +// ); +// }) +// } + +// #[test] +// fn cannot_complete_insert_at_entity_when_entity_prop_value_vector_index_out_of_range() { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_insert_at_entity_property_vector( +// entity_id, +// PROP_ID_U32_VEC, +// INVALID_PROPERTY_VEC_INDEX, +// PropertyValue::Uint32(33), +// ZERO_NONCE +// ), +// ERROR_ENTITY_PROP_VALUE_VECTOR_INDEX_IS_OUT_OF_RANGE +// ); +// }) +// } + +// #[test] +// fn cannot_complete_insert_at_entity_when_property_type_does_not_match_internal_entity_vector_type() +// { +// with_test_externalities(|| { +// let entity_id = create_entity_with_schema_support(); +// assert_err!( +// TestModule::complete_insert_at_entity_property_vector( +// entity_id, +// PROP_ID_U32_VEC, +// VALID_PROPERTY_VEC_INDEX, +// PropertyValue::Uint16(33), +// ZERO_NONCE +// ), +// ERROR_PROP_VALUE_TYPE_DOESNT_MATCH_INTERNAL_ENTITY_VECTOR_TYPE +// ); +// }) +// } + +// #[test] +// fn cannot_complete_insert_at_entity_property_vector_when_entity_prop_value_vector_is_too_long() { +// with_test_externalities(|| { +// let (_, schema_id, entity_id) = create_class_with_schema_and_entity(); +// let mut property_values = BTreeMap::new(); +// property_values.insert(PROP_ID_BOOL, PropertyValue::Bool(true)); +// property_values.insert( +// PROP_ID_U32_VEC, +// PropertyValue::Uint32Vec( +// vec![5; PROP_ID_U32_VEC_MAX_LEN as usize], +// ::Nonce::default(), +// ), +// ); +// assert_ok!(TestModule::add_entity_schema_support( +// entity_id, +// schema_id, +// property_values +// )); +// assert_err!( +// TestModule::complete_insert_at_entity_property_vector( +// entity_id, +// PROP_ID_U32_VEC, +// VALID_PROPERTY_VEC_INDEX, +// PropertyValue::Uint32(33), +// ZERO_NONCE +// ), +// ERROR_ENTITY_PROP_VALUE_VECTOR_IS_TOO_LONG +// ); +// }) +// } + +// #[test] +// fn cannot_complete_insert_at_entity_property_vector_when_nonce_does_not_match() { +// with_test_externalities(|| { +// let entity_id = complete_remove_at_entity_property_vector(); +// assert_err!( +// TestModule::complete_insert_at_entity_property_vector( +// entity_id, +// PROP_ID_U32_VEC, +// VALID_PROPERTY_VEC_INDEX, +// PropertyValue::Uint32(33), +// SECOND_NONCE +// ), +// ERROR_PROP_VALUE_VEC_NONCES_DOES_NOT_MATCH +// ); +// }) +// } + +// fn create_entity_with_prop_value_referencing_another_entity( +// ) -> (::EntityId, ::EntityId) { +// let class_id = create_simple_class_with_default_permissions(); +// let schema_id = TestModule::append_class_schema( +// class_id, +// vec![], +// vec![ +// good_prop_bool().required(), +// new_reference_class_prop_vec(class_id), +// ], +// ) +// .expect("This should not happen"); +// let entity_id = create_entity_of_class(class_id); +// let entity_id_2 = create_entity_of_class(class_id); +// let mut property_values = BTreeMap::new(); +// property_values.insert(PROP_ID_BOOL, PropertyValue::Bool(true)); +// property_values.insert( +// PROP_ID_REFERENCE_VEC, +// PropertyValue::ReferenceVec(vec![entity_id_2], ::Nonce::default()), +// ); +// assert_ok!(TestModule::add_entity_schema_support( +// entity_id, +// schema_id, +// property_values +// )); +// (entity_id, entity_id_2) +// } + +// #[test] +// fn cannot_complete_insert_at_entity_property_vector_when_unknown_internal_entity_id() { +// with_test_externalities(|| { +// let (entity_id, _) = create_entity_with_prop_value_referencing_another_entity(); +// assert_err!( +// TestModule::complete_insert_at_entity_property_vector( +// entity_id, +// PROP_ID_REFERENCE_VEC, +// VALID_PROPERTY_VEC_INDEX, +// PropertyValue::Reference(UNKNOWN_ENTITY_ID), +// ZERO_NONCE +// ), +// ERROR_ENTITY_NOT_FOUND +// ); +// }) +// } + +// Remove entity +// -------------------------------------- + +// #[test] +// fn remove_entity_successfully() { +// with_test_externalities(|| { +// let (_, _, entity_id) = create_class_with_schema_and_entity(); +// assert_ok!(TestModule::remove_entity(Origin::ROOT, None, entity_id)); +// // Ensure entity related storage was cleared successfully. +// assert_eq!( +// TestModule::entity_by_id(entity_id), +// Entity::::default() +// ); +// assert_eq!(TestModule::entity_maintainer_by_entity_id(entity_id), None); +// }) +// } + +// #[test] +// fn remove_entity_not_found() { +// with_test_externalities(|| { +// assert_err!( +// TestModule::remove_entity(Origin::ROOT, None, UNKNOWN_ENTITY_ID), +// ERROR_ENTITY_NOT_FOUND +// ); +// }) +// } + +// #[test] +// fn remove_entity_reference_counter_does_not_equal_zero() { +// with_test_externalities(|| { +// let (_, entity_by_id_2) = create_entity_with_prop_value_referencing_another_entity(); +// assert_err!( +// TestModule::remove_entity(Origin::ROOT, None, entity_by_id_2), +// ERROR_ENTITY_RC_DOES_NOT_EQUAL_TO_ZERO +// ); +// }) +// } + +// TODO test text max len + +// TODO test vec max len + +// Delete entity +// -------------------------------------- + +// #[test] +// fn delete_entity_successfully() { +// with_test_externalities(|| { +// let entity_id = create_entity(); +// assert_ok!( +// TestModule::delete_entity(entity_id), +// () +// ); +// }) +// } + +// #[test] +// fn cannot_delete_entity_when_entity_not_found() { +// with_test_externalities(|| { +// assert_entity_not_found( +// TestModule::delete_entity(UNKNOWN_ENTITY_ID) +// ); +// }) +// } + +// #[test] +// fn cannot_delete_already_deleted_entity() { +// with_test_externalities(|| { +// let entity_id = create_entity(); +// let _ok = TestModule::delete_entity(entity_id); +// assert_err!( +// TestModule::delete_entity(entity_id), +// ERROR_ENTITY_ALREADY_DELETED +// ); +// }) +// }