From 74056ef52d6e5a49dd47489d804b5d87b8dbf6cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BBuk?= Date: Thu, 20 Jun 2024 12:50:15 +0200 Subject: [PATCH 1/3] BLOCKCHAIN-463 - Judiciary Phase 1 --- .github/workflows/check-formatting.yml | 6 +- .github/workflows/test-contracts.yml | 41 ++ Cargo.lock | 622 ++++++++++++++++-- Cargo.toml | 2 + contracts/Cargo.toml | 3 +- contracts/msig_court/.gitignore | 9 + contracts/msig_court/Cargo.toml | 24 + contracts/msig_court/lib.rs | 538 +++++++++++++++ contracts/msig_court/mock.rs | 21 + liberland-extension/ink/Cargo.toml | 19 + liberland-extension/ink/src/lib.rs | 36 + liberland-extension/ink/src/types.rs | 31 + liberland-extension/runtime/Cargo.toml | 35 + liberland-extension/runtime/src/lib.rs | 70 ++ substrate/bin/node/runtime/Cargo.toml | 2 + substrate/bin/node/runtime/src/impls.rs | 11 + substrate/bin/node/runtime/src/lib.rs | 15 +- substrate/frame/democracy/src/tests.rs | 1 + substrate/frame/elections-phragmen/src/lib.rs | 1 + .../frame/liberland-legislation/src/mock.rs | 1 + substrate/frame/llm/README.md | 7 +- substrate/frame/llm/src/benchmarking.rs | 29 +- substrate/frame/llm/src/lib.rs | 220 +++---- substrate/frame/llm/src/mock.rs | 2 + substrate/frame/llm/src/tests.rs | 110 +++- substrate/frame/llm/src/weights.rs | 172 +++-- substrate/frame/staking/src/mock.rs | 1 + 27 files changed, 1766 insertions(+), 263 deletions(-) create mode 100644 .github/workflows/test-contracts.yml create mode 100755 contracts/msig_court/.gitignore create mode 100644 contracts/msig_court/Cargo.toml create mode 100644 contracts/msig_court/lib.rs create mode 100644 contracts/msig_court/mock.rs create mode 100644 liberland-extension/ink/Cargo.toml create mode 100644 liberland-extension/ink/src/lib.rs create mode 100644 liberland-extension/ink/src/types.rs create mode 100644 liberland-extension/runtime/Cargo.toml create mode 100644 liberland-extension/runtime/src/lib.rs diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml index 94acb1ad0b..f510870935 100644 --- a/.github/workflows/check-formatting.yml +++ b/.github/workflows/check-formatting.yml @@ -48,4 +48,8 @@ jobs: key: ${{ runner.os }}-cargo-rust-${{ hashFiles('**/Cargo.lock') }} - name: Format code with rustfmt - run: cargo fmt --check -p pallet-liberland-initializer -p pallet-liberland-legislation -p liberland-traits -p pallet-llm -p pallet-office -p pallet-registry -p pallet-custom-account -p pallet-contracts-registry \ No newline at end of file + run: cargo fmt --check -p pallet-liberland-initializer -p pallet-liberland-legislation -p liberland-traits -p pallet-llm -p pallet-office -p pallet-registry -p pallet-custom-account -p pallet-contracts-registry -p liberland-extension -p liberland-extension-runtime + + - name: Format code with rustfmt + working-directory: contracts/ + run: cargo fmt --check \ No newline at end of file diff --git a/.github/workflows/test-contracts.yml b/.github/workflows/test-contracts.yml new file mode 100644 index 0000000000..65b4cc01fb --- /dev/null +++ b/.github/workflows/test-contracts.yml @@ -0,0 +1,41 @@ +name: Run contracts tests + +on: + pull_request: + workflow_dispatch: + push: + branches: + - develop + - main + +jobs: + test: + runs-on: ubuntu-latest + env: + RUSTFLAGS: "-C debug-assertions=y" + RUST_BACKTRACE: 1 + steps: + - name: Cancel previous runs + uses: styfle/cancel-workflow-action@0.9.1 + with: + access_token: ${{ github.token }} + + - name: Install Rust toolchain + run: | + rustup set profile minimal + rustup update --no-self-update 1.78.0 + rustup component add --toolchain 1.78.0 rustfmt rust-src + rustup target add wasm32-unknown-unknown + rustup default 1.78.0 + + - name: Checkout + uses: actions/checkout@v4 + + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + workspaces: "contracts -> contracts/target" + + - name: Run tests + working-directory: contracts + run: cargo test \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 93a4426812..a806d90339 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df752953c49ce90719c7bf1fc587bc8227aed04732ea0c0f85e5397d7fdbd1a1" dependencies = [ "include_dir", - "itertools", + "itertools 0.10.5", "proc-macro-error", "proc-macro2", "quote", @@ -317,7 +317,7 @@ dependencies = [ "ark-std", "derivative", "hashbrown 0.13.2", - "itertools", + "itertools 0.10.5", "num-traits", "zeroize", ] @@ -346,7 +346,7 @@ dependencies = [ "ark-std", "derivative", "digest 0.10.7", - "itertools", + "itertools 0.10.5", "num-bigint", "num-traits", "paste", @@ -470,6 +470,12 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b1c5a481ec30a5abd8dfbd94ab5cf1bb4e9a66be7f1b3b322f2f1170c200fd" +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + [[package]] name = "arrayref" version = "0.3.7" @@ -529,7 +535,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -541,7 +547,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -1448,6 +1454,26 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "const_env" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9e4f72c6e3398ca6da372abd9affd8f89781fe728869bbf986206e9af9627e" +dependencies = [ + "const_env_impl", +] + +[[package]] +name = "const_env_impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4f51209740b5e1589e702b3044cdd4562cef41b6da404904192ffffb852d62" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "constant_time_eq" version = "0.2.6" @@ -1594,7 +1620,7 @@ dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "itertools", + "itertools 0.10.5", "log", "smallvec", "wasmparser", @@ -1638,7 +1664,7 @@ dependencies = [ "clap 3.2.25", "criterion-plot", "futures", - "itertools", + "itertools 0.10.5", "lazy_static", "num-traits", "oorandom", @@ -1660,7 +1686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -1813,16 +1839,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", "rustc_version", "subtle", "zeroize", @@ -2306,7 +2331,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.3", "ed25519", "rand_core 0.6.4", "serde", @@ -2612,9 +2637,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "file-per-thread-logger" @@ -2761,7 +2786,7 @@ dependencies = [ "frame-system", "gethostname", "handlebars", - "itertools", + "itertools 0.10.5", "lazy_static", "linked-hash-map", "log", @@ -2799,7 +2824,7 @@ name = "frame-election-provider-solution-type" version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-v1.1.0#f60318f68687e601c47de5ad5ca88e2c3f8139a7" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.48", @@ -2924,7 +2949,7 @@ dependencies = [ "derive-syn-parse", "expander", "frame-support-procedural-tools", - "itertools", + "itertools 0.10.5", "macro_magic", "proc-macro-warning", "proc-macro2", @@ -2938,7 +2963,7 @@ version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-v1.1.0#f60318f68687e601c47de5ad5ca88e2c3f8139a7" dependencies = [ "frame-support-procedural-tools-derive", - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.48", @@ -3226,6 +3251,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand 0.8.5", + "rand_core 0.6.4", +] + [[package]] name = "ghash" version = "0.4.4" @@ -3755,6 +3790,206 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "ink" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4a862aedbfda93175ddf75c9aaa2ae4c4b39ee5cee06c16d50bccce05bf5c7" +dependencies = [ + "derive_more", + "ink_env", + "ink_macro", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "ink_storage", + "pallet-contracts-uapi-next", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ink_allocator" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cee56055bac6d928d425e944c5f3b69baa33c9635822fd1c00cd4afc70fde3e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ink_codegen" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a1f8473fa09e0f9b6f3cb3f8d18c07c14ebf9ea1f7cdfee270f009d45ee8e9" +dependencies = [ + "blake2", + "derive_more", + "either", + "heck", + "impl-serde", + "ink_ir", + "ink_primitives", + "itertools 0.12.1", + "parity-scale-codec", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.48", +] + +[[package]] +name = "ink_engine" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f357e2e867f4e222ffc4015a6e61d1073548de89f70a4e36a8b0385562777fa" +dependencies = [ + "blake2", + "derive_more", + "ink_primitives", + "pallet-contracts-uapi-next", + "parity-scale-codec", + "secp256k1 0.28.2", + "sha2 0.10.7", + "sha3", +] + +[[package]] +name = "ink_env" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cec50b7e4f8406aab25801b015d3802a52d76cfbe48ce11cfb4200fa88e296" +dependencies = [ + "blake2", + "cfg-if", + "const_env", + "derive_more", + "ink_allocator", + "ink_engine", + "ink_prelude", + "ink_primitives", + "ink_storage_traits", + "num-traits", + "pallet-contracts-uapi-next", + "parity-scale-codec", + "paste", + "rlibc", + "scale-decode", + "scale-encode", + "scale-info", + "schnorrkel 0.11.4", + "secp256k1 0.28.2", + "sha2 0.10.7", + "sha3", + "static_assertions", +] + +[[package]] +name = "ink_ir" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1ad2975551c4ed800af971289ed6d2c68ac41ffc03a42010b3e01d7360dfb2" +dependencies = [ + "blake2", + "either", + "impl-serde", + "ink_prelude", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "ink_macro" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee1a546f37eae3b3cd223832d31702033c5369dcfa3405899587c110a7908d3" +dependencies = [ + "ink_codegen", + "ink_ir", + "ink_primitives", + "parity-scale-codec", + "proc-macro2", + "quote", + "syn 2.0.48", + "synstructure 0.13.1", +] + +[[package]] +name = "ink_metadata" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a98fcc0ff9292ff68c7ee7b84c93533c9ff13859ec3b148faa822e2da9954fe6" +dependencies = [ + "derive_more", + "impl-serde", + "ink_prelude", + "ink_primitives", + "linkme", + "parity-scale-codec", + "scale-info", + "schemars", + "serde", +] + +[[package]] +name = "ink_prelude" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea1734d058c80aa72e59c8ae75624fd8a51791efba21469f273156c0f4cad5c9" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ink_primitives" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec35ef7f45e67a53b6142d7e7f18e6d9292d76c3a2a1da14cf8423e481813d" +dependencies = [ + "derive_more", + "ink_prelude", + "parity-scale-codec", + "scale-decode", + "scale-encode", + "scale-info", + "xxhash-rust", +] + +[[package]] +name = "ink_storage" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbdb04cad74df858c05bc9cb6f30bbf12da33c3e2cb7ca211749c001fa761aa9" +dependencies = [ + "array-init", + "cfg-if", + "derive_more", + "ink_env", + "ink_metadata", + "ink_prelude", + "ink_primitives", + "ink_storage_traits", + "pallet-contracts-uapi-next", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ink_storage_traits" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ce49e3d2935fc1ec3e73117119712b187d3123339f6a31624e92f75fa2293d" +dependencies = [ + "ink_metadata", + "ink_prelude", + "ink_primitives", + "parity-scale-codec", + "scale-info", +] + [[package]] name = "inout" version = "0.1.3" @@ -3856,6 +4091,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -3970,7 +4214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44e8ab85614a08792b9bff6c8feee23be78c98d0182d4c622c05256ab553892a" dependencies = [ "heck", - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -4063,6 +4307,7 @@ dependencies = [ "frame-try-runtime", "leaf-provider", "liberland-bridge-provider", + "liberland-extension-runtime", "liberland-traits", "log", "multisig-verifier", @@ -4230,6 +4475,28 @@ dependencies = [ "sp-std", ] +[[package]] +name = "liberland-extension" +version = "1.0.0" +dependencies = [ + "ink", +] + +[[package]] +name = "liberland-extension-runtime" +version = "1.0.0" +dependencies = [ + "frame-support", + "frame-system", + "log", + "pallet-contracts", + "pallet-llm", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std", +] + [[package]] name = "liberland-traits" version = "0.1.0" @@ -4778,6 +5045,26 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "linkme" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb76662d78edc9f9bf56360d6919bdacc8b7761227727e5082f128eeb90bbf5" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dccda732e04fa3baf2e17cf835bfe2601c7c2edafd64417c627dabae3a8cda" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "linregress" version = "0.5.2" @@ -5161,12 +5448,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro-error", "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -5920,6 +6207,17 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-contracts-uapi-next" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd549c16296ea5b2eb7c65c56aba548b286c1be4d7675b424ff6ccb8319c97a9" +dependencies = [ + "bitflags 1.3.2", + "paste", + "polkavm-derive", +] + [[package]] name = "pallet-custom-account" version = "0.1.0" @@ -6432,7 +6730,7 @@ name = "pallet-staking-reward-curve" version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-v1.1.0#f60318f68687e601c47de5ad5ca88e2c3f8139a7" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.48", @@ -6607,9 +6905,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.6.4" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8e946cc0cc711189c0b0249fb8b599cbeeab9784d83c415719368bb8d4ac64" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec 0.7.4", "bitvec", @@ -6622,11 +6920,11 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.4" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a296c3079b5fefbc499e1de58dc26c09b1b9a5952d26694ee89f04a43ebbb3e" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 1.0.109", @@ -6910,6 +7208,34 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "polkavm-common" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b4e215c80fe876147f3d58158d5dfeae7dabdd6047e175af77095b78d0035c" + +[[package]] +name = "polkavm-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8" +dependencies = [ + "polkavm-derive-impl", + "syn 2.0.48", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc8211b3365bbafb2fb32057d68b0e1ca55d079f5cf6f9da9b98079b94b3987d" +dependencies = [ + "polkavm-common", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "polling" version = "2.8.0" @@ -6981,7 +7307,7 @@ checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", "float-cmp", - "itertools", + "itertools 0.10.5", "normalize-line-endings", "predicates-core", "regex", @@ -6995,7 +7321,7 @@ checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" dependencies = [ "anstyle", "difflib", - "itertools", + "itertools 0.10.5", "predicates-core", ] @@ -7057,7 +7383,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.14", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", ] [[package]] @@ -7165,7 +7500,7 @@ checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", "heck", - "itertools", + "itertools 0.10.5", "lazy_static", "log", "multimap", @@ -7186,7 +7521,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 1.0.109", @@ -7581,6 +7916,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rlibc" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc874b127765f014d792f16763a81245ab80500e2ad921ed4ee9e82481ee08fe" + [[package]] name = "rlp" version = "0.5.2" @@ -7959,7 +8300,7 @@ name = "sc-chain-spec-derive" version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-v1.1.0#f60318f68687e601c47de5ad5ca88e2c3f8139a7" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.48", @@ -8839,7 +9180,7 @@ name = "sc-tracing-proc-macro" version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-v1.1.0#f60318f68687e601c47de5ad5ca88e2c3f8139a7" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.48", @@ -8902,6 +9243,69 @@ dependencies = [ "sp-arithmetic", ] +[[package]] +name = "scale-bits" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "036575c29af9b6e4866ffb7fa055dbf623fe7a9cc159b33786de6013a6969d89" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "scale-decode" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7caaf753f8ed1ab4752c6afb20174f03598c664724e0e32628e161c21000ff76" +dependencies = [ + "derive_more", + "parity-scale-codec", + "scale-bits", + "scale-decode-derive", + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-decode-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3475108a1b62c7efd1b5c65974f30109a598b2f45f23c9ae030acb9686966db" +dependencies = [ + "darling", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scale-encode" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d70cb4b29360105483fac1ed567ff95d65224a14dd275b6303ed0a654c78de5" +dependencies = [ + "derive_more", + "parity-scale-codec", + "scale-encode-derive", + "scale-info", + "smallvec", +] + +[[package]] +name = "scale-encode-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "995491f110efdc6bea96d6a746140e32bfceb4ea47510750a5467295a4707a25" +dependencies = [ + "darling", + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "scale-info" version = "2.9.0" @@ -8913,6 +9317,7 @@ dependencies = [ "derive_more", "parity-scale-codec", "scale-info-derive", + "schemars", "serde", ] @@ -8922,7 +9327,7 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "912e55f6d20e0e80d63733872b40e1227c0bce1e1ab81ba67d696339bfd7fd29" dependencies = [ - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 1.0.109", @@ -8937,6 +9342,30 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.48", +] + [[package]] name = "schnellru" version = "0.2.1" @@ -8966,6 +9395,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "schnorrkel" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" +dependencies = [ + "aead 0.5.2", + "arrayref", + "arrayvec 0.7.4", + "curve25519-dalek 4.1.3", + "getrandom_or_panic", + "merlin 3.0.0", + "rand_core 0.6.4", + "serde_bytes", + "sha2 0.10.7", + "subtle", + "zeroize", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -9044,7 +9492,16 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ - "secp256k1-sys", + "secp256k1-sys 0.6.1", +] + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "secp256k1-sys 0.9.2", ] [[package]] @@ -9056,6 +9513,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -9114,18 +9580,38 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", @@ -9134,9 +9620,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -9321,7 +9807,7 @@ dependencies = [ "aes-gcm 0.9.4", "blake2", "chacha20poly1305", - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "ring 0.16.20", "rustc_version", @@ -9395,7 +9881,7 @@ dependencies = [ "Inflector", "blake2", "expander", - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.48", @@ -9599,8 +10085,8 @@ dependencies = [ "rand 0.8.5", "regex", "scale-info", - "schnorrkel", - "secp256k1", + "schnorrkel 0.9.1", + "secp256k1 0.24.3", "secrecy", "serde", "sp-core-hashing", @@ -9706,7 +10192,7 @@ dependencies = [ "log", "parity-scale-codec", "rustversion", - "secp256k1", + "secp256k1 0.24.3", "sp-core", "sp-externalities", "sp-keystore", @@ -9870,7 +10356,7 @@ version = "11.0.0" source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-v1.1.0#f60318f68687e601c47de5ad5ca88e2c3f8139a7" dependencies = [ "Inflector", - "proc-macro-crate", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", "syn 2.0.48", @@ -9932,7 +10418,7 @@ version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-v1.1.0#f60318f68687e601c47de5ad5ca88e2c3f8139a7" dependencies = [ "aes-gcm 0.10.2", - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.3", "ed25519-dalek", "hkdf", "parity-scale-codec", @@ -10249,7 +10735,7 @@ checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" dependencies = [ "hmac 0.11.0", "pbkdf2 0.8.0", - "schnorrkel", + "schnorrkel 0.9.1", "sha2 0.9.9", "zeroize", ] @@ -10557,6 +11043,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -10867,14 +11364,14 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.19.14", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] @@ -10892,6 +11389,17 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.0.0", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.4.13" @@ -12307,7 +12815,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "serde", "zeroize", @@ -12350,6 +12858,12 @@ dependencies = [ "time 0.3.27", ] +[[package]] +name = "xxhash-rust" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03" + [[package]] name = "yamux" version = "0.10.2" diff --git a/Cargo.toml b/Cargo.toml index 02f9b8cbee..08ddc05bee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ members = [ "substrate/frame/llm", "substrate/frame/liberland-legislation", "substrate/frame/contracts-registry", + "liberland-extension/runtime", + "liberland-extension/ink", ] [profile.release] diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index 627072d28c..d14b4ee426 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -1,5 +1,6 @@ [workspace] resolver = "2" members = [ + "msig_court", "wrapped_lld", -] +] \ No newline at end of file diff --git a/contracts/msig_court/.gitignore b/contracts/msig_court/.gitignore new file mode 100755 index 0000000000..8de8f877e4 --- /dev/null +++ b/contracts/msig_court/.gitignore @@ -0,0 +1,9 @@ +# Ignore build artifacts from the local tests sub-crate. +/target/ + +# Ignore backup files creates by cargo fmt. +**/*.rs.bk + +# Remove Cargo.lock when creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/contracts/msig_court/Cargo.toml b/contracts/msig_court/Cargo.toml new file mode 100644 index 0000000000..441599a98f --- /dev/null +++ b/contracts/msig_court/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "msig_court" +version = "0.1.0" +authors = ["[your_name] <[your_email]>"] +edition = "2021" + +[dependencies] +ink = { version = "5.0.0", default-features = false } +liberland-extension = { path = "../../liberland-extension/ink", default-features = false} + +[dev-dependencies] +ink_e2e = { version = "5.0.0" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", + "liberland-extension/std", +] +ink-as-dependency = [] +e2e-tests = [] \ No newline at end of file diff --git a/contracts/msig_court/lib.rs b/contracts/msig_court/lib.rs new file mode 100644 index 0000000000..4e68e9371c --- /dev/null +++ b/contracts/msig_court/lib.rs @@ -0,0 +1,538 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[cfg(test)] +mod mock; + +#[ink::contract(env = liberland_extension::LiberlandEnvironment)] +mod msig_court { + use ink::codegen::Env; + use ink::prelude::vec::Vec; + use ink::storage::Mapping; + use liberland_extension::LLMForceTransferArguments; + + #[derive(Debug, Clone, PartialEq, Eq)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + #[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] + pub enum Proposal { + LLMForceTransfer(LLMForceTransferArguments), + SetGovernance { threshold: u32, judges: Vec }, + } + + #[derive(Debug, PartialEq, Eq)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum ProposalState { + PendingApprovals, + Executed(Result<()>), + } + + #[derive(Debug, PartialEq, Eq, Clone)] + #[ink::scale_derive(Encode, Decode, TypeInfo)] + pub enum Error { + /// Unauthorized + Unauthorized, + /// Proposal already exists + AlreadyExists, + /// Proposal not found + NotFound, + /// Caller already approved for this proposal + AlreadyApproved, + /// Call failed + CallFailed, + /// Invalid parameters + InvalidParameters, + } + + impl From for Error { + fn from(_: liberland_extension::Error) -> Self { + Self::CallFailed + } + } + + pub type Result = core::result::Result; + pub type PropKey = ::Type; + + #[ink(storage)] + #[derive(Default)] + pub struct MsigCourt { + threshold: u32, + judges: Vec, + proposals: Mapping, + approvals: Mapping>, + } + + #[ink(event)] + pub struct Proposed { + #[ink(topic)] + proposer: AccountId, + key: PropKey, + proposal: Proposal, + } + + #[ink(event)] + pub struct Approved { + #[ink(topic)] + approver: AccountId, + key: PropKey, + } + + #[ink(event)] + pub struct Executed { + #[ink(topic)] + approver: AccountId, + key: PropKey, + result: Result<()>, + } + + impl MsigCourt { + fn execute(&mut self, proposal: Proposal) -> Result<()> { + use Proposal::*; + match proposal { + LLMForceTransfer(args) => { + self.env().extension().llm_force_transfer(args).map_err(|e| e.into()) + }, + SetGovernance { threshold, judges } => self.set_governance(threshold, judges), + } + } + + fn do_approve(&mut self, approver: AccountId, key: PropKey) -> Result { + let approvals = self.approvals.take(key).ok_or(Error::NotFound)?; + if approvals.contains(&approver) { + return Err(Error::AlreadyApproved); + } + + if approvals.len().saturating_add(1) >= self.threshold as usize { + let proposal = + self.proposals.take(key).expect("Approvals exist, so proposal must exist too"); + let result = self.execute(proposal); + self.env().emit_event(Executed { approver, key, result: result.clone() }); + Ok(ProposalState::Executed(result)) + } else { + let mut approvals = approvals; + approvals.push(approver); + self.approvals.insert(key, &approvals); + self.env().emit_event(Approved { approver, key }); + Ok(ProposalState::PendingApprovals) + } + } + + fn set_governance(&mut self, threshold: u32, judges: Vec) -> Result<()> { + if threshold as usize > judges.len() { + return Err(Error::InvalidParameters); + } + + self.threshold = threshold; + self.judges = judges; + Ok(()) + } + } + + impl MsigCourt { + #[ink(constructor)] + pub fn new(threshold: u32, judges: Vec) -> Self { + assert!(threshold as usize <= judges.len()); + Self { threshold, judges, ..Default::default() } + } + + #[ink(message)] + pub fn propose(&mut self, proposal: Proposal) -> Result<(PropKey, ProposalState)> { + let caller = self.env().caller(); + if !self.judges.contains(&caller) { + return Err(Error::Unauthorized); + } + + let mut key = + ::Type::default(); + ink::env::hash_encoded::(&proposal, &mut key); + + if self.proposals.contains(key) { + return Err(Error::AlreadyExists); + } + + self.proposals.insert(key, &proposal); + self.approvals.insert(key, &Vec::::new()); + self.env().emit_event(Proposed { proposer: caller, key, proposal }); + let state = self.do_approve(caller, key)?; + Ok((key, state)) + } + + #[ink(message)] + pub fn approve(&mut self, key: PropKey) -> Result { + let caller = self.env().caller(); + if !self.judges.contains(&caller) { + return Err(Error::Unauthorized); + } + self.do_approve(caller, key) + } + + #[ink(message)] + pub fn get_threshold(&self) -> u32 { + self.threshold + } + + #[ink(message)] + pub fn get_judges(&self) -> Vec { + self.judges.clone() + } + + #[ink(message)] + pub fn get_proposal(&self, key: PropKey) -> Option<(Proposal, Vec)> { + Some((self.proposals.get(key)?, self.approvals.get(key)?)) + } + } + + #[cfg(test)] + mod tests { + use super::*; + use crate::mock::*; + use liberland_extension::LLMAccount; + + fn alice() -> AccountId { + ink::env::test::default_accounts::().alice + } + + fn bob() -> AccountId { + ink::env::test::default_accounts::().bob + } + + fn charlie() -> AccountId { + ink::env::test::default_accounts::().charlie + } + + fn set_next_caller(caller: AccountId) { + ink::env::test::set_caller::(caller); + } + + fn assert_proposed_event( + event: &ink::env::test::EmittedEvent, + expected_proposer: AccountId, + expected_key: PropKey, + expected_proposal: Proposal, + ) { + let decoded_event = ::decode(&mut &event.data[..]) + .expect("encountered invalid contract event data buffer"); + let Proposed { proposer, key, proposal } = decoded_event; + assert_eq!(proposer, expected_proposer); + assert_eq!(key, expected_key); + assert_eq!(proposal, expected_proposal); + } + + fn assert_approved_event( + event: &ink::env::test::EmittedEvent, + expected_approver: AccountId, + expected_key: PropKey, + ) { + let decoded_event = ::decode(&mut &event.data[..]) + .expect("encountered invalid contract event data buffer"); + let Approved { approver, key } = decoded_event; + assert_eq!(approver, expected_approver); + assert_eq!(key, expected_key); + } + + fn assert_executed_event( + event: &ink::env::test::EmittedEvent, + expected_approver: AccountId, + expected_key: PropKey, + expected_result: Result<()>, + ) { + let decoded_event = ::decode(&mut &event.data[..]) + .expect("encountered invalid contract event data buffer"); + let Executed { approver, key, result } = decoded_event; + assert_eq!(approver, expected_approver); + assert_eq!(key, expected_key); + assert_eq!(result, expected_result); + } + + #[ink::test] + fn new_works() { + let msig_court = MsigCourt::new(1, vec![alice()]); + assert_eq!(msig_court.threshold, 1); + assert_eq!(msig_court.judges[0], alice()); + assert_eq!(msig_court.judges.len(), 1); + + let msig_court = MsigCourt::new(2, vec![alice(), bob(), charlie()]); + assert_eq!(msig_court.threshold, 2); + assert_eq!(msig_court.judges[0], alice()); + assert_eq!(msig_court.judges[1], bob()); + assert_eq!(msig_court.judges[2], charlie()); + assert_eq!(msig_court.judges.len(), 3); + } + + #[ink::test] + #[should_panic] + fn new_prevents_bricking() { + MsigCourt::new(2, vec![alice()]); + } + + #[ink::test] + fn propose_executes_immediately_with_threshold_1() { + let mut msig_court = MsigCourt::new(1, vec![alice()]); + set_next_caller(alice()); + let (_, state) = msig_court + .propose(Proposal::SetGovernance { threshold: 2, judges: vec![alice(), bob()] }) + .expect("propose shouldnt fail"); + + assert_eq!(state, ProposalState::Executed(Ok(()))); + } + + #[ink::test] + fn must_be_a_judge_to_propose() { + let mut msig_court = MsigCourt::new(1, vec![alice()]); + set_next_caller(bob()); + let res = msig_court + .propose(Proposal::SetGovernance { threshold: 2, judges: vec![alice(), bob()] }); + assert_eq!(res, Err(Error::Unauthorized)); + } + + #[ink::test] + fn propose_doesnt_execute_with_threshold_2() { + let mut msig_court = MsigCourt::new(2, vec![alice(), bob()]); + set_next_caller(alice()); + let proposal = Proposal::SetGovernance { threshold: 1, judges: vec![alice()] }; + let (key, state) = msig_court.propose(proposal.clone()).expect("propose shouldnt fail"); + assert_eq!(state, ProposalState::PendingApprovals); + assert_eq!(msig_court.proposals.get(&key), Some(proposal)); + assert_eq!(msig_court.approvals.get(&key), Some(vec![alice()])); + } + + #[ink::test] + fn cant_duplicate_proposals() { + let mut msig_court = MsigCourt::new(2, vec![alice(), bob()]); + set_next_caller(alice()); + let proposal = Proposal::SetGovernance { threshold: 1, judges: vec![alice()] }; + let (_, state) = msig_court.propose(proposal.clone()).expect("propose shouldnt fail"); + assert_eq!(state, ProposalState::PendingApprovals); + + let res = msig_court.propose(proposal.clone()); + assert_eq!(res, Err(Error::AlreadyExists)); + } + + #[ink::test] + fn approve_works() { + let mut msig_court = MsigCourt::new(3, vec![alice(), bob(), charlie()]); + set_next_caller(alice()); + let (key, _) = msig_court + .propose(Proposal::SetGovernance { threshold: 1, judges: vec![alice()] }) + .expect("propose shouldnt fail"); + + set_next_caller(bob()); + let res = msig_court.approve(key); + assert_eq!(res, Ok(ProposalState::PendingApprovals)); + assert_eq!(msig_court.approvals.get(&key), Some(vec![alice(), bob()])) + } + + #[ink::test] + fn cant_double_approve() { + let mut msig_court = MsigCourt::new(3, vec![alice(), bob(), charlie()]); + set_next_caller(alice()); + let (key, _) = msig_court + .propose(Proposal::SetGovernance { threshold: 1, judges: vec![alice()] }) + .expect("propose shouldnt fail"); + + let res = msig_court.approve(key); + assert_eq!(res, Err(Error::AlreadyApproved)); + } + + #[ink::test] + fn must_be_a_judge_to_approve() { + let mut msig_court = MsigCourt::new(2, vec![alice(), bob()]); + set_next_caller(alice()); + let (key, _) = msig_court + .propose(Proposal::SetGovernance { threshold: 1, judges: vec![alice()] }) + .expect("propose shouldnt fail"); + + set_next_caller(charlie()); + let res = msig_court.approve(key); + assert_eq!(res, Err(Error::Unauthorized)); + } + + #[ink::test] + fn set_governance_works() { + let mut msig_court = MsigCourt::new(1, vec![alice()]); + set_next_caller(alice()); + let (_, state) = msig_court + .propose(Proposal::SetGovernance { threshold: 2, judges: vec![alice(), bob()] }) + .expect("propose shouldnt fail"); + assert_eq!(state, ProposalState::Executed(Ok(()))); + assert_eq!(msig_court.threshold, 2); + assert_eq!(msig_court.judges[0], alice()); + assert_eq!(msig_court.judges[1], bob()); + assert_eq!(msig_court.judges.len(), 2); + } + + #[ink::test] + fn set_governance_prevents_bricking() { + let mut msig_court = MsigCourt::new(1, vec![alice()]); + set_next_caller(alice()); + let (_, state) = msig_court + .propose(Proposal::SetGovernance { threshold: 3, judges: vec![alice(), bob()] }) + .expect("propose shouldnt fail"); + assert_eq!(state, ProposalState::Executed(Err(Error::InvalidParameters))); + assert_eq!(msig_court.threshold, 1); + assert_eq!(msig_court.judges[0], alice()); + assert_eq!(msig_court.judges.len(), 1); + } + + #[ink::test] + fn llm_force_transfer_works() { + ink::env::test::register_chain_extension(MockedLiberlandExtensionSuccess); + + let mut msig_court = MsigCourt::new(1, vec![alice()]); + set_next_caller(alice()); + let (_, state) = msig_court + .propose(Proposal::LLMForceTransfer(LLMForceTransferArguments { + from: LLMAccount::Locked(alice()), + to: LLMAccount::Locked(bob()), + amount: 1u8.into(), + })) + .expect("propose shouldnt fail"); + assert_eq!(state, ProposalState::Executed(Ok(()))); + } + + #[ink::test] + fn llm_force_transfer_propagates_errors() { + ink::env::test::register_chain_extension(MockedLiberlandExtensionFail); + + let mut msig_court = MsigCourt::new(1, vec![alice()]); + set_next_caller(alice()); + let (_, state) = msig_court + .propose(Proposal::LLMForceTransfer(LLMForceTransferArguments { + from: LLMAccount::Locked(alice()), + to: LLMAccount::Locked(bob()), + amount: 1u8.into(), + })) + .expect("propose shouldnt fail"); + assert_eq!(state, ProposalState::Executed(Err(Error::CallFailed))); + } + + #[ink::test] + fn correct_events_for_threshold_1() { + let mut msig_court = MsigCourt::new(1, vec![alice()]); + let proposal = Proposal::SetGovernance { threshold: 2, judges: vec![alice(), bob()] }; + set_next_caller(alice()); + let (key, _) = msig_court.propose(proposal.clone()).expect("propose shouldnt fail"); + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 2); + assert_proposed_event(&emitted_events[0], alice(), key, proposal); + assert_executed_event(&emitted_events[1], alice(), key, Ok(())); + } + + #[ink::test] + fn correct_events_for_threshold_2() { + let mut msig_court = MsigCourt::new(2, vec![alice(), bob()]); + let proposal = + Proposal::SetGovernance { threshold: 3, judges: vec![alice(), bob(), charlie()] }; + + set_next_caller(alice()); + let (key, _) = msig_court.propose(proposal.clone()).expect("propose shouldnt fail"); + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 2); + assert_proposed_event(&emitted_events[0], alice(), key, proposal); + assert_approved_event(&emitted_events[1], alice(), key); + + set_next_caller(bob()); + msig_court.approve(key).expect("approve shouldnt fail"); + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 3); + assert_executed_event(&emitted_events[2], bob(), key, Ok(())); + } + #[ink::test] + fn correct_events_for_threshold_3() { + let mut msig_court = MsigCourt::new(3, vec![alice(), bob(), charlie()]); + let proposal = Proposal::SetGovernance { threshold: 2, judges: vec![alice(), bob()] }; + + set_next_caller(alice()); + let (key, _) = msig_court.propose(proposal.clone()).expect("propose shouldnt fail"); + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 2); + assert_proposed_event(&emitted_events[0], alice(), key, proposal); + assert_approved_event(&emitted_events[1], alice(), key); + + set_next_caller(bob()); + msig_court.approve(key).expect("approve shouldnt fail"); + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 3); + assert_approved_event(&emitted_events[2], bob(), key); + + set_next_caller(charlie()); + msig_court.approve(key).expect("approve shouldnt fail"); + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 4); + assert_executed_event(&emitted_events[3], charlie(), key, Ok(())); + } + + #[ink::test] + fn correct_events_for_failed_call() { + let mut msig_court = MsigCourt::new(1, vec![alice()]); + let proposal = Proposal::SetGovernance { threshold: 3, judges: vec![alice(), bob()] }; + set_next_caller(alice()); + let (key, _) = msig_court.propose(proposal.clone()).expect("propose shouldnt fail"); + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 2); + assert_proposed_event(&emitted_events[0], alice(), key, proposal); + assert_executed_event(&emitted_events[1], alice(), key, Err(Error::InvalidParameters)); + } + + #[ink::test] + fn get_threshold_works() { + let msig_court = MsigCourt::new(1, vec![alice()]); + assert_eq!(msig_court.get_threshold(), 1); + } + + #[ink::test] + fn get_judges_works() { + let msig_court = MsigCourt::new(1, vec![alice()]); + assert_eq!(msig_court.get_judges(), vec![alice()]); + } + + #[ink::test] + fn get_proposal_works() { + let mut msig_court = MsigCourt::new(3, vec![alice(), bob(), charlie()]); + let proposal = Proposal::SetGovernance { threshold: 2, judges: vec![alice(), bob()] }; + + set_next_caller(alice()); + let (key, _) = msig_court.propose(proposal.clone()).expect("propose shouldnt fail"); + assert_eq!(msig_court.get_proposal(key), Some((proposal.clone(), vec![alice()]))); + + set_next_caller(bob()); + msig_court.approve(key).expect("approve shouldnt fail"); + assert_eq!(msig_court.get_proposal(key), Some((proposal, vec![alice(), bob()]))); + + set_next_caller(charlie()); + msig_court.approve(key).expect("approve shouldnt fail"); + assert_eq!(msig_court.get_proposal(key), None); + } + + #[ink::test] + fn get_proposal_fails_on_not_found() { + let msig_court = MsigCourt::new(1, vec![alice()]); + let key = ::Type::default(); + assert_eq!(msig_court.get_proposal(key), None); + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + /// Imports all the definitions from the outer scope so we can use them here. + use super::*; + + /// A helper function used for calling contract messages. + use ink_e2e::ContractsBackend; + + /// The End-to-End test `Result` type. + type E2EResult = std::result::Result>; + + /// We test that we can upload and instantiate the contract using its default constructor. + #[ink_e2e::test] + async fn new_works(mut client: ink_e2e::Client) -> E2EResult<()> { + let mut constructor = MsigCourtRef::new(1, vec![ink_e2e::alice()]); + + let contract = client + .instantiate("msig_court", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + + Ok(()) + } + } +} diff --git a/contracts/msig_court/mock.rs b/contracts/msig_court/mock.rs new file mode 100644 index 0000000000..4642281adc --- /dev/null +++ b/contracts/msig_court/mock.rs @@ -0,0 +1,21 @@ +pub struct MockedLiberlandExtensionSuccess; +impl ink::env::test::ChainExtension for MockedLiberlandExtensionSuccess { + fn ext_id(&self) -> u16 { + 0 + } + + fn call(&mut self, _func_id: u16, _input: &[u8], _output: &mut Vec) -> u32 { + 0 + } +} + +pub struct MockedLiberlandExtensionFail; +impl ink::env::test::ChainExtension for MockedLiberlandExtensionFail { + fn ext_id(&self) -> u16 { + 0 + } + + fn call(&mut self, _func_id: u16, _input: &[u8], _output: &mut Vec) -> u32 { + 1 + } +} diff --git a/liberland-extension/ink/Cargo.toml b/liberland-extension/ink/Cargo.toml new file mode 100644 index 0000000000..3f95a29461 --- /dev/null +++ b/liberland-extension/ink/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "liberland-extension" +version = "1.0.0" +authors.workspace = true +description = "Liberland chain extension." +edition.workspace = true +license = "MIT" +repository.workspace = true +publish = false + +[dependencies] +ink = { version = "5.0.0", default-features = false } + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] \ No newline at end of file diff --git a/liberland-extension/ink/src/lib.rs b/liberland-extension/ink/src/lib.rs new file mode 100644 index 0000000000..3de87919eb --- /dev/null +++ b/liberland-extension/ink/src/lib.rs @@ -0,0 +1,36 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +use ink::env::Environment; +mod types; + +pub use types::*; + +#[ink::chain_extension(extension = 0)] +pub trait Liberland { + type ErrorCode = Error; + + #[ink(function = 1)] + fn llm_force_transfer(args: LLMForceTransferArguments); +} + +impl ink::env::chain_extension::FromStatusCode for Error { + fn from_status_code(status_code: u32) -> Result<(), Self> { + match status_code { + 0 => Ok(()), + 1 => Err(Self::Failed), + _ => panic!("encountered unknown status code"), + } + } +} + +impl Environment for LiberlandEnvironment { + const MAX_EVENT_TOPICS: usize = ::MAX_EVENT_TOPICS; + + type AccountId = ::AccountId; + type Balance = ::Balance; + type Hash = ::Hash; + type BlockNumber = ::BlockNumber; + type Timestamp = ::Timestamp; + + type ChainExtension = Liberland; +} diff --git a/liberland-extension/ink/src/types.rs b/liberland-extension/ink/src/types.rs new file mode 100644 index 0000000000..49255d55c7 --- /dev/null +++ b/liberland-extension/ink/src/types.rs @@ -0,0 +1,31 @@ +use ink::env::Environment; + +type AccountId = ::AccountId; +type Balance = ::Balance; + +#[derive(Debug, Clone, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] +pub enum LLMAccount { + Liquid(AccountId), + Locked(AccountId), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(ink::storage::traits::StorageLayout))] +pub struct LLMForceTransferArguments { + pub from: LLMAccount, + pub to: LLMAccount, + pub amount: Balance, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[ink::scale_derive(Encode, Decode, TypeInfo)] +pub enum Error { + Failed, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[ink::scale_derive(TypeInfo)] +pub enum LiberlandEnvironment {} diff --git a/liberland-extension/runtime/Cargo.toml b/liberland-extension/runtime/Cargo.toml new file mode 100644 index 0000000000..2e2d7f8034 --- /dev/null +++ b/liberland-extension/runtime/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "liberland-extension-runtime" +version = "1.0.0" +authors.workspace = true +description = "Liberland chain extension." +edition.workspace = true +license = "MIT" +repository.workspace = true +publish = false + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +frame-support = { default-features = false, tag = "polkadot-v1.1.0", git = "https://github.com/paritytech/polkadot-sdk" } +pallet-contracts = { default-features = false, tag = "polkadot-v1.1.0", git = "https://github.com/paritytech/polkadot-sdk" } +sp-std = { default-features = false, tag = "polkadot-v1.1.0", git = "https://github.com/paritytech/polkadot-sdk" } +sp-core = { default-features = false, tag = "polkadot-v1.1.0", git = "https://github.com/paritytech/polkadot-sdk" } +sp-runtime = { default-features = false, tag = "polkadot-v1.1.0", git = "https://github.com/paritytech/polkadot-sdk" } +frame-system = { default-features = false, tag = "polkadot-v1.1.0", git = "https://github.com/paritytech/polkadot-sdk" } +log = { version = "0.4.17", default-features = false } + +pallet-llm = { default-features = false, path = "../../substrate/frame/llm" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "pallet-contracts/std", + "sp-std/std", + "sp-core/std", + "sp-runtime/std", + "frame-system/std", + "log/std", + "pallet-llm/std", +] \ No newline at end of file diff --git a/liberland-extension/runtime/src/lib.rs b/liberland-extension/runtime/src/lib.rs new file mode 100644 index 0000000000..ed2b224005 --- /dev/null +++ b/liberland-extension/runtime/src/lib.rs @@ -0,0 +1,70 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, MaxEncodedLen}; +use log::{error, trace}; +use pallet_contracts::chain_extension::{ChainExtension, Environment, Ext, InitState, RetVal}; +use sp_runtime::DispatchError; + +#[derive(Decode, Encode, MaxEncodedLen)] +pub struct LLMForceTransferArguments { + from: pallet_llm::LLMAccount, + to: pallet_llm::LLMAccount, + amount: T::Balance, +} + +/// Contract extension for the Liberland Chain +#[derive(Default)] +pub struct LiberlandExtension; + +impl LiberlandExtension { + fn llm_force_transfer( + &mut self, + env: Environment, + ) -> Result + where + E::T: pallet_llm::Config + pallet_contracts::Config, + ::RuntimeCall: From>, + { + trace!( + target: "runtime", + "[ChainExtension]|call|llm_force_transfer" + ); + let mut env = env.buf_in_buf_out(); + let args: LLMForceTransferArguments = env.read_as()?; + let ext = env.ext(); + let call: ::RuntimeCall = + pallet_llm::Call::::force_transfer { + from: args.from, + to: args.to, + amount: args.amount, + } + .into(); + ext.call_runtime(call).map_err(|e| e.error)?; + Ok(RetVal::Converging(0)) + } +} + +impl ChainExtension for LiberlandExtension +where + T: pallet_llm::Config + pallet_contracts::Config, + ::RuntimeCall: From>, +{ + fn call(&mut self, env: Environment) -> Result + where + E::T: pallet_llm::Config + pallet_contracts::Config, + ::RuntimeCall: From>, + { + let func_id = env.func_id(); + match func_id { + 1 => self.llm_force_transfer::(env), + _ => { + error!("Called an unregistered `func_id`: {:}", func_id); + return Err(DispatchError::Other("Unimplemented func_id")); + }, + } + } + + fn enabled() -> bool { + true + } +} diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 27a89ef94f..590a854969 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -106,6 +106,7 @@ pallet-registry = { path = "../../../frame/registry", default-features = false } pallet-office = { path = "../../../frame/office", default-features = false } pallet-custom-account = { path = "../../../frame/custom-account", default-features = false } pallet-contracts-registry = { path = "../../../frame/contracts-registry", default-features = false } +liberland-extension-runtime = { path = "../../../../liberland-extension/runtime", default-features = false } # Sora Bridge: substrate-bridge-app = { git = "https://github.com/sora-xor/sora2-common.git", branch = "polkadotsdk-lib-1.1.0", default-features = false } @@ -134,6 +135,7 @@ std = [ "frame-system-rpc-runtime-api/std", "frame-system/std", "frame-try-runtime?/std", + "liberland-extension-runtime/std", "log/std", "node-primitives/std", "pallet-asset-conversion/std", diff --git a/substrate/bin/node/runtime/src/impls.rs b/substrate/bin/node/runtime/src/impls.rs index 4d8c466579..c06d791f25 100644 --- a/substrate/bin/node/runtime/src/impls.rs +++ b/substrate/bin/node/runtime/src/impls.rs @@ -512,6 +512,17 @@ impl, StringLimit> } } +#[derive( + Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, MaxEncodedLen, scale_info::TypeInfo, Serialize, Deserialize, +)] +pub struct ContractsCallFilter; + +impl Contains for ContractsCallFilter { + fn contains(c: &RuntimeCall) -> bool { + matches!(c, RuntimeCall::LLM(pallet_llm::Call::force_transfer { .. })) + } +} + // Sora Bridge pub struct GenericTimepointProvider; diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 5271326380..c33daf8e8c 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -44,7 +44,7 @@ use frame_support::{ tokens::nonfungibles_v2::Inspect, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, MapSuccess, Currency, EitherOf, EitherOfDiverse, Imbalance, InstanceFilter, - KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced + KeyOwnerProofSystem, LockIdentifier, OnUnbalanced }, weights::{ constants::{ @@ -111,7 +111,7 @@ mod migrations; use impls::{ Author, ToAccountId, IdentityCallFilter, RegistryCallFilter, NftsCallFilter, OnLLMPoliticsUnlock, - ContainsMember, CouncilAccountCallFilter, EnsureCmp, SenateAccountCallFilter, + ContainsMember, CouncilAccountCallFilter, EnsureCmp, ContractsCallFilter, SenateAccountCallFilter, }; /// Constant values used within the runtime. @@ -1078,20 +1078,14 @@ impl pallet_contracts::Config for Runtime { type Currency = Balances; type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; - /// The safest default is to allow no calls at all. - /// - /// Runtimes should whitelist dispatchables that are allowed to be called from contracts - /// and make sure they are stable. Dispatchables exposed to contracts are not allowed to - /// change because that would break already deployed contracts. The `Call` structure itself - /// is not allowed to change the indices of existing pallets, too. - type CallFilter = Nothing; + type CallFilter = ContractsCallFilter; type DepositPerItem = DepositPerItem; type DepositPerByte = DepositPerByte; type DefaultDepositLimit = DefaultDepositLimit; type CallStack = [pallet_contracts::Frame; 5]; type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; - type ChainExtension = (); + type ChainExtension = liberland_extension_runtime::LiberlandExtension; type Schedule = Schedule; type AddressGenerator = pallet_contracts::DefaultAddressGenerator; type MaxCodeLen = ConstU32<{ 123 * 1024 }>; @@ -1362,6 +1356,7 @@ impl pallet_llm::Config for Runtime { >; type OnLLMPoliticsUnlock = OnLLMPoliticsUnlock; type WeightInfo = (); + type MaxCourts = ConstU32<2>; } parameter_types! { diff --git a/substrate/frame/democracy/src/tests.rs b/substrate/frame/democracy/src/tests.rs index 9e2c7d451a..52a3fdde91 100644 --- a/substrate/frame/democracy/src/tests.rs +++ b/substrate/frame/democracy/src/tests.rs @@ -264,6 +264,7 @@ impl pallet_llm::Config for Test { type OnLLMPoliticsUnlock = (); type SenateOrigin = EnsureRoot; type WeightInfo = (); + type MaxCourts = ConstU32<1>; } parameter_types! { diff --git a/substrate/frame/elections-phragmen/src/lib.rs b/substrate/frame/elections-phragmen/src/lib.rs index ddeeeb3065..0fea128a80 100644 --- a/substrate/frame/elections-phragmen/src/lib.rs +++ b/substrate/frame/elections-phragmen/src/lib.rs @@ -1458,6 +1458,7 @@ mod tests { type OnLLMPoliticsUnlock = (); type SenateOrigin = EnsureRoot; type WeightInfo = (); + type MaxCourts = ConstU32<1>; } pub struct TestChangeMembers; diff --git a/substrate/frame/liberland-legislation/src/mock.rs b/substrate/frame/liberland-legislation/src/mock.rs index 444cd7cc5f..710aec6568 100644 --- a/substrate/frame/liberland-legislation/src/mock.rs +++ b/substrate/frame/liberland-legislation/src/mock.rs @@ -257,6 +257,7 @@ impl pallet_llm::Config for Test { type OnLLMPoliticsUnlock = (); type SenateOrigin = EnsureRoot; type WeightInfo = (); + type MaxCourts = ConstU32<1>; } impl pallet_liberland_legislation::Config for Test { diff --git a/substrate/frame/llm/README.md b/substrate/frame/llm/README.md index 8a9219b67a..2f4bcd2bc0 100644 --- a/substrate/frame/llm/README.md +++ b/substrate/frame/llm/README.md @@ -73,13 +73,19 @@ Accounts may freely transfer their not-locked LLM to other accounts. These calls can be made from any _Signed_ origin. * `send_llm`: Transfer LLM. Wrapper over `pallet-assets`' `transfer`. +* `send_llm_to_politipool`: Transfer LLM directly to account's politipool. * `politics_lock`: Lock LLM into politics pool, a.k.a. politipool. * `politics_unlock`: Unlock 10% of locked LLM. Can't be called again for a WithdrawalLock period. Affects political rights for an ElectionLock period. * `approve_transfer`: As an assembly member you can approve a transfer of LLM. Not implemented. +* `remark`: Deposit Remarked event. Used by Liberland tooling for annotating transfers. #### Restricted +* `treasury_lld_transfer`: Transfer LLD from treasury to specified account. Can only be called by selected accounts and Senate. * `treasury_llm_transfer`: Transfer LLM from treasury to specified account. Can only be called by selected accounts and Senate. +* `treasury_llm_transfer_to_politipool`: Transfer LLM from treasury to specified account's politipool. Can only be called by selected accounts and Senate. +* `force_transfer`: Force transfer LLM from between accounts. Can only be called by courts. +* `set_courts`: Set courts. Can only be called by Root. ### Public functions @@ -87,7 +93,6 @@ These calls can be made from any _Signed_ origin. * `get_llm_vault_account`: AccountId of **Vault** account. **Vault** account stores all LLM created initially on genesis and releases it to treasury on LLM Release Events. * `get_llm_treasury_account`: AccountId of **Treasury** account. **Treasury** accounts receives prereleased amount of LLM on genesis and part of LLM from **Vault** on LLM Release Events. * `get_llm_politipool_account`: AccountId of **Politipool** account. **Politipool** account stores LLM locked in politics by all other accounts. -* `remark`: Deposit Remarked event. Used by Liberland tooling for annotating transfers. ### LLM trait diff --git a/substrate/frame/llm/src/benchmarking.rs b/substrate/frame/llm/src/benchmarking.rs index f052e33cdf..37c3278710 100644 --- a/substrate/frame/llm/src/benchmarking.rs +++ b/substrate/frame/llm/src/benchmarking.rs @@ -13,7 +13,8 @@ use super::*; use crate::{LLMPolitics, Pallet as LLM}; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_system::RawOrigin; -use sp_runtime::Saturating; +use sp_core::Get; +use sp_runtime::{BoundedVec, Saturating}; use sp_std::prelude::*; const SEED: u32 = 0; @@ -109,6 +110,32 @@ benchmarks! { let e: ::RuntimeEvent = Event::Remarked(data).into(); frame_system::Pallet::::assert_last_event(e.into()); } + + force_transfer { + let user: T::AccountId = account("user", 0, SEED); + let user2: T::AccountId = account("user", 1, SEED); + let amount: T::Balance = 10000u32.into(); + LLM::::transfer_from_treasury(user.clone(), amount.clone()).unwrap(); + LLM::::set_courts(RawOrigin::Root.into(), vec![user.clone()].try_into().unwrap()).unwrap(); + let origin = RawOrigin::Signed(user.clone()); + LLM::::politics_lock(origin.clone().into(), amount.clone()).unwrap(); + assert_eq!(LLMPolitics::::get(&user), amount.clone()); + }: _(origin, LLMAccount::Locked(user.clone()), LLMAccount::Liquid(user2.clone()), amount.clone()) + verify { + assert_eq!(LLMPolitics::::get(&user), 0u8.into()); + } + + set_courts { + let l in 1 .. T::MaxCourts::get(); + let mut courts: Vec = vec![]; + for i in 1..=l { + courts.push(account("court", i, SEED)); + } + let courts: BoundedVec = courts.try_into().unwrap(); + }: _(RawOrigin::Root, courts.clone()) + verify { + assert_eq!(Courts::::get(), courts); + } } impl_benchmark_test_suite!(LLM, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/substrate/frame/llm/src/lib.rs b/substrate/frame/llm/src/lib.rs index ceeab24b93..4339dc8a89 100644 --- a/substrate/frame/llm/src/lib.rs +++ b/substrate/frame/llm/src/lib.rs @@ -1,141 +1,4 @@ -//! # Liberland Merit(LLM) Pallet -//! -//! ## Overview -//! -//! Liberland Merit is a Liberland currency that gives political power to citizens. -//! -//! The LLM pallet handles: -//! -//! * creating LLM asset in `pallet-assets` on genesis -//! * LLM release from **Vault** to **Treasury** -//! * locking, a.k.a. politipooling the LLM for use in politics -//! * verifying citizenship status -//! -//! ## LLM lifecycle -//! -//! On Genesis (see `fn create_llm`): -//! -//! * LLM is created in `pallet-assets` -//! * configured `TotalSupply` amount of LLM is created and transferred to **Vault** -//! * configured `PreReleasedAmount` is transferred from **Vault** to **Treasury** -//! -//! Every `InflationEventInterval` (see `fn maybe_release`): -//! -//! * `InflationEventReleaseFactor` of **Vault** balance is transferred to **Treasury** -//! -//! Accounts are free to locks in politics, a.k.a. politipool any amount of LLM at any time. -//! -//! Accounts may unlock 10% of locked LLM once every `Withdrawlock` duration (see [Genesis -//! Config](#genesis-config)), but it will suspend their politics rights for `Electionlock` -//! duration. -//! -//! Accounts may freely transfer their not-locked LLM to other accounts. -//! -//! ### Special accounts: -//! -//! * **Treasury**: -//! * gets `PreReleasedAmount` LLM on genesis and 10% of **Vault** balance periodically (_LLM -//! Release Event_) -//! * may hold LLD -//! * derived from PalletID `lltreasu`: `5EYCAe5hveooUENA5d7dwq3caqM4LLBzktNumMKmhNRXu4JE` -//! -//! * **Vault**: -//! * gets initial supply of LLM on genesis -//! * releases 10% of it's balance to **Trasury** on LLM Release Event (yearly) -//! * derived from PalletID `llm/safe`: `5EYCAe5hvejUE1BUTDSnxDfCqVkADRicSKqbcJrduV1KCDmk` -//! -//! * **Politipool**, -//! * gets LLM locked in politics by other accounts (`politics_lock`) -//! * releases locked LLM back on `politics_unlock` -//! * derived from PalletID `politilock`: `5EYCAe5ijGqt3WEM9aKUBdth51NEBNz9P84NaUMWZazzWt7c` -//! -//! ## Internal Storage: -//! -//! * `LastRelease`: block number for next LLM Release Event (transfer from **Vault** to -//! **Treasury**) -//! * `LLMPolitics`: amount of LLM each account has allocated into politics -//! * `Withdrawlock`: block number until which account can't do another `politics_unlock` -//! * `Electionlock`: block number until which account can't participate in politics directly -//! * `Citizens`: number of valid citizens -//! -//! ## Runtime config -//! -//! * `RuntimeEvent`: Event type to use. -//! * `TotalSupply`: Total amount of LLM to be created on genesis. That's all LLM that will ever -//! exit. It will be stored in **Vault**. -//! * `PreReleasedAmount`: Amount of LLM that should be released (a.k.a. transferred from **Vault** -//! to **Treasury**) on genesis. -//! * `CitizenshipMinimumPooledLLM`: Minimum amount of pooled LLM for valid citizens. -//! * `UnlockFactor`: How much to unlock on politics_unlock -//! * `AssetId`: LLM AssetId. -//! * `AssetName`: LLM Asset name. -//! * `AssetSymbol`: LLM Asset symbol. -//! * `InflationEventInterval`: How often should 90% of vault be released to trasury. -//! * `OnLLMPoliticsUnlock`: Handler for unlocks - for example to remove votes and delegeations in -//! democracy. -//! -//! ## Genesis Config -//! -//! * `unpooling_withdrawlock_duration`: duration, in blocks, for which additional unlocks should -//! be locked after `politics_unlock` -//! * `unpooling_electionlock_duration`: duration, in blocks, for which politics rights should be -//! suspended after `politics_unlock` -//! -//! ## Interface -//! -//! ### Dispatchable Functions -//! -//! #### Public -//! -//! These calls can be made from any _Signed_ origin. -//! -//! * `send_llm`: Transfer LLM. Wrapper over `pallet-assets`' `transfer`. -//! * `send_llm`: Transfer LLM to another account's politipool. -//! * `politics_lock`: Lock LLM into politics pool, a.k.a. politipool. -//! * `politics_unlock`: Unlock 10% of locked LLM. Can't be called again for a WithdrawalLock -//! period. Affects political rights for an ElectionLock period. -//! * `approve_transfer`: As an assembly member you can approve a transfer of LLM. Not implemented. -//! * `remark`: Deposit Remarked event. Used by Liberland tooling for annotating transfers. -//! -//! #### Restricted -//! -//! * `treasury_llm_transfer`: Transfer LLM from treasury to specified account. Can only be called -//! by Senate. -//! * `treasury_llm_transfer`: Transfer LLM from treasury to specified account's politipool. Can -//! only be called by Senate. -//! -//! ### Public functions -//! -//! * `llm_id`: Asset ID of the LLM asset for `pallet-assets` -//! * `get_llm_vault_account`: AccountId of **Vault** account. **Vault** account stores all LLM -//! created initially on genesis and releases it to treasury on LLM Release Events. -//! * `get_llm_treasury_account`: AccountId of **Treasury** account. **Treasury** accounts receives -//! prereleased amount of LLM on genesis and part of LLM from **Vault** on LLM Release Events. -//! * `get_llm_politipool_account`: AccountId of **Politipool** account. **Politipool** account -//! stores LLM locked in politics by all other accounts. -//! -//! ### LLM trait -//! -//! LLM pallet implements LLM trait with following functions available for other pallets: -//! -//! * `check_pooled_llm`: Checks if given account has any LLM locked in politics. -//! * `is_election_unlocked`: Checks if given account has rights to participate in politics -//! unlocked. They may be locked after `politics_unlock`. This does NOT check if account is a -//! valid citizen - use `CitizenshipChecker` trait for that. -//! * `get_politi_pooled_amount`: Get total amount of locked LLM across all accounts. -//! * `get_llm_politics`: Get amount of locked LLM for given account. -//! -//! ### CitizenshipChecker trait -//! -//! LLM pallet implements CitizenshipChecker trait with following functions available for other -//! pallets: -//! -//! * `ensure_politics_allowed`: Checks if given account can participate in -//! politics actions. It verifies that it's a valid citizen, doesn't have -//! election rights locked and has 5000 LLM locked in politics. -//! -//! License: MIT - +#![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] pub use pallet::*; @@ -163,8 +26,17 @@ type Assets = pallet_assets::Pallet; type BalanceOf = <::Currency as Currency< ::AccountId, >>::Balance; +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + +#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum LLMAccount { + Liquid(AccountId), + Locked(AccountId), +} -#[frame_support::pallet] +#[frame_support::pallet(dev_mode)] // FIXME pub mod pallet { // Import various types used to declare pallet in scope. use super::*; @@ -234,6 +106,11 @@ pub mod pallet { #[pallet::getter(fn citizens)] pub(super) type Citizens = StorageValue<_, u64, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn courts)] + pub(super) type Courts = + StorageValue<_, BoundedVec, ValueQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { /// duration, in blocks, for which additional unlocks should be locked @@ -308,6 +185,7 @@ pub mod pallet { type OnLLMPoliticsUnlock: OnLLMPoliticsUnlock; type WeightInfo: WeightInfo; + type MaxCourts: Get; } pub type AssetId = ::AssetId; @@ -330,6 +208,8 @@ pub mod pallet { NonCitizen, /// Temporary locked after unpooling LLM Locked, + /// Caller isn't an authorized court + NotCourt, } const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); @@ -373,7 +253,7 @@ pub mod pallet { /// `Gottawait` otherwise. /// /// Emits: - /// * `LLMPoliticsLocked` + /// * `LLMPoliticsUnlocked` /// * `Transferred` from `pallet-assets` #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::politics_unlock())] @@ -507,6 +387,66 @@ pub mod pallet { Self::deposit_event(Event::::Remarked(data)); Ok(()) } + + /// Force transfer LLM. Can only be called by Courts. + /// + /// - `from`: Account to transfer from. + /// - `to`: Account to transfer to. + /// - `amount`: Amount to transfer. + /// + /// Emits: + /// * `LLMPoliticsLocked` + /// * `LLMPoliticsUnlocked` + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::force_transfer())] + pub fn force_transfer( + origin: OriginFor, + from: LLMAccount, + to: LLMAccount, + amount: T::Balance, + ) -> DispatchResult { + let caller: T::AccountId = ensure_signed(origin)?; + ensure!(Courts::::get().contains(&caller), Error::::NotCourt); + + let politipool_account = Self::get_llm_politipool_account(); + let transfer_from = match from { + LLMAccount::Liquid(_) => return Err(Error::::InvalidAccount.into()), + LLMAccount::Locked(account) => { + let politics_balance = LLMPolitics::::get(account.clone()); + ensure!(politics_balance >= amount, Error::::LowBalance); + + LLMPolitics::::mutate(account.clone(), |b| *b -= amount); + Self::deposit_event(Event::::LLMPoliticsUnlocked(account, amount)); + politipool_account.clone() + }, + }; + let transfer_to = match to { + LLMAccount::Liquid(account) => account, + LLMAccount::Locked(account) => { + LLMPolitics::::mutate(account.clone(), |b| *b += amount); + Self::deposit_event(Event::::LLMPoliticsLocked(account, amount)); + politipool_account + }, + }; + if transfer_from != transfer_to { + Self::transfer(transfer_from, transfer_to, amount)?; + } + Ok(()) + } + + /// Set Courts. Can only be called by Root + /// + /// - `courts`: New set of authorized courts + #[pallet::call_index(9)] + #[pallet::weight(::WeightInfo::set_courts(courts.len() as u32))] + pub fn set_courts( + origin: OriginFor, + courts: BoundedVec, + ) -> DispatchResult { + ensure_root(origin)?; + Courts::::set(courts); + Ok(()) + } } #[pallet::event] diff --git a/substrate/frame/llm/src/mock.rs b/substrate/frame/llm/src/mock.rs index 0e7af0e8c7..d6bfaf214c 100644 --- a/substrate/frame/llm/src/mock.rs +++ b/substrate/frame/llm/src/mock.rs @@ -124,6 +124,7 @@ impl pallet_llm::Config for Test { type OnLLMPoliticsUnlock = (); type SenateOrigin = EnsureRoot; type WeightInfo = (); + type MaxCourts = ConstU32<3>; } parameter_types! { @@ -207,6 +208,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { setup_citizenships(balances.into_iter().map(|(acc, _)| acc).collect()); LLM::transfer_from_vault(1, 6000).unwrap(); LLM::transfer_from_vault(2, 6000).unwrap(); + LLM::set_courts(RuntimeOrigin::root(), vec![1].try_into().unwrap()).unwrap(); }); ext } diff --git a/substrate/frame/llm/src/tests.rs b/substrate/frame/llm/src/tests.rs index e08e1955dd..15d8cc6afa 100644 --- a/substrate/frame/llm/src/tests.rs +++ b/substrate/frame/llm/src/tests.rs @@ -1,8 +1,8 @@ #![cfg(test)] use crate::{ - mock::*, Config, Electionlock, ElectionlockDuration, Error, Event, LLMPolitics, LastRelease, - RemarkData, Withdrawlock, WithdrawlockDuration, + mock::*, Config, Courts, Electionlock, ElectionlockDuration, Error, Event, LLMAccount, + LLMPolitics, LastRelease, RemarkData, Withdrawlock, WithdrawlockDuration, }; use codec::Compact; use frame_support::{ @@ -650,3 +650,109 @@ fn remark_deposits_event() { System::assert_last_event(Event::Remarked(data).into()); }); } + +#[test] +fn set_courts_works() { + new_test_ext().execute_with(|| { + assert_ok!(LLM::set_courts(RuntimeOrigin::root(), vec![105, 110].try_into().unwrap())); + assert_eq!(Courts::::get(), vec![105, 110]); + }); +} + +#[test] +fn only_approved_accounts_can_call_force_transfer() { + new_test_ext().execute_with(|| { + let unapproved = RuntimeOrigin::signed(2); + let approved = RuntimeOrigin::signed(1); + + let from = LLMAccount::Locked(1); + let to = LLMAccount::Locked(2); + let amount = 1; + + assert_noop!( + LLM::force_transfer(unapproved, from.clone(), to.clone(), amount), + Error::::NotCourt + ); + + assert_ok!(LLM::politics_lock(RuntimeOrigin::signed(1), 1)); + assert_ok!(LLM::force_transfer(approved, from, to, amount)); + }); +} + +#[test] +fn cant_force_transfer_more_than_balance() { + new_test_ext().execute_with(|| { + let court = RuntimeOrigin::signed(1); + let from = LLMAccount::Locked(1); + let to = LLMAccount::Locked(2); + let amount = 1; + + assert_noop!(LLM::force_transfer(court, from, to, amount), Error::::LowBalance); + }); +} + +#[test] +fn cant_force_transfer_from_liquid() { + new_test_ext().execute_with(|| { + let court = RuntimeOrigin::signed(1); + let from = LLMAccount::Liquid(1); + let to = LLMAccount::Locked(2); + let amount = 1; + + assert_noop!(LLM::force_transfer(court, from, to, amount), Error::::InvalidAccount); + }); +} + +#[test] +fn force_updates_balances_correctly_to_liquid() { + new_test_ext().execute_with(|| { + let court = RuntimeOrigin::signed(1); + let id = LLM::llm_id(); + let politipool = LLM::get_llm_politipool_account(); + let from = LLMAccount::Locked(1); + let to = LLMAccount::Liquid(2); + let amount = 2; + + assert_ok!(LLM::politics_lock(RuntimeOrigin::signed(1), 3)); + assert_ok!(LLM::politics_lock(RuntimeOrigin::signed(2), 2)); + let politipool_before = Assets::balance(id, politipool); + let from_before = Assets::balance(id, 1); + let to_before = Assets::balance(id, 2); + + assert_ok!(LLM::force_transfer(court, from, to, amount)); + + assert_eq!(Assets::balance(id, politipool), politipool_before - amount); + assert_eq!(Assets::balance(id, 1), from_before); + assert_eq!(Assets::balance(id, 2), to_before + amount); + + assert_eq!(LLMPolitics::::get(1), 3 - amount); + assert_eq!(LLMPolitics::::get(2), 2); + }); +} + +#[test] +fn force_updates_balances_correctly_to_locked() { + new_test_ext().execute_with(|| { + let court = RuntimeOrigin::signed(1); + let id = LLM::llm_id(); + let politipool = LLM::get_llm_politipool_account(); + let from = LLMAccount::Locked(1); + let to = LLMAccount::Locked(2); + let amount = 2; + + assert_ok!(LLM::politics_lock(RuntimeOrigin::signed(1), 3)); + assert_ok!(LLM::politics_lock(RuntimeOrigin::signed(2), 2)); + let politipool_before = Assets::balance(id, politipool); + let from_before = Assets::balance(id, 1); + let to_before = Assets::balance(id, 2); + + assert_ok!(LLM::force_transfer(court, from, to, amount)); + + assert_eq!(Assets::balance(id, politipool), politipool_before); + assert_eq!(Assets::balance(id, 1), from_before); + assert_eq!(Assets::balance(id, 2), to_before); + + assert_eq!(LLMPolitics::::get(1), 3 - amount); + assert_eq!(LLMPolitics::::get(2), 2 + amount); + }); +} diff --git a/substrate/frame/llm/src/weights.rs b/substrate/frame/llm/src/weights.rs index 3e1e9fa111..fd8378f411 100644 --- a/substrate/frame/llm/src/weights.rs +++ b/substrate/frame/llm/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_llm //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2024-05-10, STEPS: `20`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-06-21, STEPS: `20`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `kacper-HP-ProBook-445-G7`, CPU: `AMD Ryzen 7 4700U with Radeon Graphics` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 @@ -37,6 +37,8 @@ pub trait WeightInfo { fn send_llm() -> Weight; fn treasury_lld_transfer() -> Weight; fn remark(l: u32, ) -> Weight; + fn force_transfer() -> Weight; + fn set_courts(l: u32, ) -> Weight; } /// Weights for pallet_llm using the Substrate node and recommended hardware. @@ -49,20 +51,20 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `LLM::LLMPolitics` (r:1 w:1) - /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: None, mode: `Measured`) fn politics_lock() -> Weight { // Proof Size summary in bytes: // Measured: `1584` // Estimated: `6208` - // Minimum execution time: 79_110_000 picoseconds. - Weight::from_parts(79_601_000, 6208) + // Minimum execution time: 80_249_000 picoseconds. + Weight::from_parts(81_297_000, 6208) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `LLM::LLMPolitics` (r:1 w:1) - /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `LLM::Withdrawlock` (r:1 w:1) - /// Proof: `LLM::Withdrawlock` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Proof: `LLM::Withdrawlock` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Assets::Asset` (r:1 w:1) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) /// Storage: `Assets::Account` (r:2 w:2) @@ -70,19 +72,19 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `LLM::WithdrawlockDuration` (r:1 w:0) - /// Proof: `LLM::WithdrawlockDuration` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Proof: `LLM::WithdrawlockDuration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `LLM::ElectionlockDuration` (r:1 w:0) - /// Proof: `LLM::ElectionlockDuration` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Proof: `LLM::ElectionlockDuration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Democracy::VotingOf` (r:1 w:0) /// Proof: `Democracy::VotingOf` (`max_values`: None, `max_size`: Some(3803), added: 6278, mode: `MaxEncodedLen`) /// Storage: `LLM::Electionlock` (r:0 w:1) - /// Proof: `LLM::Electionlock` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Proof: `LLM::Electionlock` (`max_values`: None, `max_size`: None, mode: `Measured`) fn politics_unlock() -> Weight { // Proof Size summary in bytes: // Measured: `1565` // Estimated: `7268` - // Minimum execution time: 96_613_000 picoseconds. - Weight::from_parts(97_415_000, 7268) + // Minimum execution time: 98_618_000 picoseconds. + Weight::from_parts(99_526_000, 7268) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -96,8 +98,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `878` // Estimated: `6208` - // Minimum execution time: 61_307_000 picoseconds. - Weight::from_parts(77_607_000, 6208) + // Minimum execution time: 61_461_000 picoseconds. + Weight::from_parts(62_370_000, 6208) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -108,13 +110,13 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `LLM::LLMPolitics` (r:1 w:1) - /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: None, mode: `Measured`) fn treasury_llm_transfer_to_politipool() -> Weight { // Proof Size summary in bytes: // Measured: `1415` // Estimated: `8817` - // Minimum execution time: 118_205_000 picoseconds. - Weight::from_parts(120_258_000, 8817) + // Minimum execution time: 118_943_000 picoseconds. + Weight::from_parts(123_971_000, 8817) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -125,13 +127,13 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `LLM::LLMPolitics` (r:1 w:1) - /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: None, mode: `Measured`) fn send_llm_to_politipool() -> Weight { // Proof Size summary in bytes: // Measured: `1689` // Estimated: `8817` - // Minimum execution time: 130_187_000 picoseconds. - Weight::from_parts(130_918_000, 8817) + // Minimum execution time: 131_444_000 picoseconds. + Weight::from_parts(132_142_000, 8817) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -145,8 +147,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1151` // Estimated: `6208` - // Minimum execution time: 72_247_000 picoseconds. - Weight::from_parts(72_557_000, 6208) + // Minimum execution time: 73_544_000 picoseconds. + Weight::from_parts(74_523_000, 6208) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -156,8 +158,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `312` // Estimated: `6196` - // Minimum execution time: 64_643_000 picoseconds. - Weight::from_parts(65_504_000, 6196) + // Minimum execution time: 64_814_000 picoseconds. + Weight::from_parts(66_280_000, 6196) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -166,10 +168,42 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_709_000 picoseconds. - Weight::from_parts(10_461_236, 0) - // Standard Error: 1_504 - .saturating_add(Weight::from_parts(2_933, 0).saturating_mul(l.into())) + // Minimum execution time: 10_476_000 picoseconds. + Weight::from_parts(11_854_364, 0) + // Standard Error: 1_546 + .saturating_add(Weight::from_parts(3_034, 0).saturating_mul(l.into())) + } + /// Storage: `LLM::Courts` (r:1 w:0) + /// Proof: `LLM::Courts` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `LLM::LLMPolitics` (r:1 w:1) + /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `1431` + // Estimated: `6208` + // Minimum execution time: 83_322_000 picoseconds. + Weight::from_parts(84_929_000, 6208) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `LLM::Courts` (r:0 w:1) + /// Proof: `LLM::Courts` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `l` is `[1, 2]`. + fn set_courts(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_076_000 picoseconds. + Weight::from_parts(6_768_812, 0) + // Standard Error: 73_598 + .saturating_add(Weight::from_parts(241_677, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } @@ -182,20 +216,20 @@ impl WeightInfo for () { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `LLM::LLMPolitics` (r:1 w:1) - /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: None, mode: `Measured`) fn politics_lock() -> Weight { // Proof Size summary in bytes: // Measured: `1584` // Estimated: `6208` - // Minimum execution time: 79_110_000 picoseconds. - Weight::from_parts(79_601_000, 6208) + // Minimum execution time: 80_249_000 picoseconds. + Weight::from_parts(81_297_000, 6208) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `LLM::LLMPolitics` (r:1 w:1) - /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `LLM::Withdrawlock` (r:1 w:1) - /// Proof: `LLM::Withdrawlock` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Proof: `LLM::Withdrawlock` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Assets::Asset` (r:1 w:1) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) /// Storage: `Assets::Account` (r:2 w:2) @@ -203,19 +237,19 @@ impl WeightInfo for () { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `LLM::WithdrawlockDuration` (r:1 w:0) - /// Proof: `LLM::WithdrawlockDuration` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Proof: `LLM::WithdrawlockDuration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `LLM::ElectionlockDuration` (r:1 w:0) - /// Proof: `LLM::ElectionlockDuration` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Proof: `LLM::ElectionlockDuration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Democracy::VotingOf` (r:1 w:0) /// Proof: `Democracy::VotingOf` (`max_values`: None, `max_size`: Some(3803), added: 6278, mode: `MaxEncodedLen`) /// Storage: `LLM::Electionlock` (r:0 w:1) - /// Proof: `LLM::Electionlock` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Proof: `LLM::Electionlock` (`max_values`: None, `max_size`: None, mode: `Measured`) fn politics_unlock() -> Weight { // Proof Size summary in bytes: // Measured: `1565` // Estimated: `7268` - // Minimum execution time: 96_613_000 picoseconds. - Weight::from_parts(97_415_000, 7268) + // Minimum execution time: 98_618_000 picoseconds. + Weight::from_parts(99_526_000, 7268) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -229,8 +263,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `878` // Estimated: `6208` - // Minimum execution time: 61_307_000 picoseconds. - Weight::from_parts(77_607_000, 6208) + // Minimum execution time: 61_461_000 picoseconds. + Weight::from_parts(62_370_000, 6208) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -241,13 +275,13 @@ impl WeightInfo for () { /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `LLM::LLMPolitics` (r:1 w:1) - /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: None, mode: `Measured`) fn treasury_llm_transfer_to_politipool() -> Weight { // Proof Size summary in bytes: // Measured: `1415` // Estimated: `8817` - // Minimum execution time: 118_205_000 picoseconds. - Weight::from_parts(120_258_000, 8817) + // Minimum execution time: 118_943_000 picoseconds. + Weight::from_parts(123_971_000, 8817) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -258,13 +292,13 @@ impl WeightInfo for () { /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `LLM::LLMPolitics` (r:1 w:1) - /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) + /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: None, mode: `Measured`) fn send_llm_to_politipool() -> Weight { // Proof Size summary in bytes: // Measured: `1689` // Estimated: `8817` - // Minimum execution time: 130_187_000 picoseconds. - Weight::from_parts(130_918_000, 8817) + // Minimum execution time: 131_444_000 picoseconds. + Weight::from_parts(132_142_000, 8817) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -278,8 +312,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1151` // Estimated: `6208` - // Minimum execution time: 72_247_000 picoseconds. - Weight::from_parts(72_557_000, 6208) + // Minimum execution time: 73_544_000 picoseconds. + Weight::from_parts(74_523_000, 6208) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -289,8 +323,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `312` // Estimated: `6196` - // Minimum execution time: 64_643_000 picoseconds. - Weight::from_parts(65_504_000, 6196) + // Minimum execution time: 64_814_000 picoseconds. + Weight::from_parts(66_280_000, 6196) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -299,9 +333,41 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_709_000 picoseconds. - Weight::from_parts(10_461_236, 0) - // Standard Error: 1_504 - .saturating_add(Weight::from_parts(2_933, 0).saturating_mul(l.into())) + // Minimum execution time: 10_476_000 picoseconds. + Weight::from_parts(11_854_364, 0) + // Standard Error: 1_546 + .saturating_add(Weight::from_parts(3_034, 0).saturating_mul(l.into())) + } + /// Storage: `LLM::Courts` (r:1 w:0) + /// Proof: `LLM::Courts` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `LLM::LLMPolitics` (r:1 w:1) + /// Proof: `LLM::LLMPolitics` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `1431` + // Estimated: `6208` + // Minimum execution time: 83_322_000 picoseconds. + Weight::from_parts(84_929_000, 6208) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `LLM::Courts` (r:0 w:1) + /// Proof: `LLM::Courts` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `l` is `[1, 2]`. + fn set_courts(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_076_000 picoseconds. + Weight::from_parts(6_768_812, 0) + // Standard Error: 73_598 + .saturating_add(Weight::from_parts(241_677, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index f41d66f2a7..975a083f42 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -234,6 +234,7 @@ impl pallet_llm::Config for Test { type OnLLMPoliticsUnlock = (); type SenateOrigin = EnsureRoot; type WeightInfo = (); + type MaxCourts = ConstU32<100>; } use pallet_nfts::PalletFeatures; From 93c0ea20a1ac3f144229709930e2fb3c4b4ce16e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BBuk?= Date: Tue, 23 Jul 2024 12:08:55 +0200 Subject: [PATCH 2/3] Restrict Court to LLM Staked -> Staked transfers only --- substrate/frame/llm/src/benchmarking.rs | 2 +- substrate/frame/llm/src/lib.rs | 2 +- substrate/frame/llm/src/tests.rs | 23 +++++------------------ 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/substrate/frame/llm/src/benchmarking.rs b/substrate/frame/llm/src/benchmarking.rs index 37c3278710..277baf851a 100644 --- a/substrate/frame/llm/src/benchmarking.rs +++ b/substrate/frame/llm/src/benchmarking.rs @@ -120,7 +120,7 @@ benchmarks! { let origin = RawOrigin::Signed(user.clone()); LLM::::politics_lock(origin.clone().into(), amount.clone()).unwrap(); assert_eq!(LLMPolitics::::get(&user), amount.clone()); - }: _(origin, LLMAccount::Locked(user.clone()), LLMAccount::Liquid(user2.clone()), amount.clone()) + }: _(origin, LLMAccount::Locked(user.clone()), LLMAccount::Locked(user2.clone()), amount.clone()) verify { assert_eq!(LLMPolitics::::get(&user), 0u8.into()); } diff --git a/substrate/frame/llm/src/lib.rs b/substrate/frame/llm/src/lib.rs index 4339dc8a89..60613b0757 100644 --- a/substrate/frame/llm/src/lib.rs +++ b/substrate/frame/llm/src/lib.rs @@ -421,7 +421,7 @@ pub mod pallet { }, }; let transfer_to = match to { - LLMAccount::Liquid(account) => account, + LLMAccount::Liquid(_) => return Err(Error::::InvalidAccount.into()), LLMAccount::Locked(account) => { LLMPolitics::::mutate(account.clone(), |b| *b += amount); Self::deposit_event(Event::::LLMPoliticsLocked(account, amount)); diff --git a/substrate/frame/llm/src/tests.rs b/substrate/frame/llm/src/tests.rs index 15d8cc6afa..533aafbaf2 100644 --- a/substrate/frame/llm/src/tests.rs +++ b/substrate/frame/llm/src/tests.rs @@ -704,29 +704,16 @@ fn cant_force_transfer_from_liquid() { } #[test] -fn force_updates_balances_correctly_to_liquid() { +fn cant_force_transfer_to_liquid() { new_test_ext().execute_with(|| { + assert_ok!(LLM::politics_lock(RuntimeOrigin::signed(1), 3)); + let court = RuntimeOrigin::signed(1); - let id = LLM::llm_id(); - let politipool = LLM::get_llm_politipool_account(); let from = LLMAccount::Locked(1); let to = LLMAccount::Liquid(2); - let amount = 2; - - assert_ok!(LLM::politics_lock(RuntimeOrigin::signed(1), 3)); - assert_ok!(LLM::politics_lock(RuntimeOrigin::signed(2), 2)); - let politipool_before = Assets::balance(id, politipool); - let from_before = Assets::balance(id, 1); - let to_before = Assets::balance(id, 2); - - assert_ok!(LLM::force_transfer(court, from, to, amount)); - - assert_eq!(Assets::balance(id, politipool), politipool_before - amount); - assert_eq!(Assets::balance(id, 1), from_before); - assert_eq!(Assets::balance(id, 2), to_before + amount); + let amount = 1; - assert_eq!(LLMPolitics::::get(1), 3 - amount); - assert_eq!(LLMPolitics::::get(2), 2); + assert_noop!(LLM::force_transfer(court, from, to, amount), Error::::InvalidAccount); }); } From dbef17d83aa8c4510a509ce9a7eab41a9512e3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BBuk?= Date: Thu, 25 Jul 2024 15:23:48 +0200 Subject: [PATCH 3/3] Cleanup devmode --- substrate/frame/llm/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/llm/src/lib.rs b/substrate/frame/llm/src/lib.rs index 60613b0757..ef7021df0a 100644 --- a/substrate/frame/llm/src/lib.rs +++ b/substrate/frame/llm/src/lib.rs @@ -36,7 +36,7 @@ pub enum LLMAccount { Locked(AccountId), } -#[frame_support::pallet(dev_mode)] // FIXME +#[frame_support::pallet] pub mod pallet { // Import various types used to declare pallet in scope. use super::*;