diff --git a/Cargo.lock b/Cargo.lock index d7b5b84b4..24184dc61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,7 +101,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", ] @@ -113,7 +113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -161,11 +161,11 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.1.53" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da226340862e036ab26336dc99ca85311c6b662267c1440e1733890fd688802c" +checksum = "4d37bc62b68c056e3742265ab73c73d413d07357909e0e4ea1e95453066a7469" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "num_enum", "strum 0.26.3", ] @@ -177,7 +177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4138dc275554afa6f18c4217262ac9388790b2fc393c2dfe03c51d357abf013" dependencies = [ "alloy-eips", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rlp", "alloy-serde", "alloy-trie", @@ -196,7 +196,7 @@ checksum = "0fa04e1882c31288ce1028fdf31b6ea94cfa9eafa2e497f903ded631c8c6a42c" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rlp", "alloy-serde", "serde", @@ -212,40 +212,40 @@ dependencies = [ "alloy-json-abi", "alloy-network", "alloy-network-primitives", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-provider", "alloy-pubsub", "alloy-rpc-types-eth", - "alloy-sol-types 0.8.18", + "alloy-sol-types 0.8.21", "alloy-transport", "futures 0.3.31", "futures-util", - "thiserror 2.0.8", + "thiserror 2.0.11", ] [[package]] name = "alloy-core" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0713007d14d88a6edb8e248cddab783b698dbb954a28b8eee4bab21cfb7e578" +checksum = "482f377cebceed4bb1fb5e7970f0805e2ab123d06701be9351b67ed6341e74aa" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rlp", - "alloy-sol-types 0.8.18", + "alloy-sol-types 0.8.21", ] [[package]] name = "alloy-dyn-abi" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44e3b98c37b3218924cd1d2a8570666b89662be54e5b182643855f783ea68b33" +checksum = "555896f0b8578adb522b1453b6e6cc6704c3027bd0af20058befdde992cee8e9" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-sol-type-parser", - "alloy-sol-types 0.8.18", + "alloy-sol-types 0.8.21", "const-hex", "itoa", "serde", @@ -259,7 +259,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rlp", "serde", ] @@ -270,7 +270,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cabf647eb4650c91a9d38cb6f972bb320009e7e9d61765fb688a86f1563b33e8" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rlp", "derive_more 1.0.0", "k256", @@ -285,7 +285,7 @@ checksum = "52dd5869ed09e399003e0e0ec6903d981b2a92e74c5d37e6b40890bad2517526" dependencies = [ "alloy-eip2930", "alloy-eip7702", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rlp", "alloy-serde", "c-kzg", @@ -302,7 +302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d2a7fe5c1a9bd6793829ea21a636f30fc2b3f5d2e7418ba86d96e41dd1f460" dependencies = [ "alloy-eips", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-serde", "alloy-trie", "serde", @@ -310,11 +310,11 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "731ea743b3d843bc657e120fb1d1e9cc94f5dab8107e35a82125a63e6420a102" +checksum = "4012581681b186ba0882007ed873987cc37f86b1b488fe6b91d5efd0b585dc41" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-sol-type-parser", "serde", "serde_json", @@ -326,11 +326,11 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2008bedb8159a255b46b7c8614516eda06679ea82f620913679afbd8031fea72" dependencies = [ - "alloy-primitives 0.8.18", - "alloy-sol-types 0.8.18", + "alloy-primitives 0.8.21", + "alloy-sol-types 0.8.21", "serde", "serde_json", - "thiserror 2.0.8", + "thiserror 2.0.11", "tracing", ] @@ -345,18 +345,18 @@ dependencies = [ "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rpc-types-any", "alloy-rpc-types-eth", "alloy-serde", "alloy-signer", - "alloy-sol-types 0.8.18", + "alloy-sol-types 0.8.21", "async-trait", "auto_impl", "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.8", + "thiserror 2.0.11", ] [[package]] @@ -367,7 +367,7 @@ checksum = "f31c3c6b71340a1d076831823f09cb6e02de01de5c6630a9631bdb36f947ff80" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-serde", "serde", ] @@ -382,7 +382,7 @@ dependencies = [ "bytes", "cfg-if", "const-hex", - "derive_more 0.99.18", + "derive_more 0.99.19", "hex-literal 0.4.1", "itoa", "proptest", @@ -394,9 +394,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788bb18e8f61d5d9340b52143f27771daf7e1dccbaf2741621d2493f9debf52e" +checksum = "478bedf4d24e71ea48428d1bc278553bd7c6ae07c30ca063beb0b09fe58a9e74" dependencies = [ "alloy-rlp", "bytes", @@ -405,7 +405,7 @@ dependencies = [ "derive_more 1.0.0", "foldhash", "hashbrown 0.15.2", - "indexmap 2.7.0", + "indexmap 2.7.1", "itoa", "k256", "keccak-asm", @@ -413,7 +413,7 @@ dependencies = [ "proptest", "rand", "ruint", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "serde", "sha3", "tiny-keccak", @@ -431,7 +431,7 @@ dependencies = [ "alloy-json-rpc", "alloy-network", "alloy-network-primitives", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types-debug", @@ -454,7 +454,7 @@ dependencies = [ "schnellru", "serde", "serde_json", - "thiserror 2.0.8", + "thiserror 2.0.11", "tokio", "tracing", "url", @@ -468,7 +468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2269fd635f7b505f27c63a3cb293148cd02301efce4c8bdd9ff54fbfc4a20e23" dependencies = [ "alloy-json-rpc", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-transport", "bimap", "futures 0.3.31", @@ -482,9 +482,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f542548a609dca89fcd72b3b9f355928cf844d4363c5eed9c5273a3dd225e097" +checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" dependencies = [ "alloy-rlp-derive", "arrayvec 0.7.6", @@ -493,13 +493,13 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a833d97bf8a5f0f878daf2c8451fff7de7f9de38baa5a45d936ec718d81255a" +checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -509,7 +509,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d06a292b37e182e514903ede6e623b9de96420e8109ce300da288a96d88b7e4b" dependencies = [ "alloy-json-rpc", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-pubsub", "alloy-transport", "alloy-transport-http", @@ -534,7 +534,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9383845dd924939e7ab0298bbfe231505e20928907d7905aa3bf112287305e06" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-rpc-types-trace", @@ -559,7 +559,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "358d6a8d7340b9eb1a7589a6c1fb00df2c9b26e90737fa5ed0108724dd8dac2c" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "serde", ] @@ -571,7 +571,7 @@ checksum = "4a5f821f30344862a0b6eb9a1c2eb91dfb2ff44c7489f37152a526cdcab79264" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rlp", "alloy-serde", "derive_more 1.0.0", @@ -589,14 +589,14 @@ dependencies = [ "alloy-consensus-any", "alloy-eips", "alloy-network-primitives", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rlp", "alloy-serde", - "alloy-sol-types 0.8.18", + "alloy-sol-types 0.8.21", "itertools 0.13.0", "serde", "serde_json", - "thiserror 2.0.8", + "thiserror 2.0.11", ] [[package]] @@ -605,12 +605,12 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd38207e056cc7d1372367fbb4560ddf9107cbd20731743f641246bf0dede149" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rpc-types-eth", "alloy-serde", "serde", "serde_json", - "thiserror 2.0.8", + "thiserror 2.0.11", ] [[package]] @@ -619,7 +619,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae0465c71d4dced7525f408d84873aeebb71faf807d22d74c4a426430ccd9b55" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "serde", "serde_json", ] @@ -630,12 +630,12 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bfa395ad5cc952c82358d31e4c68b27bf4a89a5456d9b27e226e77dac50e4ff" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "async-trait", "auto_impl", "elliptic-curve", "k256", - "thiserror 2.0.8", + "thiserror 2.0.11", ] [[package]] @@ -646,12 +646,12 @@ checksum = "fbdc63ce9eda1283fcbaca66ba4a414b841c0e3edbeef9c86a71242fc9e84ccc" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-signer", "async-trait", "k256", "rand", - "thiserror 2.0.8", + "thiserror 2.0.11", ] [[package]] @@ -666,49 +666,49 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "syn-solidity 0.4.2", "tiny-keccak", ] [[package]] name = "alloy-sol-macro" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07b74d48661ab2e4b50bb5950d74dbff5e61dd8ed03bb822281b706d54ebacb" +checksum = "a2708e27f58d747423ae21d31b7a6625159bd8d867470ddd0256f396a68efa11" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19cc9c7f20b90f9be1a8f71a3d8e283a43745137b0837b1a1cb13159d37cad72" +checksum = "c6b7984d7e085dec382d2c5ef022b533fcdb1fe6129200af30ebf5afddb6a361" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck 0.5.0", - "indexmap 2.7.0", + "indexmap 2.7.1", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.90", - "syn-solidity 0.8.18", + "syn 2.0.98", + "syn-solidity 0.8.21", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713b7e6dfe1cb2f55c80fb05fd22ed085a1b4e48217611365ed0ae598a74c6ac" +checksum = "33d6a9fc4ed1a3c70bdb2357bec3924551c1a59f24e5a04a74472c755b37f87d" dependencies = [ "alloy-json-abi", "const-hex", @@ -717,15 +717,15 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.90", - "syn-solidity 0.8.18", + "syn 2.0.98", + "syn-solidity 0.8.21", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eda2711ab2e1fb517fc6e2ffa9728c9a232e296d16810810e6957b781a1b8bc" +checksum = "1b1b3e9a48a6dd7bb052a111c8d93b5afc7956ed5e2cb4177793dc63bb1d2a36" dependencies = [ "serde", "winnow", @@ -745,13 +745,13 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b478bc9c0c4737a04cd976accde4df7eba0bdc0d90ad6ff43d58bc93cf79c1" +checksum = "6044800da35c38118fd4b98e18306bd3b91af5dedeb54c1b768cf1b4fb68f549" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.8.18", - "alloy-sol-macro 0.8.18", + "alloy-primitives 0.8.21", + "alloy-sol-macro 0.8.21", "const-hex", "serde", ] @@ -768,7 +768,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.8", + "thiserror 2.0.11", "tokio", "tower 0.5.2", "tracing", @@ -820,7 +820,7 @@ dependencies = [ "alloy-transport", "futures 0.3.31", "http 1.2.0", - "rustls 0.23.20", + "rustls 0.23.22", "serde_json", "tokio", "tokio-tungstenite 0.24.0", @@ -830,11 +830,11 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6917c79e837aa7b77b7a6dae9f89cbe15313ac161c4d3cfaf8909ef21f3d22d8" +checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" dependencies = [ - "alloy-primitives 0.8.18", + "alloy-primitives 0.8.21", "alloy-rlp", "arrayvec 0.7.6", "derive_more 1.0.0", @@ -915,19 +915,20 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "approx" @@ -949,7 +950,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1436,7 +1437,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "synstructure 0.13.1", ] @@ -1459,7 +1460,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1582,7 +1583,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.42", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", @@ -1594,7 +1595,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 5.3.1", + "event-listener 5.4.0", "event-listener-strategy", "pin-project-lite", ] @@ -1623,9 +1624,9 @@ dependencies = [ "async-task", "blocking", "cfg-if", - "event-listener 5.3.1", + "event-listener 5.4.0", "futures-lite", - "rustix 0.38.42", + "rustix 0.38.44", "tracing", ] @@ -1641,7 +1642,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.42", + "rustix 0.38.44", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -1666,7 +1667,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1677,13 +1678,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1744,13 +1745,13 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1775,7 +1776,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object 0.36.5", + "object 0.36.7", "rustc-demangle", "windows-targets 0.52.6", ] @@ -1874,13 +1875,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.25", + "prettyplease 0.2.29", "proc-macro2", "quote", "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -1900,7 +1901,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "bit-vec", + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", ] [[package]] @@ -1909,6 +1919,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitcoin-internals" version = "0.2.0" @@ -1949,9 +1965,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "bitvec" @@ -1999,9 +2015,9 @@ dependencies = [ [[package]] name = "blake2b_simd" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" dependencies = [ "arrayref", "arrayvec 0.7.6", @@ -2010,9 +2026,9 @@ dependencies = [ [[package]] name = "blake2s_simd" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" +checksum = "e90f7deecfac93095eb874a40febd69427776e24e1bd7f87f33ac62d6f0174df" dependencies = [ "arrayref", "arrayvec 0.7.6", @@ -2380,9 +2396,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "regex-automata 0.4.9", @@ -2400,9 +2416,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byte-slice-cast" @@ -2418,9 +2434,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" [[package]] name = "byteorder" @@ -2430,9 +2446,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" dependencies = [ "serde", ] @@ -2509,7 +2525,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.24", + "semver 1.0.25", "serde", "serde_json", "thiserror 1.0.69", @@ -2523,7 +2539,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.24", + "semver 1.0.25", "serde", "serde_json", "thiserror 1.0.69", @@ -2543,9 +2559,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.4" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" dependencies = [ "jobserver", "libc", @@ -2722,9 +2738,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" dependencies = [ "clap_builder", "clap_derive", @@ -2732,9 +2748,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -2745,14 +2761,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -2862,12 +2878,11 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.3" +version = "7.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9" +checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" dependencies = [ - "strum 0.26.3", - "strum_macros 0.26.4", + "unicode-segmentation", "unicode-width 0.2.0", ] @@ -2939,11 +2954,31 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "tiny-keccak", ] +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -3013,9 +3048,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -3214,9 +3249,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-bigint" @@ -3367,7 +3402,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3600,14 +3635,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "cxx" -version = "1.0.135" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d44ff199ff93242c3afe480ab588d544dd08d72e92885e152ffebc670f076ad" +checksum = "dc49567e08c72902f4cbc7242ee8d874ec9cbe97fbabf77b4e0e1f447513e13a" dependencies = [ "cc", "cxxbridge-cmd", @@ -3619,47 +3654,47 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.135" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fd8f17ad454fc1e4f4ab83abffcc88a532e90350d3ffddcb73030220fcbd52" +checksum = "fe46b5309c99e9775e7a338c98e4097455f52db5b684fd793ca22848fde6e371" dependencies = [ "cc", "codespan-reporting", "proc-macro2", "quote", "scratch", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "cxxbridge-cmd" -version = "1.0.135" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4717c9c806a9e07fdcb34c84965a414ea40fafe57667187052cf1eb7f5e8a8a9" +checksum = "4315c4ce8d23c26d87f2f83698725fd5718d8e6ace4a9093da2664d23294d372" dependencies = [ "clap", "codespan-reporting", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "cxxbridge-flags" -version = "1.0.135" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f6515329bf3d98f4073101c7866ff2bec4e635a13acb82e3f3753fff0bf43cb" +checksum = "f55d69deb3a92f610a60ecc524a72c7374b6dc822f8fb7bb4e5d9473f10530c4" [[package]] name = "cxxbridge-macro" -version = "1.0.135" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb93e6a7ce8ec985c02bbb758237a31598b340acbbc3c19c5a4fa6adaaac92ab" +checksum = "5bee7a1d9b5091462002c2b8de2a4ed0f0fde011d503cc272633f66075bd5141" dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3683,7 +3718,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3694,7 +3729,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3726,15 +3761,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" [[package]] name = "data-encoding-macro" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" +checksum = "9f9724adfcf41f45bf652b3995837669d73c4d49a1b5ac1ff82905ac7d9b5558" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -3742,12 +3777,12 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" +checksum = "18e4fdb82bd54a12e42fb58a800dcae6b9e13982238ce2296dc3570b92148e1f" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.98", ] [[package]] @@ -3823,7 +3858,7 @@ checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3834,7 +3869,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3845,20 +3880,20 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "derive_more" -version = "0.99.18" +version = "0.99.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -3879,7 +3914,7 @@ dependencies = [ "convert_case 0.6.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "unicode-xid", ] @@ -3988,7 +4023,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4012,9 +4047,9 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.90", + "syn 2.0.98", "termcolor", - "toml 0.8.19", + "toml 0.8.20", "walkdir", ] @@ -4065,9 +4100,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "dyn-clonable" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +checksum = "a36efbb9bfd58e1723780aa04b61aba95ace6a05d9ffabfdb0b43672552f0805" dependencies = [ "dyn-clonable-impl", "dyn-clone", @@ -4075,20 +4110,20 @@ dependencies = [ [[package]] name = "dyn-clonable-impl" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +checksum = "7e8671d54058979a37a26f3511fbf8d198ba1aa35ffb202c42587d918d77213a" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.98", ] [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" [[package]] name = "ecdsa" @@ -4152,7 +4187,7 @@ source = "git+https://github.com/webb-tools/Ed448-Goldilocks#5af06c81541ed19e245 dependencies = [ "elliptic-curve", "rand_core", - "serdect 0.3.0-rc.0", + "serdect 0.3.0", "sha3", "subtle 2.6.1", "zeroize", @@ -4179,7 +4214,7 @@ dependencies = [ "enum-ordinalize 4.3.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4290,7 +4325,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4303,7 +4338,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4323,27 +4358,27 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "enumflags2" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4354,7 +4389,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4549,15 +4584,15 @@ dependencies = [ "ethers-core", "ethers-etherscan", "eyre", - "prettyplease 0.2.25", + "prettyplease 0.2.29", "proc-macro2", "quote", "regex", "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.90", - "toml 0.8.19", + "syn 2.0.98", + "toml 0.8.20", "walkdir", ] @@ -4574,7 +4609,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4600,7 +4635,7 @@ dependencies = [ "serde", "serde_json", "strum 0.26.3", - "syn 2.0.90", + "syn 2.0.98", "tempfile", "thiserror 1.0.69", "tiny-keccak", @@ -4616,7 +4651,7 @@ dependencies = [ "chrono", "ethers-core", "reqwest 0.11.27", - "semver 1.0.24", + "semver 1.0.25", "serde", "serde_json", "thiserror 1.0.69", @@ -4725,7 +4760,7 @@ dependencies = [ "path-slash", "rayon", "regex", - "semver 1.0.24", + "semver 1.0.25", "serde", "serde_json", "solang-parser", @@ -4746,9 +4781,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -4761,7 +4796,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ - "event-listener 5.3.1", + "event-listener 5.4.0", "pin-project-lite", ] @@ -4880,10 +4915,10 @@ dependencies = [ "blake2 0.10.6", "file-guard", "fs-err", - "prettyplease 0.2.25", + "prettyplease 0.2.29", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -4925,17 +4960,6 @@ dependencies = [ "bytes", ] -[[package]] -name = "fastrlp" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" -dependencies = [ - "arrayvec 0.7.6", - "auto_impl", - "bytes", -] - [[package]] name = "fc-api" version = "1.0.0-dev" @@ -5314,7 +5338,7 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "fork-tree" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", ] @@ -5386,7 +5410,7 @@ dependencies = [ "ethereum", "ethereum-types", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "parity-scale-codec", ] @@ -5396,7 +5420,7 @@ version = "3.0.0-dev" source = "git+https://github.com/paritytech/frontier.git?branch=stable2407#2e219e17a526125da003e64ef22ec037917083fa" dependencies = [ "evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "num_enum", "parity-scale-codec", "scale-info", @@ -5426,7 +5450,7 @@ name = "fp-self-contained" version = "1.0.0-dev" source = "git+https://github.com/paritytech/frontier.git?branch=stable2407#2e219e17a526125da003e64ef22ec037917083fa" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "parity-scale-codec", "scale-info", "serde", @@ -5451,10 +5475,10 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "frame-benchmarking" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ - "frame-support 37.0.1", - "frame-support-procedural 30.0.4 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", + "frame-support 37.1.0", + "frame-support-procedural 30.0.5", "frame-system 37.1.0", "linregress", "log", @@ -5479,7 +5503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01bdd47c2d541b38bd892da647d1e972c9d85b4ecd7094ad64f7600175da54d" dependencies = [ "frame-support 38.2.0", - "frame-support-procedural 30.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "frame-support-procedural 30.0.6", "frame-system 38.0.0", "linregress", "log", @@ -5500,7 +5524,7 @@ dependencies = [ [[package]] name = "frame-benchmarking-cli" version = "42.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "Inflector", "array-bytes", @@ -5508,7 +5532,7 @@ dependencies = [ "clap", "comfy-table", "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "gethostname", "handlebars", @@ -5585,27 +5609,27 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "frame-election-provider-solution-type" version = "14.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "frame-election-provider-support" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-election-provider-solution-type 14.0.1 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -5635,10 +5659,10 @@ dependencies = [ [[package]] name = "frame-executive" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "aquamarine", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "frame-try-runtime 0.43.0", "log", @@ -5696,11 +5720,11 @@ dependencies = [ [[package]] name = "frame-metadata-hash-extension" version = "0.5.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "docify", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -5726,8 +5750,8 @@ dependencies = [ [[package]] name = "frame-support" -version = "37.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +version = "37.1.0" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "aquamarine", "array-bytes", @@ -5735,7 +5759,7 @@ dependencies = [ "docify", "environmental", "frame-metadata 16.0.0", - "frame-support-procedural 30.0.4 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", + "frame-support-procedural 30.0.5", "impl-trait-for-tuples", "k256", "log", @@ -5777,7 +5801,7 @@ dependencies = [ "docify", "environmental", "frame-metadata 16.0.0", - "frame-support-procedural 30.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "frame-support-procedural 30.0.6", "impl-trait-for-tuples", "k256", "log", @@ -5809,53 +5833,54 @@ dependencies = [ [[package]] name = "frame-support-procedural" -version = "30.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e8f9b6bc1517a6fcbf0b2377e5c8c6d39f5bb7862b191a59a9992081d63972d" +version = "30.0.5" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "Inflector", "cfg-expr", "derive-syn-parse", "expander", - "frame-support-procedural-tools 13.0.1", + "frame-support-procedural-tools 13.0.0", "itertools 0.11.0", "macro_magic", "proc-macro-warning 1.0.2", "proc-macro2", "quote", - "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.90", + "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", + "syn 2.0.98", ] [[package]] name = "frame-support-procedural" -version = "30.0.4" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +version = "30.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da784d943f2a945be923ab081a7c0837355b38045c50945d7ec1a138e2f3c52" dependencies = [ "Inflector", "cfg-expr", "derive-syn-parse", + "docify", "expander", - "frame-support-procedural-tools 13.0.0", + "frame-support-procedural-tools 13.0.1", "itertools 0.11.0", "macro_magic", "proc-macro-warning 1.0.2", "proc-macro2", "quote", - "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", - "syn 2.0.90", + "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 2.0.98", ] [[package]] name = "frame-support-procedural-tools" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-support-procedural-tools-derive 12.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -5868,7 +5893,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -5879,27 +5904,27 @@ checksum = "ed971c6435503a099bdac99fe4c5bea08981709e5b5a0a8535a1856f48561191" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "frame-support-procedural-tools-derive" version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "frame-system" version = "37.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "cfg-if", "docify", - "frame-support 37.0.1", + "frame-support 37.1.0", "log", "parity-scale-codec", "scale-info", @@ -5936,10 +5961,10 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -5976,7 +6001,7 @@ dependencies = [ [[package]] name = "frame-system-rpc-runtime-api" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "parity-scale-codec", @@ -5986,9 +6011,9 @@ dependencies = [ [[package]] name = "frame-try-runtime" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "parity-scale-codec", "sp-api 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "sp-runtime 39.0.3", @@ -6008,9 +6033,9 @@ dependencies = [ [[package]] name = "frost-core" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5afd375261c34d31ff24dad068382f4bc3c95010c919d4fb8d483dc3d85c023" +checksum = "1858230cabb6792a5020daf4b0074f57b7d1e2a520ac544c77f102babee62ff4" dependencies = [ "byteorder", "const-crc32-nostd", @@ -6018,9 +6043,9 @@ dependencies = [ "derive-getters", "document-features", "hex", - "itertools 0.13.0", + "itertools 0.14.0", "rand_core", - "thiserror 1.0.69", + "thiserror 2.0.11", "thiserror-nostd-notrait", "visibility", "zeroize", @@ -6028,9 +6053,9 @@ dependencies = [ [[package]] name = "frost-ed25519" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4186731878c57b4e4d5d1103c8e0d2a827d3cb63cf577826ce29d52c34be7d39" +checksum = "c350ac3d0463a009a061aba12b67920acee94338951c849bb4c492d55223dece" dependencies = [ "curve25519-dalek", "document-features", @@ -6055,9 +6080,9 @@ dependencies = [ [[package]] name = "frost-p256" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235c7f29e912d63953881fc9ee942922c0f63e97ce011e3af8772ced55db4fa6" +checksum = "aef561eda6f686941c6082c3ccef8b0c2f44eb84cd951b6a660f98c4d9348ad4" dependencies = [ "document-features", "frost-core", @@ -6082,9 +6107,9 @@ dependencies = [ [[package]] name = "frost-rerandomized" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9d77595060546b53543d96b83dbeacaf3907e40a89763a8bb22124812b0cb6" +checksum = "e8a3b10d9c1e9f298522510940b5b8c3d55040420517ec8d2bb86c4c2d1ae3ee" dependencies = [ "derive-getters", "document-features", @@ -6095,9 +6120,9 @@ dependencies = [ [[package]] name = "frost-ristretto255" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3f0e065fb99f6abb96b58bd63fd7a6255efe37ee9672d97a7404ac644e54a7" +checksum = "0dd77a6e22b83079654472bbabc7a0b1197116b80bf01d15b76f132dc9fe9f3a" dependencies = [ "curve25519-dalek", "document-features", @@ -6109,9 +6134,9 @@ dependencies = [ [[package]] name = "frost-secp256k1" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52edea5a88d11135b2bf069f03e756d405d39c5a1c9c9f4d6147505948d7fff7" +checksum = "2d4890a0cdd6897a4af81016ec3299e4eb3a459d6468933d2a385302241662b5" dependencies = [ "document-features", "frost-core", @@ -6238,9 +6263,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" dependencies = [ "fastrand", "futures-core", @@ -6267,7 +6292,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -6358,9 +6383,9 @@ dependencies = [ [[package]] name = "generic-ec" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac0dbefcc2e3240d740f62913ba9b1176b4e50bb14218093baa140b01406f44" +checksum = "8de1099ac0b4d87261d67ff5d4ed400af617a1da40b58908d759b9cf5fd8ed27" dependencies = [ "generic-ec-core", "generic-ec-curves", @@ -6376,9 +6401,9 @@ dependencies = [ [[package]] name = "generic-ec-core" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c0350c4b769e9d6cb9af9300c30ad28bcb665bc351a2197eafcb07b0dd4318b" +checksum = "dcba5fdf70cc3ce5805c487f8523b4ceeb32e8ec5237c71ffd93c1ca47a97fee" dependencies = [ "generic-array 0.14.7", "rand_core", @@ -6389,9 +6414,9 @@ dependencies = [ [[package]] name = "generic-ec-curves" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b9342a074a45ff3ec1ffe919b41f8ed7b84a29b2b2f1dd8d8dbdfc4c1055c6" +checksum = "4c7c6d23001a5eb60eec2b785a63d2ca965fdfbaf3314b3b46df047398369e28" dependencies = [ "elliptic-curve", "generic-ec-core", @@ -6423,10 +6448,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + [[package]] name = "getrandom_or_panic" version = "0.0.3" @@ -6476,9 +6513,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "gloo-net" @@ -6569,7 +6606,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.7.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -6588,7 +6625,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.2.0", - "indexmap 2.7.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -6885,9 +6922,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -6927,9 +6964,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -6970,7 +7007,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "native-tls", "tokio", @@ -6989,7 +7026,7 @@ dependencies = [ "futures-util", "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.2", + "hyper 1.6.0", "pin-project-lite", "socket2 0.5.8", "tokio", @@ -7135,7 +7172,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -7291,7 +7328,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -7332,9 +7369,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -7420,19 +7457,19 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7477,6 +7514,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -7514,9 +7560,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -7538,31 +7584,31 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5c71d8c1a731cc4227c2f698d377e7848ca12c8a48866fc5e6951c43a4db843" +checksum = "834af00800e962dee8f7bfc0f60601de215e73e78e5497d733a2919da837d3c8" dependencies = [ "jsonrpsee-client-transport", - "jsonrpsee-core 0.24.7", - "jsonrpsee-types 0.24.7", + "jsonrpsee-core 0.24.8", + "jsonrpsee-types 0.24.8", "jsonrpsee-wasm-client", "jsonrpsee-ws-client", ] [[package]] name = "jsonrpsee-client-transport" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548125b159ba1314104f5bb5f38519e03a41862786aa3925cf349aae9cdd546e" +checksum = "def0fd41e2f53118bd1620478d12305b2c75feef57ea1f93ef70568c98081b7e" dependencies = [ "base64 0.22.1", "futures-channel", "futures-util", "gloo-net", "http 1.2.0", - "jsonrpsee-core 0.24.7", + "jsonrpsee-core 0.24.8", "pin-project", - "rustls 0.23.20", + "rustls 0.23.22", "rustls-pki-types", "rustls-platform-verifier", "soketto", @@ -7601,16 +7647,16 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2882f6f8acb9fdaec7cefc4fd607119a9bd709831df7d7672a1d3b644628280" +checksum = "76637f6294b04e747d68e69336ef839a3493ca62b35bf488ead525f7da75c5bb" dependencies = [ "async-trait", "futures-timer", "futures-util", - "jsonrpsee-types 0.24.7", + "jsonrpsee-types 0.24.8", "pin-project", - "rustc-hash 2.1.0", + "rustc-hash 2.1.1", "serde", "serde_json", "thiserror 1.0.69", @@ -7630,7 +7676,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -7644,7 +7690,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "jsonrpsee-core 0.23.2", "jsonrpsee-types 0.23.2", @@ -7676,9 +7722,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1" +checksum = "ddb81adb1a5ae9182df379e374a79e24e992334e7346af4d065ae5b2acb8d4c6" dependencies = [ "http 1.2.0", "serde", @@ -7688,25 +7734,25 @@ dependencies = [ [[package]] name = "jsonrpsee-wasm-client" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01cd500915d24ab28ca17527e23901ef1be6d659a2322451e1045532516c25" +checksum = "42e41af42ca39657313748174d02766e5287d3a57356f16756dbd8065b933977" dependencies = [ "jsonrpsee-client-transport", - "jsonrpsee-core 0.24.7", - "jsonrpsee-types 0.24.7", + "jsonrpsee-core 0.24.8", + "jsonrpsee-types 0.24.8", ] [[package]] name = "jsonrpsee-ws-client" -version = "0.24.7" +version = "0.24.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe322e0896d0955a3ebdd5bf813571c53fea29edd713bc315b76620b327e86d" +checksum = "6f4f3642a292f5b76d8a16af5c88c16a0860f2ccc778104e5c848b28183d9538" dependencies = [ "http 1.2.0", "jsonrpsee-client-transport", - "jsonrpsee-core 0.24.7", - "jsonrpsee-types 0.24.7", + "jsonrpsee-core 0.24.8", + "jsonrpsee-types 0.24.8", "url", ] @@ -7814,7 +7860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas", - "bit-set", + "bit-set 0.5.3", "ena", "itertools 0.11.0", "lalrpop-util", @@ -7876,9 +7922,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" @@ -7906,7 +7952,7 @@ dependencies = [ "either", "futures 0.3.31", "futures-timer", - "getrandom", + "getrandom 0.2.15", "instant", "libp2p-allow-block-list", "libp2p-connection-limits", @@ -8227,7 +8273,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -8336,7 +8382,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", "redox_syscall 0.5.8", ] @@ -8417,9 +8463,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.20" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" dependencies = [ "cc", "pkg-config", @@ -8443,9 +8489,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linked_hash_set" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" +checksum = "bae85b5be22d9843c80e5fc80e9b64c8a3b1f98f867c709956eca3efff4e92e2" dependencies = [ "linked-hash-map", ] @@ -8467,9 +8513,9 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lioness" @@ -8503,7 +8549,7 @@ dependencies = [ "futures 0.3.31", "futures-timer", "hex-literal 0.4.1", - "indexmap 2.7.0", + "indexmap 2.7.1", "libc", "mockall 0.12.1", "multiaddr 0.17.1", @@ -8562,9 +8608,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "lru" @@ -8595,9 +8641,9 @@ dependencies = [ [[package]] name = "lz4" -version = "1.28.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" dependencies = [ "lz4-sys", ] @@ -8630,7 +8676,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -8644,7 +8690,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -8655,7 +8701,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -8666,7 +8712,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -8722,7 +8768,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.42", + "rustix 0.38.44", ] [[package]] @@ -8801,9 +8847,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -8815,7 +8861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -8870,7 +8916,7 @@ dependencies = [ "fragile", "lazy_static", "mockall_derive 0.12.1", - "predicates 3.1.2", + "predicates 3.1.3", "predicates-tree", ] @@ -8895,7 +8941,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -9063,9 +9109,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ "libc", "log", @@ -9117,17 +9163,16 @@ dependencies = [ [[package]] name = "netlink-proto" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b33524dc0968bfad349684447bfce6db937a9ac3332a1fe60c0c5a5ce63f21" +checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60" dependencies = [ "bytes", "futures 0.3.31", "log", "netlink-packet-core", "netlink-sys", - "thiserror 1.0.69", - "tokio", + "thiserror 2.0.11", ] [[package]] @@ -9286,7 +9331,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -9368,7 +9413,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -9407,9 +9452,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -9434,9 +9479,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "oorandom" @@ -9483,11 +9528,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "foreign-types", "libc", @@ -9504,14 +9549,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" @@ -9524,9 +9569,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", @@ -9574,12 +9619,12 @@ name = "pallet-airdrop-claims" version = "1.2.8" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "libsecp256k1", "log", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-vesting 37.0.0", "parity-scale-codec", @@ -9703,10 +9748,10 @@ dependencies = [ [[package]] name = "pallet-assets" version = "39.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "impl-trait-for-tuples", "log", @@ -9800,9 +9845,9 @@ dependencies = [ [[package]] name = "pallet-authorship" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "impl-trait-for-tuples", "parity-scale-codec", @@ -9827,10 +9872,10 @@ dependencies = [ [[package]] name = "pallet-babe" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "pallet-authorship 37.0.0", @@ -9874,16 +9919,16 @@ dependencies = [ [[package]] name = "pallet-bags-list" version = "36.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "aquamarine", "docify", "frame-benchmarking 37.0.0", "frame-election-provider-support 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "parity-scale-codec", "scale-info", "sp-core 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -9916,12 +9961,12 @@ dependencies = [ [[package]] name = "pallet-balances" -version = "38.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +version = "38.0.1" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -9951,7 +9996,7 @@ version = "1.0.0" source = "git+https://github.com/paritytech/frontier.git?branch=stable2407#2e219e17a526125da003e64ef22ec037917083fa" dependencies = [ "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -10007,11 +10052,11 @@ dependencies = [ [[package]] name = "pallet-bounties" -version = "36.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +version = "36.0.1" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "pallet-treasury 36.0.1", @@ -10148,13 +10193,13 @@ dependencies = [ [[package]] name = "pallet-child-bounties" version = "36.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", - "pallet-bounties 36.0.0", + "pallet-bounties 36.0.1", "pallet-treasury 36.0.1", "parity-scale-codec", "scale-info", @@ -10205,10 +10250,10 @@ dependencies = [ [[package]] name = "pallet-collective" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -10327,7 +10372,7 @@ checksum = "3170e2f4a3d95f2ace274b703a72630294f0a27c687a4adbad9590e2b3e5fe82" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -10397,10 +10442,10 @@ dependencies = [ [[package]] name = "pallet-democracy" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -10452,7 +10497,7 @@ source = "git+https://github.com/paritytech/frontier.git?branch=stable2407#2e219 dependencies = [ "fp-dynamic-fee", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -10463,11 +10508,11 @@ dependencies = [ [[package]] name = "pallet-election-provider-multi-phase" version = "36.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", "frame-election-provider-support 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "pallet-election-provider-support-benchmarking 36.0.0", @@ -10508,7 +10553,7 @@ dependencies = [ [[package]] name = "pallet-election-provider-support-benchmarking" version = "36.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", "frame-election-provider-support 37.0.0", @@ -10535,10 +10580,10 @@ dependencies = [ [[package]] name = "pallet-elections-phragmen" version = "38.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -10582,7 +10627,7 @@ dependencies = [ "fp-evm", "fp-rpc", "fp-storage", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "pallet-evm", "parity-scale-codec", @@ -10601,7 +10646,7 @@ dependencies = [ "fp-account", "fp-evm", "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hash-db", "hex-literal 0.4.1", @@ -10619,7 +10664,7 @@ name = "pallet-evm-chain-id" version = "1.0.0-dev" source = "git+https://github.com/paritytech/frontier.git?branch=stable2407#2e219e17a526125da003e64ef22ec037917083fa" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -10631,11 +10676,11 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "libsecp256k1", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "parity-scale-codec", @@ -10658,10 +10703,10 @@ dependencies = [ "derive_more 1.0.0", "evm", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "parity-scale-codec", @@ -10700,11 +10745,11 @@ dependencies = [ "derive_more 1.0.0", "evm", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "libsecp256k1", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "parity-scale-codec", @@ -10733,11 +10778,11 @@ version = "0.2.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "log", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-democracy 37.0.0", "pallet-evm", "pallet-preimage 37.0.0", @@ -10759,7 +10804,7 @@ version = "2.0.0-dev" source = "git+https://github.com/paritytech/frontier.git?branch=stable2407#2e219e17a526125da003e64ef22ec037917083fa" dependencies = [ "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "pallet-evm", "parity-scale-codec", "sp-runtime 39.0.3", @@ -10800,14 +10845,14 @@ dependencies = [ "fp-self-contained", "fp-storage", "frame-election-provider-support 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex", "hex-literal 0.4.1", "libsecp256k1", "num_enum", "pallet-assets 39.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-base-fee", "pallet-dynamic-fee", "pallet-ethereum", @@ -10820,9 +10865,11 @@ dependencies = [ "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", + "pallet-evm-precompile-staking", "pallet-multi-asset-delegation", "pallet-session 37.0.0", "pallet-staking 37.0.0", + "pallet-staking-reward-curve", "pallet-timestamp 36.0.1", "parity-scale-codec", "precompile-utils", @@ -10833,6 +10880,7 @@ dependencies = [ "smallvec", "sp-core 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "sp-io 38.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", + "sp-keyring 39.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "sp-keystore 0.40.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "sp-runtime 39.0.3", "sp-staking 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -10845,7 +10893,7 @@ name = "pallet-evm-precompile-multi-asset-delegation-fuzzer" version = "2.0.0" dependencies = [ "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "honggfuzz", "log", @@ -10865,10 +10913,10 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-preimage 37.0.0", "pallet-timestamp 36.0.1", @@ -10889,10 +10937,10 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-proxy 37.0.0", "pallet-timestamp 36.0.1", @@ -10913,11 +10961,11 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "log", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-scheduler 38.0.0", "pallet-timestamp 36.0.1", @@ -10948,7 +10996,7 @@ dependencies = [ "fp-self-contained", "fp-storage", "frame-election-provider-support 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex", "hex-literal 0.4.1", @@ -10956,12 +11004,13 @@ dependencies = [ "libsecp256k1", "num_enum", "pallet-assets 39.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-base-fee", "pallet-dynamic-fee", "pallet-ethereum", "pallet-evm", "pallet-evm-chain-id", + "pallet-evm-precompile-balances-erc20", "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", "pallet-evm-precompile-curve25519", @@ -10969,6 +11018,7 @@ dependencies = [ "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", + "pallet-evm-precompileset-assets-erc20", "pallet-multi-asset-delegation", "pallet-services", "pallet-session 37.0.0", @@ -11016,9 +11066,9 @@ dependencies = [ "derive_more 1.0.0", "fp-evm", "frame-election-provider-support 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-session 37.0.0", "pallet-staking 37.0.0", @@ -11045,11 +11095,11 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "pallet-assets 39.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-multi-asset-delegation", "pallet-tangle-lst", @@ -11074,11 +11124,11 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "log", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "parity-scale-codec", @@ -11098,12 +11148,12 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "k256", "log", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "parity-scale-codec", @@ -11122,12 +11172,12 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "log", "p256", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "parity-scale-codec", @@ -11147,13 +11197,13 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "generic-ec", "hex", "hex-literal 0.4.1", "log", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "parity-scale-codec", @@ -11174,7 +11224,7 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "frost-core", "frost-ed25519", @@ -11185,7 +11235,7 @@ dependencies = [ "frost-secp256k1", "frost-secp256k1-tr", "hex-literal 0.4.1", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "parity-scale-codec", @@ -11206,10 +11256,10 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "pallet-vesting 37.0.0", @@ -11231,12 +11281,12 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "libsecp256k1", "pallet-assets 39.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "parity-scale-codec", @@ -11258,12 +11308,12 @@ version = "0.1.0" dependencies = [ "derive_more 1.0.0", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex-literal 0.4.1", "libsecp256k1", "pallet-assets 39.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-evm", "pallet-timestamp 36.0.1", "parity-scale-codec", @@ -11319,10 +11369,10 @@ dependencies = [ [[package]] name = "pallet-grandpa" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "pallet-authorship 37.0.0", @@ -11367,7 +11417,7 @@ version = "1.0.0" source = "git+https://github.com/paritytech/frontier.git?branch=stable2407#2e219e17a526125da003e64ef22ec037917083fa" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "pallet-evm", "parity-scale-codec", @@ -11379,11 +11429,11 @@ dependencies = [ [[package]] name = "pallet-identity" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "enumflags2", "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -11412,10 +11462,10 @@ dependencies = [ [[package]] name = "pallet-im-online" version = "36.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "pallet-authorship 37.0.0", @@ -11451,10 +11501,10 @@ dependencies = [ [[package]] name = "pallet-indices" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -11484,9 +11534,9 @@ dependencies = [ [[package]] name = "pallet-insecure-randomness-collective-flip" version = "25.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "safe-mix", @@ -11632,7 +11682,7 @@ dependencies = [ "fp-storage", "frame-benchmarking 37.0.0", "frame-election-provider-support 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex", "hex-literal 0.4.1", @@ -11641,7 +11691,7 @@ dependencies = [ "log", "num_enum", "pallet-assets 39.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-base-fee", "pallet-dynamic-fee", "pallet-ethereum", @@ -11654,9 +11704,11 @@ dependencies = [ "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", + "pallet-proxy 37.0.0", "pallet-session 37.0.0", "pallet-staking 37.0.0", "pallet-timestamp 36.0.1", + "pallet-utility 37.0.1", "parity-scale-codec", "precompile-utils", "scale-info", @@ -11677,7 +11729,7 @@ dependencies = [ name = "pallet-multi-asset-delegation-fuzzer" version = "2.0.0" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "honggfuzz", "log", @@ -11691,10 +11743,10 @@ dependencies = [ [[package]] name = "pallet-multisig" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -11819,12 +11871,12 @@ dependencies = [ [[package]] name = "pallet-nomination-pools" version = "36.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "parity-scale-codec", "scale-info", "sp-core 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -11869,12 +11921,12 @@ dependencies = [ [[package]] name = "pallet-offences" version = "36.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "parity-scale-codec", "scale-info", "serde", @@ -11928,7 +11980,7 @@ name = "pallet-oracle" version = "1.1.0" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -11980,10 +12032,10 @@ dependencies = [ [[package]] name = "pallet-preimage" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -12013,10 +12065,10 @@ dependencies = [ [[package]] name = "pallet-proxy" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -12151,7 +12203,7 @@ dependencies = [ "polkavm-linker 0.10.0", "sp-runtime 39.0.5", "tempfile", - "toml 0.8.19", + "toml 0.8.20", ] [[package]] @@ -12197,7 +12249,7 @@ checksum = "b8aee42afa416be6324cf6650c137da9742f27dc7be3c7ed39ad9748baf3b9ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -12230,7 +12282,7 @@ dependencies = [ "fp-storage", "frame-benchmarking 37.0.0", "frame-election-provider-support 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex", "hex-literal 0.4.1", @@ -12239,7 +12291,7 @@ dependencies = [ "log", "num_enum", "pallet-assets 39.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-base-fee", "pallet-dynamic-fee", "pallet-ethereum", @@ -12367,11 +12419,11 @@ dependencies = [ [[package]] name = "pallet-scheduler" version = "38.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -12429,7 +12481,7 @@ dependencies = [ "fp-storage", "frame-benchmarking 37.0.0", "frame-election-provider-support 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex", "hex-literal 0.4.1", @@ -12439,12 +12491,13 @@ dependencies = [ "log", "num_enum", "pallet-assets 39.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-base-fee", "pallet-dynamic-fee", "pallet-ethereum", "pallet-evm", "pallet-evm-chain-id", + "pallet-evm-precompile-balances-erc20", "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", "pallet-evm-precompile-curve25519", @@ -12452,6 +12505,8 @@ dependencies = [ "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", + "pallet-evm-precompileset-assets-erc20", + "pallet-multi-asset-delegation", "pallet-session 37.0.0", "pallet-staking 37.0.0", "pallet-timestamp 36.0.1", @@ -12467,6 +12522,7 @@ dependencies = [ "sp-runtime 39.0.3", "sp-staking 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", + "sp-weights 31.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "tangle-primitives", ] @@ -12497,9 +12553,9 @@ dependencies = [ [[package]] name = "pallet-session" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "impl-trait-for-tuples", "log", @@ -12588,11 +12644,11 @@ dependencies = [ [[package]] name = "pallet-staking" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", "frame-election-provider-support 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "pallet-authorship 37.0.0", @@ -12632,12 +12688,12 @@ dependencies = [ [[package]] name = "pallet-staking-reward-curve" version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -12699,11 +12755,11 @@ dependencies = [ [[package]] name = "pallet-sudo" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -12732,11 +12788,11 @@ name = "pallet-tangle-lst" version = "25.0.0" dependencies = [ "cfg-if", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "pallet-assets 39.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-staking 37.0.0", "parity-scale-codec", "scale-info", @@ -12755,11 +12811,11 @@ version = "35.0.0" dependencies = [ "frame-benchmarking 37.0.0", "frame-election-provider-support 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "pallet-assets 39.0.0", "pallet-bags-list 36.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-staking 37.0.0", "pallet-staking-reward-curve", "pallet-tangle-lst", @@ -12777,11 +12833,11 @@ dependencies = [ [[package]] name = "pallet-timestamp" version = "36.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -12835,9 +12891,9 @@ dependencies = [ [[package]] name = "pallet-transaction-payment" version = "37.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -12866,7 +12922,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc" version = "40.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "jsonrpsee 0.23.2", "pallet-transaction-payment-rpc-runtime-api 37.0.0", @@ -12882,7 +12938,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "pallet-transaction-payment 37.0.1", "parity-scale-codec", @@ -12927,14 +12983,14 @@ dependencies = [ [[package]] name = "pallet-treasury" version = "36.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "impl-trait-for-tuples", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "parity-scale-codec", "scale-info", "serde", @@ -12964,13 +13020,13 @@ dependencies = [ [[package]] name = "pallet-tx-pause" version = "18.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-proxy 37.0.0", "pallet-utility 37.0.1", "parity-scale-codec", @@ -13014,10 +13070,10 @@ dependencies = [ [[package]] name = "pallet-utility" version = "37.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "parity-scale-codec", "scale-info", @@ -13045,10 +13101,10 @@ dependencies = [ [[package]] name = "pallet-vesting" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "log", "parity-scale-codec", @@ -13277,29 +13333,31 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.6.12" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" dependencies = [ "arrayvec 0.7.6", "bitvec", "byte-slice-cast", "bytes", + "const_format", "impl-trait-for-tuples", "parity-scale-codec-derive", + "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.6.12" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.98", ] [[package]] @@ -13491,7 +13549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.8", + "thiserror 2.0.11", "ucd-trie", ] @@ -13515,7 +13573,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -13536,7 +13594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.7.0", + "indexmap 2.7.1", ] [[package]] @@ -13560,80 +13618,71 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared 0.11.2", + "phf_shared", ] [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared 0.11.2", + "phf_shared", "rand", ] [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", - "phf_shared 0.11.2", + "phf_shared", "proc-macro2", "quote", - "syn 2.0.90", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", + "syn 2.0.98", ] [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 0.3.11", + "siphasher 1.0.1", ] [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -13721,7 +13770,7 @@ dependencies = [ [[package]] name = "polkadot-core-primitives" version = "15.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "scale-info", @@ -13736,7 +13785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52b5648a2e8ce1f9a0f8c41c38def670cefd91932cd793468e1a5b0b0b4e4af1" dependencies = [ "bounded-collections", - "derive_more 0.99.18", + "derive_more 0.99.19", "parity-scale-codec", "polkadot-core-primitives 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "scale-info", @@ -13749,10 +13798,10 @@ dependencies = [ [[package]] name = "polkadot-parachain-primitives" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "bounded-collections", - "derive_more 0.99.18", + "derive_more 0.99.19", "parity-scale-codec", "polkadot-core-primitives 15.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "scale-info", @@ -13887,7 +13936,7 @@ checksum = "1d4cdf181c2419b35c2cbde813da2d8ee777b69b4a6fa346b962d144e3521976" dependencies = [ "bitflags 1.3.2", "bitvec", - "derive_more 0.99.18", + "derive_more 0.99.19", "frame-benchmarking 38.0.0", "frame-support 38.2.0", "frame-system 38.0.0", @@ -13973,7 +14022,7 @@ dependencies = [ "frame-executive 38.0.0", "frame-metadata-hash-extension 0.6.0", "frame-support 38.2.0", - "frame-support-procedural 30.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "frame-support-procedural 30.0.6", "frame-system 38.0.0", "frame-system-benchmarking 38.0.0", "frame-system-rpc-runtime-api 34.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -14283,7 +14332,7 @@ dependencies = [ "polkavm-common 0.9.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -14295,7 +14344,7 @@ dependencies = [ "polkavm-common 0.10.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -14305,7 +14354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl 0.9.0", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -14315,7 +14364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9324fe036de37c17829af233b46ef6b5562d4a0c09bb7fdb9f8378856dee30cf" dependencies = [ "polkavm-derive-impl 0.10.0", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -14342,7 +14391,7 @@ dependencies = [ "gimli 0.28.1", "hashbrown 0.14.5", "log", - "object 0.36.5", + "object 0.36.7", "polkavm-common 0.10.0", "regalloc2 0.9.3", "rustc-demangle", @@ -14370,7 +14419,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.42", + "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] @@ -14436,11 +14485,11 @@ name = "precompile-utils" version = "0.1.0" source = "git+https://github.com/paritytech/frontier.git?branch=stable2407#2e219e17a526125da003e64ef22ec037917083fa" dependencies = [ - "derive_more 0.99.18", + "derive_more 0.99.19", "environmental", "evm", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex", "hex-literal 0.4.1", @@ -14467,7 +14516,7 @@ source = "git+https://github.com/paritytech/frontier.git?branch=stable2407#2e219 dependencies = [ "case", "num_enum", - "prettyplease 0.2.25", + "prettyplease 0.2.29", "proc-macro2", "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -14496,9 +14545,9 @@ dependencies = [ [[package]] name = "predicates" -version = "3.1.2" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ "anstyle", "predicates-core", @@ -14506,15 +14555,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] name = "predicates-tree" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ "predicates-core", "termtree", @@ -14532,12 +14581,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -14656,7 +14705,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -14667,7 +14716,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -14678,14 +14727,14 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -14724,18 +14773,18 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.6.0", + "bit-set 0.8.0", + "bit-vec 0.8.0", + "bitflags 2.8.0", "lazy_static", "num-traits", "rand", @@ -14802,11 +14851,11 @@ dependencies = [ "multimap 0.10.0", "once_cell", "petgraph", - "prettyplease 0.2.25", + "prettyplease 0.2.29", "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.90", + "syn 2.0.98", "tempfile", ] @@ -14833,7 +14882,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -14865,15 +14914,15 @@ dependencies = [ [[package]] name = "quanta" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773ce68d0bb9bc7ef20be3536ffe94e223e1f365bd374108b2659fac0c65cfe6" +checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -15005,9 +15054,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -15046,7 +15095,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -15079,11 +15128,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.2.0" +version = "11.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" +checksum = "c6928fa44c097620b706542d428957635951bade7143269085389d42c8a4927e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -15145,7 +15194,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -15154,7 +15203,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror 1.0.69", ] @@ -15176,7 +15225,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -15302,7 +15351,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-tls", "hyper-util", "ipnet", @@ -15372,7 +15421,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -15626,18 +15675,16 @@ dependencies = [ [[package]] name = "ruint" -version = "1.12.4" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", - "fastrlp 0.3.1", - "fastrlp 0.4.0", + "fastrlp", "num-bigint 0.4.6", - "num-integer", "num-traits", "parity-scale-codec", "primitive-types 0.12.2", @@ -15670,9 +15717,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc-hex" @@ -15704,7 +15751,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.24", + "semver 1.0.25", ] [[package]] @@ -15732,14 +15779,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.42" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", - "linux-raw-sys 0.4.14", + "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] @@ -15768,9 +15815,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" dependencies = [ "log", "once_cell", @@ -15826,9 +15873,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-platform-verifier" @@ -15841,13 +15888,13 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.20", + "rustls 0.23.22", "rustls-native-certs 0.7.3", "rustls-platform-verifier-android", "rustls-webpki 0.102.8", "security-framework", "security-framework-sys", - "webpki-roots 0.26.7", + "webpki-roots 0.26.8", "winapi", ] @@ -15880,9 +15927,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rusty-fork" @@ -15903,7 +15950,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b" dependencies = [ "byteorder", - "derive_more 0.99.18", + "derive_more 0.99.19", ] [[package]] @@ -15919,9 +15966,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "safe-mix" @@ -15934,9 +15981,9 @@ dependencies = [ [[package]] name = "safe_arch" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" dependencies = [ "bytemuck", ] @@ -15974,7 +16021,7 @@ dependencies = [ [[package]] name = "sc-allocator" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "log", "sp-core 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -15985,7 +16032,7 @@ dependencies = [ [[package]] name = "sc-basic-authorship" version = "0.44.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "futures 0.3.31", "futures-timer", @@ -16007,7 +16054,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.42.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "sp-api 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -16022,7 +16069,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "docify", @@ -16049,18 +16096,18 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "sc-cli" version = "0.46.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "chrono", @@ -16101,7 +16148,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "fnv", "futures 0.3.31", @@ -16128,7 +16175,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.44.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "hash-db", "kvdb", @@ -16154,7 +16201,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "futures 0.3.31", @@ -16178,7 +16225,7 @@ dependencies = [ [[package]] name = "sc-consensus-aura" version = "0.44.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "futures 0.3.31", @@ -16207,7 +16254,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe" version = "0.44.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "fork-tree", @@ -16243,7 +16290,7 @@ dependencies = [ [[package]] name = "sc-consensus-babe-rpc" version = "0.44.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "futures 0.3.31", "jsonrpsee 0.23.2", @@ -16265,7 +16312,7 @@ dependencies = [ [[package]] name = "sc-consensus-epochs" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "fork-tree", "parity-scale-codec", @@ -16278,7 +16325,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa" version = "0.29.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "ahash 0.8.11", "array-bytes", @@ -16322,7 +16369,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa-rpc" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "finality-grandpa", "futures 0.3.31", @@ -16342,7 +16389,7 @@ dependencies = [ [[package]] name = "sc-consensus-manual-seal" version = "0.45.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "assert_matches", "async-trait", @@ -16377,7 +16424,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "futures 0.3.31", @@ -16400,7 +16447,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.40.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", @@ -16461,7 +16508,7 @@ dependencies = [ [[package]] name = "sc-executor-common" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "polkavm 0.9.3", "sc-allocator 29.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -16486,7 +16533,7 @@ dependencies = [ [[package]] name = "sc-executor-polkavm" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "log", "polkavm 0.9.3", @@ -16516,7 +16563,7 @@ dependencies = [ [[package]] name = "sc-executor-wasmtime" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "anyhow", "cfg-if", @@ -16534,7 +16581,7 @@ dependencies = [ [[package]] name = "sc-informant" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "ansi_term", "futures 0.3.31", @@ -16551,7 +16598,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "33.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "parking_lot 0.12.3", @@ -16565,7 +16612,7 @@ dependencies = [ [[package]] name = "sc-mixnet" version = "0.14.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "arrayvec 0.7.6", @@ -16593,8 +16640,8 @@ dependencies = [ [[package]] name = "sc-network" -version = "0.44.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +version = "0.44.2" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "async-channel 1.9.0", @@ -16645,7 +16692,7 @@ dependencies = [ [[package]] name = "sc-network-common" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "bitflags 1.3.2", @@ -16663,7 +16710,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.44.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "ahash 0.8.11", "futures 0.3.31", @@ -16682,7 +16729,7 @@ dependencies = [ [[package]] name = "sc-network-light" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "async-channel 1.9.0", @@ -16703,7 +16750,7 @@ dependencies = [ [[package]] name = "sc-network-sync" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "async-channel 1.9.0", @@ -16740,7 +16787,7 @@ dependencies = [ [[package]] name = "sc-network-transactions" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "futures 0.3.31", @@ -16759,7 +16806,7 @@ dependencies = [ [[package]] name = "sc-network-types" version = "0.12.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "bs58 0.5.1", "ed25519-dalek", @@ -16776,7 +16823,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "39.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "bytes", @@ -16810,7 +16857,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.18.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -16819,7 +16866,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "39.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "futures 0.3.31", "jsonrpsee 0.23.2", @@ -16851,7 +16898,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "jsonrpsee 0.23.2", "parity-scale-codec", @@ -16871,14 +16918,14 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "16.0.2" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "forwarded-header-value", "futures 0.3.31", "governor", "http 1.2.0", "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "ip_network", "jsonrpsee 0.23.2", "log", @@ -16893,7 +16940,7 @@ dependencies = [ [[package]] name = "sc-rpc-spec-v2" version = "0.44.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "futures 0.3.31", @@ -16925,7 +16972,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.45.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "directories", @@ -16989,7 +17036,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.36.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "log", "parity-scale-codec", @@ -17000,9 +17047,9 @@ dependencies = [ [[package]] name = "sc-sysinfo" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ - "derive_more 0.99.18", + "derive_more 0.99.19", "futures 0.3.31", "libc", "log", @@ -17021,7 +17068,7 @@ dependencies = [ [[package]] name = "sc-telemetry" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "chrono", "futures 0.3.31", @@ -17041,7 +17088,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "ansi_term", "chrono", @@ -17071,18 +17118,18 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "sc-transaction-pool" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "futures 0.3.31", @@ -17109,7 +17156,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "futures 0.3.31", @@ -17125,7 +17172,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-channel 1.9.0", "futures 0.3.31", @@ -17165,7 +17212,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" dependencies = [ - "derive_more 0.99.18", + "derive_more 0.99.19", "parity-scale-codec", "scale-bits 0.6.0", "scale-type-resolver", @@ -17184,7 +17231,7 @@ dependencies = [ "scale-decode-derive", "scale-type-resolver", "smallvec", - "thiserror 2.0.8", + "thiserror 2.0.11", ] [[package]] @@ -17196,7 +17243,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -17211,7 +17258,7 @@ dependencies = [ "scale-encode-derive", "scale-type-resolver", "smallvec", - "thiserror 2.0.8", + "thiserror 2.0.11", ] [[package]] @@ -17224,7 +17271,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -17250,7 +17297,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -17272,8 +17319,8 @@ dependencies = [ "proc-macro2", "quote", "scale-info", - "syn 2.0.90", - "thiserror 2.0.8", + "syn 2.0.98", + "thiserror 2.0.11", ] [[package]] @@ -17291,7 +17338,7 @@ dependencies = [ "scale-encode", "scale-type-resolver", "serde", - "thiserror 2.0.8", + "thiserror 2.0.11", "yap", ] @@ -17306,9 +17353,9 @@ dependencies = [ [[package]] name = "schnellru" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" dependencies = [ "ahash 0.8.11", "cfg-if", @@ -17472,7 +17519,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation", "core-foundation-sys", "libc", @@ -17482,9 +17529,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -17519,9 +17566,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" dependencies = [ "serde", ] @@ -17555,9 +17602,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -17582,20 +17629,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -17643,7 +17690,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -17658,9 +17705,9 @@ dependencies = [ [[package]] name = "serdect" -version = "0.3.0-rc.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a504c8ee181e3e594d84052f983d60afe023f4d94d050900be18062bbbf7b58" +checksum = "f42f67da2385b51a5f9652db9c93d78aeaf7610bf5ec366080b6de810604af53" dependencies = [ "base16ct", "serde", @@ -17791,9 +17838,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" dependencies = [ "bstr", "unicode-segmentation", @@ -17801,9 +17848,9 @@ dependencies = [ [[package]] name = "similar-asserts" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe85670573cd6f0fa97940f26e7e6601213c3b0555246c24234131f88c5709e" +checksum = "9f08357795f0d604ea7d7130f22c74b03838c959bdb14adde3142aab4d18a293" dependencies = [ "console", "similar", @@ -17815,7 +17862,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cae9a3fcdadafb6d97f4c0e007e4247b114ee0f119f650c3cbf3a8b3a1479694" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -17826,13 +17873,13 @@ checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" [[package]] name = "simple_asn1" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint 0.4.6", "num-traits", - "thiserror 1.0.69", + "thiserror 2.0.11", "time", ] @@ -17927,10 +17974,10 @@ dependencies = [ "bs58 0.5.1", "chacha20", "crossbeam-queue", - "derive_more 0.99.18", + "derive_more 0.99.19", "ed25519-zebra", "either", - "event-listener 5.3.1", + "event-listener 5.4.0", "fnv", "futures-lite", "futures-util", @@ -17977,9 +18024,9 @@ dependencies = [ "base64 0.22.1", "blake2-rfc", "bs58 0.5.1", - "derive_more 0.99.18", + "derive_more 0.99.19", "either", - "event-listener 5.3.1", + "event-listener 5.4.0", "fnv", "futures-channel", "futures-lite", @@ -18439,7 +18486,7 @@ dependencies = [ [[package]] name = "sp-api" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "hash-db", @@ -18470,13 +18517,13 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "sp-api-proc-macro" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "Inflector", "blake2 0.10.6", @@ -18484,7 +18531,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -18503,7 +18550,7 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "38.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "scale-info", @@ -18531,7 +18578,7 @@ dependencies = [ [[package]] name = "sp-arithmetic" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "integer-sqrt", @@ -18569,7 +18616,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "sp-api 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "sp-inherents 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -18579,7 +18626,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "37.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "futures 0.3.31", "parity-scale-codec", @@ -18598,7 +18645,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.40.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "futures 0.3.31", @@ -18630,7 +18677,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" version = "0.40.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "parity-scale-codec", @@ -18665,7 +18712,7 @@ dependencies = [ [[package]] name = "sp-consensus-babe" version = "0.40.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "parity-scale-codec", @@ -18723,7 +18770,7 @@ dependencies = [ [[package]] name = "sp-consensus-grandpa" version = "21.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "finality-grandpa", "log", @@ -18752,7 +18799,7 @@ dependencies = [ [[package]] name = "sp-consensus-slots" version = "0.40.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "scale-info", @@ -18822,7 +18869,7 @@ dependencies = [ [[package]] name = "sp-core" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "bitflags 1.3.2", @@ -18912,7 +18959,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "blake2b_simd", "byteorder", @@ -18930,23 +18977,23 @@ checksum = "b85d0f1f1e44bd8617eb2a48203ee854981229e3e79e6f468c7175d5fd37489b" dependencies = [ "quote", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "sp-crypto-hashing-proc-macro" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "quote", "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "sp-database" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "kvdb", "parking_lot 0.12.3", @@ -18960,17 +19007,17 @@ checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -18987,7 +19034,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "environmental", "parity-scale-codec", @@ -18997,7 +19044,7 @@ dependencies = [ [[package]] name = "sp-genesis-builder" version = "0.15.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "scale-info", @@ -19036,7 +19083,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -19076,7 +19123,7 @@ dependencies = [ [[package]] name = "sp-io" version = "38.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "bytes", "docify", @@ -19113,7 +19160,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "39.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "sp-core 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "sp-runtime 39.0.3", @@ -19135,7 +19182,7 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.40.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", @@ -19156,7 +19203,7 @@ dependencies = [ [[package]] name = "sp-maybe-compressed-blob" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "thiserror 1.0.69", "zstd 0.12.4", @@ -19176,7 +19223,7 @@ dependencies = [ [[package]] name = "sp-metadata-ir" version = "0.7.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "frame-metadata 16.0.0", "parity-scale-codec", @@ -19198,7 +19245,7 @@ dependencies = [ [[package]] name = "sp-mixnet" version = "0.12.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "scale-info", @@ -19241,7 +19288,7 @@ dependencies = [ [[package]] name = "sp-npos-elections" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "scale-info", @@ -19265,7 +19312,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "sp-api 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "sp-core 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -19275,7 +19322,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "backtrace", "lazy_static", @@ -19295,7 +19342,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "32.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "rustc-hash 1.1.0", "serde", @@ -19305,7 +19352,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "39.0.3" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "either", @@ -19378,7 +19425,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -19405,26 +19452,26 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "sp-runtime-interface-proc-macro" version = "18.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "Inflector", "expander", "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "sp-session" version = "35.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "scale-info", @@ -19467,7 +19514,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -19515,7 +19562,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.43.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "hash-db", "log", @@ -19560,7 +19607,7 @@ dependencies = [ [[package]] name = "sp-statement-store" version = "18.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "aes-gcm", "curve25519-dalek", @@ -19590,7 +19637,7 @@ checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" [[package]] name = "sp-storage" @@ -19608,7 +19655,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "21.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "impl-serde 0.4.0", "parity-scale-codec", @@ -19633,7 +19680,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "parity-scale-codec", @@ -19645,7 +19692,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "tracing", @@ -19678,7 +19725,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "sp-api 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "sp-runtime 39.0.3", @@ -19702,7 +19749,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "34.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "async-trait", "parity-scale-codec", @@ -19740,7 +19787,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "ahash 0.8.11", "hash-db", @@ -19781,7 +19828,7 @@ dependencies = [ [[package]] name = "sp-version" version = "37.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "impl-serde 0.4.0", "parity-scale-codec", @@ -19804,24 +19851,24 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "sp-version-proc-macro" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "sp-wasm-interface" version = "21.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -19861,7 +19908,7 @@ dependencies = [ [[package]] name = "sp-weights" version = "31.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "bounded-collections", "parity-scale-codec", @@ -19948,7 +19995,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.7.0", + "indexmap 2.7.1", "log", "memchr", "native-tls", @@ -20087,7 +20134,7 @@ dependencies = [ [[package]] name = "staging-xcm" version = "14.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "bounded-collections", @@ -20125,9 +20172,9 @@ dependencies = [ [[package]] name = "staging-xcm-builder" version = "16.0.3" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "impl-trait-for-tuples", "log", @@ -20169,11 +20216,11 @@ dependencies = [ [[package]] name = "staging-xcm-executor" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "environmental", "frame-benchmarking 37.0.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "impl-trait-for-tuples", "parity-scale-codec", "scale-info", @@ -20222,9 +20269,9 @@ dependencies = [ [[package]] name = "starknet-crypto" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded22ccf4cb9e572ce3f77de6066af53560cd2520d508876c83bb1e6b29d5cbc" +checksum = "039a3bad70806b494c9e6b21c5238a6c8a373d66a26071859deb0ccca6f93634" dependencies = [ "crypto-bigint", "hex", @@ -20329,14 +20376,13 @@ dependencies = [ [[package]] name = "string_cache" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" dependencies = [ "new_debug_unreachable", - "once_cell", "parking_lot 0.12.3", - "phf_shared 0.10.0", + "phf_shared", "precomputed-hash", ] @@ -20384,7 +20430,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -20403,7 +20449,7 @@ dependencies = [ [[package]] name = "substrate-bip39" version = "0.6.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "hmac 0.12.1", "pbkdf2 0.12.2", @@ -20434,7 +20480,7 @@ checksum = "b285e7d183a32732fdc119f3d81b7915790191fad602b7c709ef247073c77a2e" [[package]] name = "substrate-frame-rpc-system" version = "38.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "docify", "frame-system-rpc-runtime-api 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -20454,10 +20500,10 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.17.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "http-body-util", - "hyper 1.5.2", + "hyper 1.6.0", "hyper-util", "log", "prometheus", @@ -20468,7 +20514,7 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "array-bytes", "build-helper", @@ -20488,7 +20534,7 @@ dependencies = [ "sp-version 37.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "strum 0.26.3", "tempfile", - "toml 0.8.19", + "toml 0.8.20", "walkdir", "wasm-opt", ] @@ -20509,7 +20555,7 @@ dependencies = [ "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.26.3", "tempfile", - "toml 0.8.19", + "toml 0.8.20", "walkdir", "wasm-opt", ] @@ -20538,10 +20584,10 @@ dependencies = [ "finito", "frame-metadata 18.0.0", "futures 0.3.31", - "getrandom", + "getrandom 0.2.15", "hex", "impl-serde 0.5.0", - "jsonrpsee 0.24.7", + "jsonrpsee 0.24.8", "parity-scale-codec", "polkadot-sdk", "primitive-types 0.13.1", @@ -20556,7 +20602,7 @@ dependencies = [ "subxt-lightclient", "subxt-macro", "subxt-metadata", - "thiserror 2.0.8", + "thiserror 2.0.11", "tokio", "tokio-util", "tracing", @@ -20571,7 +20617,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b622b426e571fdd86b08ad0bec4ef0e323d937bb56ff5edcfaf4716f50384ca" dependencies = [ - "getrandom", + "getrandom 0.2.15", "heck 0.5.0", "parity-scale-codec", "proc-macro2", @@ -20579,8 +20625,8 @@ dependencies = [ "scale-info", "scale-typegen", "subxt-metadata", - "syn 2.0.90", - "thiserror 2.0.8", + "syn 2.0.98", + "thiserror 2.0.11", ] [[package]] @@ -20609,7 +20655,7 @@ dependencies = [ "serde", "serde_json", "subxt-metadata", - "thiserror 2.0.8", + "thiserror 2.0.11", "tracing", ] @@ -20622,7 +20668,7 @@ dependencies = [ "futures 0.3.31", "futures-timer", "futures-util", - "getrandom", + "getrandom 0.2.15", "js-sys", "pin-project", "send_wrapper 0.6.0", @@ -20630,7 +20676,7 @@ dependencies = [ "serde_json", "smoldot", "smoldot-light", - "thiserror 2.0.8", + "thiserror 2.0.11", "tokio", "tokio-stream", "tracing", @@ -20653,7 +20699,7 @@ dependencies = [ "scale-typegen", "subxt-codegen", "subxt-utils-fetchmetadata", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -20668,7 +20714,7 @@ dependencies = [ "parity-scale-codec", "polkadot-sdk", "scale-info", - "thiserror 2.0.8", + "thiserror 2.0.11", ] [[package]] @@ -20681,7 +20727,7 @@ dependencies = [ "bip39", "cfg-if", "crypto_secretbox", - "getrandom", + "getrandom 0.2.15", "hex", "hmac 0.12.1", "parity-scale-codec", @@ -20696,7 +20742,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "subxt-core", - "thiserror 2.0.8", + "thiserror 2.0.11", "zeroize", ] @@ -20708,7 +20754,7 @@ checksum = "526a07767a8f16a9471dda6e3d41c23f9656b302e9cdefdcd7d5a74830284a5d" dependencies = [ "hex", "parity-scale-codec", - "thiserror 2.0.8", + "thiserror 2.0.11", ] [[package]] @@ -20722,7 +20768,7 @@ dependencies = [ "hex", "once_cell", "reqwest 0.11.27", - "semver 1.0.24", + "semver 1.0.25", "serde", "serde_json", "sha2 0.10.8", @@ -20744,9 +20790,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -20762,19 +20808,19 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "syn-solidity" -version = "0.8.18" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e89d8bf2768d277f40573c83a02a099e96d96dd3104e13ea676194e61ac4b0" +checksum = "9c2de690018098e367beeb793991c7d4dc7270f42c9d2ac4ccc876c1368ca430" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -20812,7 +20858,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -20832,7 +20878,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation", "system-configuration-sys 0.6.0", ] @@ -20976,7 +21022,7 @@ dependencies = [ "educe 0.6.0", "ethabi", "fp-evm", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "hex", "impl-trait-for-tuples", @@ -21005,7 +21051,7 @@ dependencies = [ "frame-election-provider-support 37.0.0", "frame-executive 37.0.0", "frame-metadata-hash-extension 0.5.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "frame-system-benchmarking 37.0.0", "frame-system-rpc-runtime-api 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -21016,9 +21062,9 @@ dependencies = [ "pallet-authorship 37.0.0", "pallet-babe 37.0.0", "pallet-bags-list 36.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-base-fee", - "pallet-bounties 36.0.0", + "pallet-bounties 36.0.1", "pallet-child-bounties 36.0.0", "pallet-collective 37.0.0", "pallet-democracy 37.0.0", @@ -21133,7 +21179,7 @@ dependencies = [ "frame-election-provider-support 37.0.0", "frame-executive 37.0.0", "frame-metadata-hash-extension 0.5.0", - "frame-support 37.0.1", + "frame-support 37.1.0", "frame-system 37.1.0", "frame-system-benchmarking 37.0.0", "frame-system-rpc-runtime-api 34.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", @@ -21146,9 +21192,9 @@ dependencies = [ "pallet-authorship 37.0.0", "pallet-babe 37.0.0", "pallet-bags-list 36.0.0", - "pallet-balances 38.0.0", + "pallet-balances 38.0.1", "pallet-base-fee", - "pallet-bounties 36.0.0", + "pallet-bounties 36.0.1", "pallet-child-bounties 36.0.0", "pallet-collective 37.0.0", "pallet-democracy 37.0.0", @@ -21260,14 +21306,15 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", - "rustix 0.38.42", + "rustix 0.38.44", "windows-sys 0.59.0", ] @@ -21297,15 +21344,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ - "rustix 0.38.42", + "rustix 0.38.44", "windows-sys 0.59.0", ] [[package]] name = "termtree" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "testnet-parachains-constants" @@ -21347,7 +21394,7 @@ dependencies = [ "serdect 0.2.0", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk?branch=stable2407)", "subtle 2.6.1", - "thiserror 2.0.8", + "thiserror 2.0.11", "thiserror-nostd-notrait", "visibility", "zeroize", @@ -21364,11 +21411,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.8" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.8", + "thiserror-impl 2.0.11", ] [[package]] @@ -21379,18 +21426,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "thiserror-impl" -version = "2.0.8" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -21410,7 +21457,7 @@ checksum = "585e5ef40a784ce60b49c67d762110688d211d395d39e096be204535cf64590e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -21510,9 +21557,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -21525,9 +21572,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.42.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -21543,13 +21590,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -21578,7 +21625,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.23.20", + "rustls 0.23.22", "tokio", ] @@ -21618,12 +21665,12 @@ checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", - "rustls 0.23.20", + "rustls 0.23.22", "rustls-pki-types", "tokio", "tokio-rustls 0.26.1", "tungstenite 0.24.0", - "webpki-roots 0.26.7", + "webpki-roots 0.26.8", ] [[package]] @@ -21651,9 +21698,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -21672,11 +21719,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.7.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -21719,7 +21766,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "bytes", "http 1.2.0", "http-body 1.0.1", @@ -21761,7 +21808,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -21962,7 +22009,7 @@ dependencies = [ "httparse", "log", "rand", - "rustls 0.23.20", + "rustls 0.23.22", "rustls-pki-types", "sha1", "thiserror 1.0.69", @@ -22016,7 +22063,7 @@ checksum = "603329303137e0d59238ee4d6b9c085eada8e2a9d20666f3abd9dadf8f8543f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -22057,9 +22104,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-normalization" @@ -22191,15 +22238,15 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom", + "getrandom 0.2.15", "serde", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -22221,7 +22268,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -22256,9 +22303,9 @@ dependencies = [ [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] @@ -22288,36 +22335,46 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -22328,9 +22385,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -22338,22 +22395,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-instrument" @@ -22689,9 +22749,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -22725,9 +22785,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] @@ -22758,14 +22818,14 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.42", + "rustix 0.38.44", ] [[package]] name = "wide" -version = "0.7.30" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e6db2670d2be78525979e9a5f9c69d296fd7d670549fe9ebf70f8708cb5019" +checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" dependencies = [ "bytemuck", "safe_arch", @@ -23107,9 +23167,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" dependencies = [ "memchr", ] @@ -23124,6 +23184,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "write16" version = "1.0.0" @@ -23219,18 +23288,18 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] name = "xcm-procedural" version = "10.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#f2081f6657f011634a483e3a4c91e54150c974c7" +source = "git+https://github.com/paritytech/polkadot-sdk?branch=stable2407#7642d6b5dea6dd7871ef8bbfcb9d48ba8a8b0f77" dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -23273,9 +23342,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" [[package]] name = "xmltree" @@ -23342,7 +23411,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "synstructure 0.13.1", ] @@ -23364,7 +23433,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -23384,7 +23453,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", "synstructure 0.13.1", ] @@ -23405,7 +23474,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] @@ -23427,7 +23496,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.98", ] [[package]] diff --git a/pallets/multi-asset-delegation/Cargo.toml b/pallets/multi-asset-delegation/Cargo.toml index c8b136472..561a63021 100644 --- a/pallets/multi-asset-delegation/Cargo.toml +++ b/pallets/multi-asset-delegation/Cargo.toml @@ -62,6 +62,8 @@ pallet-session = { workspace = true } pallet-staking = { workspace = true, optional = true } sp-staking = { workspace = true } frame-election-provider-support = { workspace = true, optional = true } +pallet-proxy = { workspace = true, optional = true } +pallet-utility = { workspace = true, optional = true } [dev-dependencies] ethereum = { workspace = true, features = ["with-codec"] } @@ -102,6 +104,8 @@ pallet-session = { workspace = true } pallet-staking = { workspace = true } sp-staking = { workspace = true } frame-election-provider-support = { workspace = true } +pallet-proxy = { workspace = true } +pallet-utility = { workspace = true } [features] default = ["std"] @@ -141,6 +145,8 @@ std = [ "ethabi/std", "sp-keyring/std", "pallet-ethereum/std", + "pallet-proxy/std", + "pallet-utility/std", ] try-runtime = ["frame-support/try-runtime"] runtime-benchmarks = [ @@ -178,6 +184,8 @@ fuzzing = [ "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", + "pallet-proxy", + "pallet-utility", "precompile-utils", "sp-keyring", "pallet-timestamp", diff --git a/pallets/multi-asset-delegation/fuzzer/call.rs b/pallets/multi-asset-delegation/fuzzer/call.rs index 14e94188c..6a1074b2d 100644 --- a/pallets/multi-asset-delegation/fuzzer/call.rs +++ b/pallets/multi-asset-delegation/fuzzer/call.rs @@ -51,13 +51,13 @@ fn random_ed_multiple(rng: &mut R) -> Balance { } fn random_asset(rng: &mut R) -> Asset { - let asset_id = rng.gen_range(1..u128::MAX); + let asset = rng.gen_range(1..u128::MAX); let is_evm = rng.gen_bool(0.5); if is_evm { let evm_address = rng.gen::<[u8; 20]>().into(); Asset::Erc20(evm_address) } else { - Asset::Custom(asset_id) + Asset::Custom(asset) } } @@ -190,20 +190,20 @@ fn random_calls( // Deposit let (origin, who) = random_signed_origin(&mut rng); fund_account(&mut rng, &who); - let asset_id = random_asset(&mut rng); + let asset = random_asset(&mut rng); let amount = random_ed_multiple(&mut rng); let evm_address = if rng.gen_bool(0.5) { Some(rng.gen::<[u8; 20]>().into()) } else { None }; - [(mad::Call::deposit { asset_id, amount, evm_address, lock_multiplier: None }, origin)] + [(mad::Call::deposit { asset, amount, evm_address, lock_multiplier: None }, origin)] .to_vec() }, "schedule_withdraw" => { // Schedule withdraw let (origin, who) = random_signed_origin(&mut rng); fund_account(&mut rng, &who); - let asset_id = random_asset(&mut rng); + let asset = random_asset(&mut rng); let amount = random_ed_multiple(&mut rng); - [(mad::Call::schedule_withdraw { asset_id, amount }, origin)].to_vec() + [(mad::Call::schedule_withdraw { asset, amount }, origin)].to_vec() }, "execute_withdraw" => { // Execute withdraw @@ -217,16 +217,16 @@ fn random_calls( // Cancel withdraw let (origin, who) = random_signed_origin(&mut rng); fund_account(&mut rng, &who); - let asset_id = random_asset(&mut rng); + let asset = random_asset(&mut rng); let amount = random_ed_multiple(&mut rng); - [(mad::Call::cancel_withdraw { asset_id, amount }, origin)].to_vec() + [(mad::Call::cancel_withdraw { asset, amount }, origin)].to_vec() }, "delegate" => { // Delegate let (origin, who) = random_signed_origin(&mut rng); fund_account(&mut rng, &who); let (operator_origin, operator) = random_signed_origin(&mut rng); - let asset_id = random_asset(&mut rng); + let asset = random_asset(&mut rng); let amount = random_ed_multiple(&mut rng); let blueprint_selection = { let all = rng.gen_bool(0.5); @@ -245,7 +245,7 @@ fn random_calls( }; [ join_operators_call(&mut rng, operator_origin.clone(), &operator), - (mad::Call::delegate { operator, asset_id, amount, blueprint_selection }, origin), + (mad::Call::delegate { operator, asset, amount, blueprint_selection }, origin), ] .to_vec() }, @@ -254,11 +254,11 @@ fn random_calls( let (origin, who) = random_signed_origin(&mut rng); fund_account(&mut rng, &who); let (operator_origin, operator) = random_signed_origin(&mut rng); - let asset_id = random_asset(&mut rng); + let asset = random_asset(&mut rng); let amount = random_ed_multiple(&mut rng); [ join_operators_call(&mut rng, operator_origin.clone(), &operator), - (mad::Call::schedule_delegator_unstake { operator, asset_id, amount }, origin), + (mad::Call::schedule_delegator_unstake { operator, asset, amount }, origin), ] .to_vec() }, @@ -273,11 +273,11 @@ fn random_calls( let (origin, who) = random_signed_origin(&mut rng); fund_account(&mut rng, &who); let (operator_origin, operator) = random_signed_origin(&mut rng); - let asset_id = random_asset(&mut rng); + let asset = random_asset(&mut rng); let amount = random_ed_multiple(&mut rng); [ join_operators_call(&mut rng, operator_origin.clone(), &operator), - (mad::Call::cancel_delegator_unstake { operator, asset_id, amount }, origin), + (mad::Call::cancel_delegator_unstake { operator, asset, amount }, origin), ] .to_vec() }, @@ -398,8 +398,8 @@ fn do_sanity_checks(call: mad::Call, origin: RuntimeOrigin, outcome: Po let info = MultiAssetDelegation::operator_info(&caller).unwrap_or_default(); assert_eq!(info.status, OperatorStatus::Active, "status not set to active"); }, - mad::Call::deposit { asset_id, amount, .. } => { - match asset_id { + mad::Call::deposit { asset, amount, .. } => { + match asset { Asset::Custom(id) => { let pallet_balance = Assets::balance(id, MultiAssetDelegation::pallet_account()); @@ -419,17 +419,17 @@ fn do_sanity_checks(call: mad::Call, origin: RuntimeOrigin, outcome: Po assert_eq!( MultiAssetDelegation::delegators(&caller) .unwrap_or_default() - .calculate_delegation_by_asset(asset_id), + .calculate_delegation_by_asset(asset), amount ); }, - mad::Call::schedule_withdraw { asset_id, amount } => { + mad::Call::schedule_withdraw { asset, amount } => { let round = MultiAssetDelegation::current_round(); assert!( MultiAssetDelegation::delegators(&caller) .unwrap_or_default() .get_withdraw_requests() - .contains(&WithdrawRequest { asset_id, amount, requested_round: round }), + .contains(&WithdrawRequest { asset, amount, requested_round: round }), "withdraw request not found" ); }, @@ -442,17 +442,17 @@ fn do_sanity_checks(call: mad::Call, origin: RuntimeOrigin, outcome: Po "withdraw requests not removed" ); }, - mad::Call::cancel_withdraw { asset_id, amount } => { + mad::Call::cancel_withdraw { asset, amount } => { let round = MultiAssetDelegation::current_round(); assert!( !MultiAssetDelegation::delegators(&caller) .unwrap_or_default() .get_withdraw_requests() - .contains(&WithdrawRequest { asset_id, amount, requested_round: round }), + .contains(&WithdrawRequest { asset, amount, requested_round: round }), "withdraw request not removed" ); }, - mad::Call::delegate { operator, asset_id, amount, .. } => { + mad::Call::delegate { operator, asset, amount, .. } => { let delegator = MultiAssetDelegation::delegators(&caller).unwrap_or_default(); let operator_info = MultiAssetDelegation::operator_info(&operator).unwrap_or_default(); assert!( @@ -460,7 +460,7 @@ fn do_sanity_checks(call: mad::Call, origin: RuntimeOrigin, outcome: Po .calculate_delegation_by_operator(operator) .iter() .find_map(|x| { - if x.asset_id == asset_id { + if x.asset == asset { Some(x.amount) } else { None @@ -474,7 +474,7 @@ fn do_sanity_checks(call: mad::Call, origin: RuntimeOrigin, outcome: Po .delegations .iter() .find_map(|x| { - if x.delegator == caller && x.asset_id == asset_id { + if x.delegator == caller && x.asset == asset { Some(x.amount) } else { None @@ -484,9 +484,9 @@ fn do_sanity_checks(call: mad::Call, origin: RuntimeOrigin, outcome: Po "delegator not added to operator" ); }, - mad::Call::schedule_delegator_unstake { operator, asset_id, amount } => {}, + mad::Call::schedule_delegator_unstake { operator, asset, amount } => {}, mad::Call::execute_delegator_unstake {} => {}, - mad::Call::cancel_delegator_unstake { operator, asset_id, amount } => {}, + mad::Call::cancel_delegator_unstake { operator, asset, amount } => {}, other => unimplemented!("sanity checks for call: {other:?} not implemented"), } } diff --git a/pallets/multi-asset-delegation/src/extra.rs b/pallets/multi-asset-delegation/src/extra.rs new file mode 100644 index 000000000..84b5e19d9 --- /dev/null +++ b/pallets/multi-asset-delegation/src/extra.rs @@ -0,0 +1,99 @@ +use frame_support::pallet_prelude::*; +use mock::{AccountId, Runtime, RuntimeCall}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::traits::{DispatchInfoOf, SignedExtension}; +use types::BalanceOf; + +use super::*; + +#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CheckNominatedRestaked(core::marker::PhantomData); + +impl sp_std::fmt::Debug for CheckNominatedRestaked { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "CheckNominatedRestaked") + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl CheckNominatedRestaked { + pub fn new() -> Self { + CheckNominatedRestaked(core::marker::PhantomData) + } +} + +impl CheckNominatedRestaked { + /// See [`crate::Pallet::can_unbound`] + pub fn can_unbound(who: &T::AccountId, amount: BalanceOf) -> bool { + crate::Pallet::::can_unbound(who, amount) + } +} + +impl Default for CheckNominatedRestaked { + fn default() -> Self { + CheckNominatedRestaked(core::marker::PhantomData) + } +} + +impl SignedExtension for CheckNominatedRestaked { + const IDENTIFIER: &'static str = "CheckNominatedRestaked"; + + type AccountId = AccountId; + + type Call = RuntimeCall; + + type AdditionalSigned = (); + + type Pre = (); + + fn additional_signed(&self) -> Result { + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + match call { + RuntimeCall::Staking(pallet_staking::Call::unbond { value }) => { + if Self::can_unbound(who, *value) { + Ok(ValidTransaction::default()) + } else { + Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(1))) + } + }, + RuntimeCall::Proxy(pallet_proxy::Call::proxy { ref call, real, .. }) => { + self.validate(real, call, _info, _len) + }, + RuntimeCall::Utility(pallet_utility::Call::batch { ref calls }) + | RuntimeCall::Utility(pallet_utility::Call::batch_all { ref calls }) + | RuntimeCall::Utility(pallet_utility::Call::force_batch { ref calls }) => { + for call in calls { + self.validate(who, call, _info, _len)?; + } + Ok(ValidTransaction::default()) + }, + _ => Ok(ValidTransaction::default()), + } + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } +} diff --git a/pallets/multi-asset-delegation/src/functions/delegate.rs b/pallets/multi-asset-delegation/src/functions/delegate.rs index 7f29acd40..c41b220fe 100644 --- a/pallets/multi-asset-delegation/src/functions/delegate.rs +++ b/pallets/multi-asset-delegation/src/functions/delegate.rs @@ -13,445 +13,925 @@ // // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . -use super::*; -use crate::{types::*, Pallet}; + +use crate::{types::*, Config, Delegators, Error, Event, Operators, Pallet}; use frame_support::{ ensure, pallet_prelude::DispatchResult, - traits::{fungibles::Mutate, tokens::Preservation, Get}, + traits::{Get, LockIdentifier, LockableCurrency, WithdrawReasons}, }; use sp_runtime::{ - traits::{CheckedAdd, CheckedSub, Zero}, - DispatchError, Percent, -}; -use sp_std::vec::Vec; -use tangle_primitives::{ - services::{Asset, EvmAddressMapping}, - BlueprintId, + traits::{CheckedAdd, Saturating, Zero}, + DispatchError, }; +use sp_staking::StakingInterface; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; +use tangle_primitives::{services::Asset, traits::MultiAssetDelegationInfo, RoundIndex}; + +pub const DELEGATION_LOCK_ID: LockIdentifier = *b"delegate"; + +type AccountIdOf = ::AccountId; + +type DelegationResult = Vec<(AccountIdOf, Asset<::AssetId>, BalanceOf)>; +type BondLessRequestResult = Result< + BondLessRequest< + AccountIdOf, + ::AssetId, + BalanceOf, + ::MaxDelegatorBlueprints, + >, + DispatchError, +>; +type DelegatorBondInfo = BondInfoDelegator< + AccountIdOf, + BalanceOf, + ::AssetId, + ::MaxDelegatorBlueprints, +>; +type DepositUpdates = BTreeMap::AssetId>, BalanceOf>; +type DelegationUpdates = + BTreeMap<(AccountIdOf, Asset<::AssetId>), (usize, BalanceOf)>; +type OperatorUpdates = BTreeMap<(AccountIdOf, Asset<::AssetId>), BalanceOf>; +type AggregateResult = + Result<(DepositUpdates, DelegationUpdates, OperatorUpdates, Vec), Error>; impl Pallet { /// Processes the delegation of an amount of an asset to an operator. - /// Creates a new delegation for the delegator and updates their status to active, the deposit - /// of the delegator is moved to delegation. + /// + /// This function handles both creating new delegations and increasing existing ones. + /// It updates three main pieces of state: + /// 1. The delegator's deposit record (marking funds as delegated) + /// 2. The delegator's delegation list + /// 3. The operator's delegation records + /// + /// # Performance Considerations + /// + /// - Single storage read for operator verification + /// - Single storage write for delegation update + /// - Bounded by MaxDelegations for new delegations + /// /// # Arguments /// - /// * `who` - The account ID of the delegator. - /// * `operator` - The account ID of the operator. - /// * `asset_id` - The ID of the asset to be delegated. - /// * `amount` - The amount to be delegated. + /// * `who` - The account ID of the delegator + /// * `operator` - The account ID of the operator to delegate to + /// * `asset` - The asset being delegated + /// * `amount` - The amount to delegate + /// * `blueprint_selection` - Strategy for selecting which blueprints to work with: + /// - Fixed: Work with specific blueprints + /// - All: Work with all available blueprints /// /// # Errors /// - /// Returns an error if the delegator does not have enough deposited balance, - /// or if the operator is not found. + /// * `NotDelegator` - Account is not a delegator + /// * `NotAnOperator` - Target account is not an operator + /// * `InsufficientBalance` - Not enough deposited balance + /// * `MaxDelegationsExceeded` - Would exceed maximum allowed delegations + /// * `OverflowRisk` - Arithmetic overflow during calculations + /// * `OperatorNotActive` - Operator is not in active status + /// + /// # Example + /// + /// ```ignore + /// // Delegate 100 tokens to operator with Fixed blueprint selection + /// let blueprint_ids = vec![1, 2, 3]; + /// process_delegate( + /// delegator, + /// operator, + /// Asset::Custom(token_id), + /// 100, + /// DelegatorBlueprintSelection::Fixed(blueprint_ids) + /// )?; + /// ``` pub fn process_delegate( who: T::AccountId, operator: T::AccountId, - asset_id: Asset, + asset: Asset, amount: BalanceOf, blueprint_selection: DelegatorBlueprintSelection, ) -> DispatchResult { + // Verify operator exists and is active + ensure!(Self::is_operator(&operator), Error::::NotAnOperator); + ensure!(Self::is_operator_active(&operator), Error::::NotActiveOperator); + ensure!(!amount.is_zero(), Error::::InvalidAmount); + Delegators::::try_mutate(&who, |maybe_metadata| { let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; - // Ensure enough deposited balance + // Ensure enough deposited balance and update it let user_deposit = - metadata.deposits.get_mut(&asset_id).ok_or(Error::::InsufficientBalance)?; - - // update the user deposit + metadata.deposits.get_mut(&asset).ok_or(Error::::InsufficientBalance)?; user_deposit .increase_delegated_amount(amount) .map_err(|_| Error::::InsufficientBalance)?; - // Check if the delegation exists and update it, otherwise create a new delegation - if let Some(delegation) = metadata + // Find existing delegation or create new one + let delegation_exists = metadata .delegations - .iter_mut() - .find(|d| d.operator == operator && d.asset_id == asset_id) - { - delegation.amount = - delegation.amount.checked_add(&amount).ok_or(Error::::OverflowRisk)?; - } else { - // Create the new delegation - let new_delegation = BondInfoDelegator { - operator: operator.clone(), - amount, - asset_id, - blueprint_selection, - }; - - // Create a mutable copy of delegations - let mut delegations = metadata.delegations.clone(); - delegations - .try_push(new_delegation) - .map_err(|_| Error::::MaxDelegationsExceeded)?; - metadata.delegations = delegations; - - // Update the status - metadata.status = DelegatorStatus::Active; - } - - // Update the operator's metadata - if let Some(mut operator_metadata) = Operators::::get(&operator) { - // Check if the operator has capacity for more delegations - ensure!( - operator_metadata.delegation_count < T::MaxDelegations::get(), - Error::::MaxDelegationsExceeded - ); - - // Create and push the new delegation bond - let delegation = DelegatorBond { delegator: who.clone(), amount, asset_id }; - - let mut delegations = operator_metadata.delegations.clone(); - - // Check if delegation already exists - if let Some(existing_delegation) = - delegations.iter_mut().find(|d| d.delegator == who && d.asset_id == asset_id) - { - existing_delegation.amount = existing_delegation - .amount - .checked_add(&amount) - .ok_or(Error::::OverflowRisk)?; - } else { - delegations - .try_push(delegation) + .iter() + .position(|d| d.operator == operator && d.asset == asset && !d.is_nomination); + + match delegation_exists { + Some(idx) => { + // Update existing delegation + let delegation = &mut metadata.delegations[idx]; + delegation.amount = + delegation.amount.checked_add(&amount).ok_or(Error::::OverflowRisk)?; + }, + None => { + // Create new delegation + metadata + .delegations + .try_push(BondInfoDelegator { + operator: operator.clone(), + amount, + asset, + blueprint_selection, + is_nomination: false, + }) .map_err(|_| Error::::MaxDelegationsExceeded)?; - operator_metadata.delegation_count = - operator_metadata.delegation_count.saturating_add(1); - } - operator_metadata.delegations = delegations; - - // Update storage - Operators::::insert(&operator, operator_metadata); - } else { - return Err(Error::::NotAnOperator.into()); + metadata.status = DelegatorStatus::Active; + }, } + // Update operator metadata + Self::update_operator_metadata(&operator, &who, asset, amount, true)?; + + // Emit event + Self::deposit_event(Event::Delegated { who: who.clone(), operator, amount, asset }); + Ok(()) }) } /// Schedules a stake reduction for a delegator. /// + /// Creates an unstake request that can be executed after the delegation bond less delay period. + /// The actual unstaking occurs when `execute_delegator_unstake` is called after the delay. + /// Multiple unstake requests for the same delegation are allowed, but each must be within + /// the available delegated amount. + /// + /// # Performance Considerations + /// + /// - Single storage read for delegation verification + /// - Single storage write for request creation + /// - Bounded by MaxUnstakeRequests + /// /// # Arguments /// - /// * `who` - The account ID of the delegator. - /// * `operator` - The account ID of the operator. - /// * `asset_id` - The ID of the asset to be reduced. - /// * `amount` - The amount to be reduced. + /// * `who` - The account ID of the delegator + /// * `operator` - The account ID of the operator + /// * `asset` - The asset to unstake + /// * `amount` - The amount to unstake /// /// # Errors /// - /// Returns an error if the delegator has no active delegation, - /// or if the unstake amount is greater than the current delegation amount. + /// * `NotDelegator` - Account is not a delegator + /// * `NoActiveDelegation` - No active delegation found for operator and asset + /// * `InsufficientBalance` - Trying to unstake more than delegated + /// * `MaxUnstakeRequestsExceeded` - Too many pending unstake requests + /// * `InvalidAmount` - Attempting to unstake zero tokens + /// + /// # Example + /// + /// ```ignore + /// // Schedule unstaking of 50 tokens from operator + /// process_schedule_delegator_unstake( + /// delegator, + /// operator, + /// Asset::Custom(token_id), + /// 50 + /// )?; + /// ``` pub fn process_schedule_delegator_unstake( who: T::AccountId, operator: T::AccountId, - asset_id: Asset, + asset: Asset, amount: BalanceOf, ) -> DispatchResult { + ensure!(!amount.is_zero(), Error::::InvalidAmount); + Delegators::::try_mutate(&who, |maybe_metadata| { let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; - // Ensure the delegator has an active delegation with the operator for the given asset - let delegation_index = metadata + // Find and validate delegation in a single pass + let delegation = metadata .delegations .iter() - .position(|d| d.operator == operator && d.asset_id == asset_id) + .find(|d| d.operator == operator && d.asset == asset && !d.is_nomination) .ok_or(Error::::NoActiveDelegation)?; - // Get the delegation and clone necessary data - let blueprint_selection = - metadata.delegations[delegation_index].blueprint_selection.clone(); - let delegation = &mut metadata.delegations[delegation_index]; - ensure!(delegation.amount >= amount, Error::::InsufficientBalance); + // Verify sufficient delegation amount considering existing unstake requests + let pending_unstake_amount: BalanceOf = metadata + .delegator_unstake_requests + .iter() + .filter(|r| r.operator == operator && r.asset == asset) + .fold(Zero::zero(), |acc, r| acc.saturating_add(r.amount)); - delegation.amount = - delegation.amount.checked_sub(&amount).ok_or(Error::::InsufficientBalance)?; + let available_amount = delegation.amount.saturating_sub(pending_unstake_amount); + ensure!(available_amount >= amount, Error::::InsufficientBalance); // Create the unstake request - let current_round = Self::current_round(); - let mut unstake_requests = metadata.delegator_unstake_requests.clone(); - unstake_requests - .try_push(BondLessRequest { - operator: operator.clone(), - asset_id, - amount, - requested_round: current_round, - blueprint_selection, - }) - .map_err(|_| Error::::MaxUnstakeRequestsExceeded)?; - metadata.delegator_unstake_requests = unstake_requests; + Self::create_unstake_request( + metadata, + operator.clone(), + asset, + amount, + delegation.blueprint_selection.clone(), + false, // is_nomination = false for regular delegations + )?; - // Remove the delegation if the remaining amount is zero - if delegation.amount.is_zero() { - metadata.delegations.remove(delegation_index); - } + Ok(()) + }) + } - // Update the operator's metadata - Operators::::try_mutate(&operator, |maybe_operator_metadata| -> DispatchResult { - let operator_metadata = - maybe_operator_metadata.as_mut().ok_or(Error::::NotAnOperator)?; + /// Cancels a scheduled stake reduction for a delegator. + /// + /// This function removes a pending unstake request without modifying any actual delegations. + /// It performs a simple lookup and removal of the matching request. + /// + /// # Performance Considerations + /// + /// - Single storage read for request verification + /// - Single storage write for request removal + /// - O(n) search through unstake requests + /// + /// # Arguments + /// + /// * `who` - The account ID of the delegator + /// * `operator` - The operator whose unstake request to cancel + /// * `asset` - The asset of the unstake request + /// * `amount` - The exact amount of the unstake request to cancel + /// + /// # Errors + /// + /// * `NotDelegator` - Account is not a delegator + /// * `NoBondLessRequest` - No matching unstake request found + /// * `InvalidAmount` - Amount specified is zero + /// + /// # Example + /// + /// ```ignore + /// // Cancel an unstake request for 50 tokens + /// process_cancel_delegator_unstake( + /// delegator, + /// operator, + /// Asset::Custom(token_id), + /// 50 + /// )?; + /// ``` + pub fn process_cancel_delegator_unstake( + who: T::AccountId, + operator: T::AccountId, + asset: Asset, + amount: BalanceOf, + ) -> DispatchResult { + ensure!(!amount.is_zero(), Error::::InvalidAmount); - // Ensure the operator has a matching delegation - let operator_delegation_index = operator_metadata - .delegations - .iter() - .position(|d| d.delegator == who && d.asset_id == asset_id) - .ok_or(Error::::NoActiveDelegation)?; + Delegators::::try_mutate(&who, |maybe_metadata| { + let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; - let operator_delegation = - &mut operator_metadata.delegations[operator_delegation_index]; - - // Reduce the amount in the operator's delegation - ensure!(operator_delegation.amount >= amount, Error::::InsufficientBalance); - operator_delegation.amount = operator_delegation - .amount - .checked_sub(&amount) - .ok_or(Error::::InsufficientBalance)?; - - // Remove the delegation if the remaining amount is zero - if operator_delegation.amount.is_zero() { - operator_metadata.delegations.remove(operator_delegation_index); - operator_metadata.delegation_count = operator_metadata - .delegation_count - .checked_sub(1u32) - .ok_or(Error::::InsufficientBalance)?; - } + // Find and remove the matching unstake request + let request_index = metadata + .delegator_unstake_requests + .iter() + .position(|r| { + r.asset == asset + && r.amount == amount + && r.operator == operator + && !r.is_nomination + }) + .ok_or(Error::::NoBondLessRequest)?; - Ok(()) - })?; + // Remove the request and emit event + metadata.delegator_unstake_requests.remove(request_index); Ok(()) }) } - /// Executes scheduled stake reductions for a delegator. + /// Executes all ready unstake requests for a delegator. + /// + /// This function processes multiple unstake requests in a batched manner for efficiency: + /// 1. Aggregates all ready requests by asset and operator to minimize storage operations + /// 2. Updates deposits, delegations, and operator metadata in batches + /// 3. Removes zero-amount delegations and processed requests + /// + /// # Performance Considerations + /// + /// - Uses batch processing to minimize storage reads/writes + /// - Aggregates updates by asset and operator + /// - Removes items in reverse order to avoid unnecessary shifting /// /// # Arguments /// - /// * `who` - The account ID of the delegator. + /// * `who` - The account ID of the delegator /// /// # Errors /// - /// Returns an error if the delegator has no unstake requests or if none of the unstake requests - /// are ready. - pub fn process_execute_delegator_unstake(who: T::AccountId) -> DispatchResult { - Delegators::::try_mutate(&who, |maybe_metadata| { - let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; + /// * `NotDelegator` - Account is not a delegator + /// * `NoBondLessRequest` - No unstake requests exist + /// * `BondLessNotReady` - No requests are ready for execution + /// * `NoActiveDelegation` - Referenced delegation not found + /// * `InsufficientBalance` - Insufficient balance for unstaking + pub fn process_execute_delegator_unstake( + who: T::AccountId, + ) -> Result, DispatchError> { + Delegators::::try_mutate( + &who, + |maybe_metadata| -> Result, DispatchError> { + let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; + ensure!( + !metadata.delegator_unstake_requests.is_empty(), + Error::::NoBondLessRequest + ); - // Ensure there are outstanding unstake requests - ensure!(!metadata.delegator_unstake_requests.is_empty(), Error::::NoBondLessRequest); + let current_round = Self::current_round(); + let delay = T::DelegationBondLessDelay::get(); - let current_round = Self::current_round(); - let delay = T::DelegationBondLessDelay::get(); + // Aggregate all updates from ready requests + let (deposit_updates, delegation_updates, operator_updates, indices_to_remove) = + Self::aggregate_unstake_requests(metadata, current_round, delay)?; - // First, collect all ready requests and process them - let ready_requests: Vec<_> = metadata - .delegator_unstake_requests + // Create a map to aggregate amounts by operator and asset + let mut event_aggregates = + BTreeMap::<(T::AccountId, Asset), BalanceOf>::new(); + + // Sum up amounts by operator and asset + for &idx in &indices_to_remove { + if let Some(request) = metadata.delegator_unstake_requests.get(idx) { + let key = (request.operator.clone(), request.asset); + let entry = event_aggregates.entry(key).or_insert(Zero::zero()); + *entry = entry.saturating_add(request.amount); + } + } + + // Apply updates in batches + // 1. Update deposits + for (asset, amount) in deposit_updates { + metadata + .deposits + .get_mut(&asset) + .ok_or(Error::::InsufficientBalance)? + .decrease_delegated_amount(amount) + .map_err(|_| Error::::InsufficientBalance)?; + } + + // 2. Update delegations + let mut delegations_to_remove = Vec::new(); + for ((_, _), (idx, amount)) in delegation_updates { + let delegation = + metadata.delegations.get_mut(idx).ok_or(Error::::NoActiveDelegation)?; + ensure!(delegation.amount >= amount, Error::::InsufficientBalance); + + delegation.amount = delegation.amount.saturating_sub(amount); + if delegation.amount.is_zero() { + delegations_to_remove.push(idx); + } + } + + // 3. Remove zero-amount delegations + delegations_to_remove.sort_unstable_by(|a, b| b.cmp(a)); + for idx in delegations_to_remove { + metadata.delegations.remove(idx); + } + + // 4. Update operator metadata + for ((operator, asset), amount) in operator_updates { + Self::update_operator_metadata(&operator, &who, asset, amount, false)?; + } + + // 5. Remove processed requests + let mut indices = indices_to_remove; + indices.sort_unstable_by(|a, b| b.cmp(a)); + for idx in indices { + metadata.delegator_unstake_requests.remove(idx); + } + + // Convert the aggregates map into a vector for return + Ok(event_aggregates + .into_iter() + .map(|((operator, asset), amount)| (operator, asset, amount)) + .collect()) + }, + ) + } + + /// Processes the delegation of nominated tokens to an operator. + /// + /// This function allows delegators to utilize their nominated (staked) tokens in the delegation system. + /// It differs from regular delegation in that: + /// 1. It uses nominated tokens instead of deposited assets + /// 2. It maintains a lock on the nominated tokens + /// 3. It tracks total nomination delegations to prevent over-delegation + /// + /// # Performance Considerations + /// + /// - External call to staking system for verification + /// - Single storage read for delegation lookup + /// - Single storage write for delegation update + /// - Additional storage write for token locking + /// + /// # Arguments + /// + /// * `who` - The account ID of the delegator + /// * `operator` - The operator to delegate to + /// * `amount` - The amount of nominated tokens to delegate + /// * `blueprint_selection` - Strategy for selecting which blueprints to work with + /// + /// # Errors + /// + /// * `NotDelegator` - Account is not a delegator + /// * `NotNominator` - Account has no nominated tokens + /// * `InsufficientBalance` - Not enough nominated tokens available + /// * `MaxDelegationsExceeded` - Would exceed maximum allowed delegations + /// * `OverflowRisk` - Arithmetic overflow during calculations + /// * `InvalidAmount` - Amount specified is zero + /// + /// # Example + /// + /// ```ignore + /// // Delegate 1000 nominated tokens to operator + /// process_delegate_nominations( + /// delegator, + /// operator, + /// 1000, + /// DelegatorBlueprintSelection::All + /// )?; + /// ``` + pub(crate) fn process_delegate_nominations( + who: T::AccountId, + operator: T::AccountId, + amount: BalanceOf, + blueprint_selection: DelegatorBlueprintSelection, + ) -> DispatchResult { + ensure!(!amount.is_zero(), Error::::InvalidAmount); + ensure!(Self::is_operator(&operator), Error::::NotAnOperator); + ensure!(Self::is_operator_active(&operator), Error::::NotActiveOperator); + + // Verify nomination amount in the staking system + let nominated_amount = Self::verify_nomination_amount(&who, amount)?; + + Delegators::::try_mutate(&who, |maybe_metadata| -> DispatchResult { + let metadata = maybe_metadata.get_or_insert_with(Default::default); + + // Calculate new total after this delegation + let current_total = metadata.total_nomination_delegations(); + let new_total = current_total.checked_add(&amount).ok_or(Error::::OverflowRisk)?; + + // Ensure total delegations don't exceed nominated amount + ensure!(new_total <= nominated_amount, Error::::InsufficientBalance); + + // Find existing nomination delegation or create new one + let delegation_exists = metadata + .delegations .iter() - .filter(|request| current_round >= delay + request.requested_round) - .cloned() - .collect(); - - // If no requests are ready, return an error - ensure!(!ready_requests.is_empty(), Error::::BondLessNotReady); - - // Process each ready request - for request in ready_requests.iter() { - let deposit_record = metadata - .deposits - .get_mut(&request.asset_id) - .ok_or(Error::::InsufficientBalance)?; - - deposit_record - .decrease_delegated_amount(request.amount) - .map_err(|_| Error::::InsufficientBalance)?; + .position(|d| d.operator == operator && d.is_nomination); + + match delegation_exists { + Some(idx) => { + // Update existing delegation + let delegation = &mut metadata.delegations[idx]; + let new_amount = + delegation.amount.checked_add(&amount).ok_or(Error::::OverflowRisk)?; + + delegation.amount = new_amount; + T::Currency::set_lock( + DELEGATION_LOCK_ID, + &who, + new_amount, + WithdrawReasons::TRANSFER, + ); + }, + None => { + // Create new delegation + metadata + .delegations + .try_push(BondInfoDelegator { + operator: operator.clone(), + amount, + asset: Asset::Custom(Zero::zero()), + blueprint_selection, + is_nomination: true, + }) + .map_err(|_| Error::::MaxDelegationsExceeded)?; + + T::Currency::set_lock( + DELEGATION_LOCK_ID, + &who, + amount, + WithdrawReasons::TRANSFER, + ); + }, } - // Remove the processed requests - metadata - .delegator_unstake_requests - .retain(|request| current_round < delay + request.requested_round); + // Update operator metadata + Self::update_operator_metadata( + &operator, + &who, + Asset::Custom(Zero::zero()), + amount, + true, // is_increase = true for delegation + )?; + + // Emit event + Self::deposit_event(Event::NominationDelegated { + who: who.clone(), + operator: operator.clone(), + amount, + }); Ok(()) - }) + })?; + + Ok(()) } - /// Cancels a scheduled stake reduction for a delegator. + /// Schedules an unstake request for nomination delegations. + /// + /// Similar to regular unstaking but specifically for nominated tokens. This function: + /// 1. Verifies the nomination delegation exists + /// 2. Checks if there's enough balance to unstake + /// 3. Creates an unstake request that can be executed after the delay period + /// + /// # Performance Considerations + /// + /// - Single storage read for delegation verification + /// - Single storage write for request creation + /// - O(n) search through delegations /// /// # Arguments /// - /// * `who` - The account ID of the delegator. - /// * `asset_id` - The ID of the asset for which to cancel the unstake request. - /// * `amount` - The amount of the unstake request to cancel. + /// * `who` - The account ID of the delegator + /// * `operator` - The operator to unstake from + /// * `amount` - The amount of nominated tokens to unstake + /// * `blueprint_selection` - The blueprint selection to use after unstaking /// /// # Errors /// - /// Returns an error if the delegator has no matching unstake request or if there is no active - /// delegation. - pub fn process_cancel_delegator_unstake( - who: T::AccountId, + /// * `NotDelegator` - Account is not a delegator + /// * `NoActiveDelegation` - No active nomination delegation found + /// * `InsufficientBalance` - Trying to unstake more than delegated + /// * `MaxUnstakeRequestsExceeded` - Too many pending unstake requests + /// * `InvalidAmount` - Amount specified is zero + /// * `AssetNotWhitelisted` - Invalid asset type for nominations + /// + /// # Example + /// + /// ```ignore + /// // Schedule unstaking of 500 nominated tokens + /// process_schedule_delegator_nomination_unstake( + /// &delegator, + /// operator, + /// 500, + /// DelegatorBlueprintSelection::All + /// )?; + /// ``` + pub fn process_schedule_delegator_nomination_unstake( + who: &T::AccountId, operator: T::AccountId, - asset_id: Asset, amount: BalanceOf, - ) -> DispatchResult { - Delegators::::try_mutate(&who, |maybe_metadata| { + blueprint_selection: DelegatorBlueprintSelection, + ) -> Result { + ensure!(!amount.is_zero(), Error::::InvalidAmount); + ensure!(Self::is_operator(&operator), Error::::NotAnOperator); + + Delegators::::try_mutate(who, |maybe_metadata| -> Result { let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; - // Find and remove the matching unstake request + // Find the nomination delegation and verify amount + let (_, current_amount) = + Self::find_nomination_delegation(&metadata.delegations, &operator)? + .ok_or(Error::::NoActiveDelegation)?; + + // Calculate total pending unstakes + let pending_unstake_amount: BalanceOf = metadata + .delegator_unstake_requests + .iter() + .filter(|r| r.operator == operator && r.is_nomination) + .fold(Zero::zero(), |acc, r| acc.saturating_add(r.amount)); + + let available_amount = current_amount.saturating_sub(pending_unstake_amount); + ensure!(available_amount >= amount, Error::::InsufficientBalance); + + // Create the unstake request + Self::create_unstake_request( + metadata, + operator.clone(), + Asset::Custom(Zero::zero()), + amount, + blueprint_selection, + true, // is_nomination = true for nomination delegations + )?; + + let when = Self::current_round() + T::DelegationBondLessDelay::get(); + Ok(when) + }) + } + + /// Cancels a scheduled unstake request for nomination delegations. + /// + /// Similar to regular unstake cancellation but specifically for nominated tokens. + /// This function removes a pending unstake request without modifying any actual delegations. + /// + /// # Performance Considerations + /// + /// - Single storage read for request verification + /// - Single storage write for request removal + /// - O(n) search through unstake requests + /// + /// # Arguments + /// + /// * `who` - The account ID of the delegator + /// * `operator` - The operator whose unstake request to cancel + /// + /// # Errors + /// + /// * `NotDelegator` - Account is not a delegator + /// * `NoBondLessRequest` - No matching unstake request found + /// + /// # Example + /// + /// ```ignore + /// // Cancel nomination unstake request from operator + /// process_cancel_delegator_nomination_unstake( + /// &delegator, + /// operator + /// )?; + /// ``` + pub(crate) fn process_cancel_delegator_nomination_unstake( + who: &T::AccountId, + operator: T::AccountId, + ) -> BondLessRequestResult { + Delegators::::try_mutate(who, |maybe_metadata| -> BondLessRequestResult { + let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; + + // Find and remove the unstake request let request_index = metadata .delegator_unstake_requests .iter() - .position(|r| { - r.asset_id == asset_id && r.amount == amount && r.operator == operator - }) + .position(|r| r.operator == operator && r.is_nomination) .ok_or(Error::::NoBondLessRequest)?; - let unstake_request = metadata.delegator_unstake_requests.remove(request_index); - - // Update the operator's metadata - Operators::::try_mutate( - &unstake_request.operator, - |maybe_operator_metadata| -> DispatchResult { - let operator_metadata = - maybe_operator_metadata.as_mut().ok_or(Error::::NotAnOperator)?; - - // Find the matching delegation and increase its amount, or insert a new - // delegation if not found - let mut delegations = operator_metadata.delegations.clone(); - if let Some(delegation) = delegations - .iter_mut() - .find(|d| d.asset_id == asset_id && d.delegator == who.clone()) - { - delegation.amount = delegation - .amount - .checked_add(&amount) - .ok_or(Error::::OverflowRisk)?; - } else { - delegations - .try_push(DelegatorBond { delegator: who.clone(), amount, asset_id }) - .map_err(|_| Error::::MaxDelegationsExceeded)?; - - // Increase the delegation count only when a new delegation is added - operator_metadata.delegation_count = operator_metadata - .delegation_count - .checked_add(1) - .ok_or(Error::::OverflowRisk)?; - } - operator_metadata.delegations = delegations; + // Remove the request + let request = metadata.delegator_unstake_requests.remove(request_index); - Ok(()) - }, + Ok(request.clone()) + }) + } + + /// Execute an unstake request for nomination delegations + pub fn process_execute_delegator_nomination_unstake( + who: &T::AccountId, + operator: T::AccountId, + ) -> Result, DispatchError> { + Delegators::::try_mutate(who, |maybe_metadata| -> Result, DispatchError> { + let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; + + // Find and validate the unstake request + let (request_index, request) = metadata + .delegator_unstake_requests + .iter() + .enumerate() + .find(|(_, r)| r.operator == operator && r.is_nomination) + .ok_or(Error::::NoBondLessRequest)?; + + let delay = T::DelegationBondLessDelay::get(); + ensure!( + request.requested_round + delay <= Self::current_round(), + Error::::BondLessNotReady + ); + + // Store the amount before removing the request + let unstake_amount = request.amount; + // Find the nomination delegation + let (delegation_index, current_amount) = + Self::find_nomination_delegation(&metadata.delegations, &operator)? + .ok_or(Error::::NoActiveDelegation)?; + + // Verify the unstake amount is still valid + ensure!(current_amount >= unstake_amount, Error::::InsufficientBalance); + + // Update the delegation + let delegation = &mut metadata.delegations[delegation_index]; + delegation.amount = delegation.amount.saturating_sub(unstake_amount); + + // Update operator metadata during execution + Self::update_operator_metadata( + &operator, + who, + Asset::Custom(Zero::zero()), + unstake_amount, + false, // is_increase = false for unstaking )?; - // Update the delegator's metadata - let mut delegations = metadata.delegations.clone(); + // Remove the unstake request + metadata.delegator_unstake_requests.remove(request_index); - // If a similar delegation exists, increase the amount - if let Some(delegation) = delegations.iter_mut().find(|d| { - d.operator == unstake_request.operator && d.asset_id == unstake_request.asset_id - }) { - delegation.amount = delegation - .amount - .checked_add(&unstake_request.amount) - .ok_or(Error::::OverflowRisk)?; + // Set the lock to the new amount or remove it if zero + if delegation.amount.is_zero() { + T::Currency::remove_lock(DELEGATION_LOCK_ID, who); + metadata.delegations.remove(delegation_index); + } else { + T::Currency::set_lock( + DELEGATION_LOCK_ID, + who, + delegation.amount, + WithdrawReasons::TRANSFER, + ); + } + + Ok(unstake_amount) + }) + } + + /// Helper function to update operator metadata for a delegation change + fn update_operator_metadata( + operator: &T::AccountId, + who: &T::AccountId, + asset: Asset, + amount: BalanceOf, + is_increase: bool, + ) -> DispatchResult { + Operators::::try_mutate(operator, |maybe_operator_metadata| -> DispatchResult { + let operator_metadata = + maybe_operator_metadata.as_mut().ok_or(Error::::NotAnOperator)?; + + let mut delegations = operator_metadata.delegations.clone(); + + if is_increase { + // Adding or increasing delegation + ensure!( + operator_metadata.delegation_count < T::MaxDelegations::get(), + Error::::MaxDelegationsExceeded + ); + + if let Some(existing_delegation) = + delegations.iter_mut().find(|d| d.delegator == *who && d.asset == asset) + { + existing_delegation.amount = existing_delegation + .amount + .checked_add(&amount) + .ok_or(Error::::OverflowRisk)?; + } else { + delegations + .try_push(DelegatorBond { delegator: who.clone(), amount, asset }) + .map_err(|_| Error::::MaxDelegationsExceeded)?; + operator_metadata.delegation_count = + operator_metadata.delegation_count.saturating_add(1); + } } else { - // Create a new delegation - delegations - .try_push(BondInfoDelegator { - operator: unstake_request.operator.clone(), - amount: unstake_request.amount, - asset_id: unstake_request.asset_id, - blueprint_selection: unstake_request.blueprint_selection, - }) - .map_err(|_| Error::::MaxDelegationsExceeded)?; + // Decreasing or removing delegation + if let Some(index) = + delegations.iter().position(|d| d.delegator == *who && d.asset == asset) + { + let delegation = &mut delegations[index]; + ensure!(delegation.amount >= amount, Error::::InsufficientBalance); + + delegation.amount = delegation.amount.saturating_sub(amount); + if delegation.amount.is_zero() { + delegations.remove(index); + operator_metadata.delegation_count = operator_metadata + .delegation_count + .checked_sub(1) + .ok_or(Error::::InsufficientBalance)?; + } + } } - metadata.delegations = delegations; + operator_metadata.delegations = delegations; Ok(()) }) } - /// Slashes a delegator's stake. + /// Checks if an account can unbond a specified amount of tokens. + /// + /// This function verifies whether unbonding a certain amount would leave sufficient + /// tokens to cover existing nomination delegations. It: + /// 1. Checks if the account is a nominator + /// 2. Calculates remaining stake after unbonding + /// 3. Verifies nominated delegations can still be covered /// /// # Arguments /// - /// * `delegator` - The account ID of the delegator. - /// * `operator` - The account ID of the operator. - /// * `blueprint_id` - The ID of the blueprint. - /// * `percentage` - The percentage of the stake to slash. + /// * `who` - The account to check + /// * `amount` - The amount to potentially unbond /// - /// # Errors + /// # Returns /// - /// Returns an error if the delegator is not found, or if the delegation is not active. - pub fn slash_delegator( - delegator: &T::AccountId, + /// * `true` if unbonding is allowed + /// * `false` if unbonding would leave insufficient stake for delegations + pub fn can_unbound(who: &T::AccountId, amount: BalanceOf) -> bool { + let Ok(stake) = T::StakingInterface::stake(who) else { + // not a nominator + return true; + }; + // Simulate the unbound operation + let remaining_stake = stake.active.saturating_sub(amount); + let delegator = Self::delegators(who).unwrap_or_default(); + let nominated_amount = + delegator.delegations.iter().fold(BalanceOf::::zero(), |acc, d| { + if d.is_nomination { + acc.saturating_add(d.amount) + } else { + acc + } + }); + let restake_amount = remaining_stake.saturating_sub(nominated_amount); + !restake_amount.is_zero() + } + + /// Helper function to verify and get nomination amount + fn verify_nomination_amount( + who: &T::AccountId, + required_amount: BalanceOf, + ) -> Result, Error> { + let stake = T::StakingInterface::stake(who).map_err(|_| Error::::NotNominator)?; + ensure!(stake.total >= required_amount, Error::::InsufficientBalance); + Ok(stake.active) + } + + /// Helper function to find and validate a nomination delegation + fn find_nomination_delegation( + delegations: &[DelegatorBondInfo], operator: &T::AccountId, - blueprint_id: BlueprintId, - percentage: Percent, - ) -> Result<(), DispatchError> { - Delegators::::try_mutate(delegator, |maybe_metadata| { - let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; + ) -> Result)>, Error> { + if let Some((index, delegation)) = delegations + .iter() + .enumerate() + .find(|(_, d)| d.operator == *operator && d.is_nomination) + { + ensure!( + delegation.asset == Asset::Custom(Zero::zero()), + Error::::AssetNotWhitelisted + ); + Ok(Some((index, delegation.amount))) + } else { + Ok(None) + } + } - let delegation = metadata - .delegations - .iter_mut() - .find(|d| &d.operator == operator) - .ok_or(Error::::NoActiveDelegation)?; + /// Helper function to create an unstake request + fn create_unstake_request( + metadata: &mut DelegatorMetadataOf, + operator: T::AccountId, + asset: Asset, + amount: BalanceOf, + blueprint_selection: DelegatorBlueprintSelection, + is_nomination: bool, + ) -> DispatchResult { + let unstake_request = BondLessRequest { + operator, + asset, + amount, + requested_round: Self::current_round(), + blueprint_selection, + is_nomination, + }; + + metadata + .delegator_unstake_requests + .try_push(unstake_request) + .map_err(|_| Error::::MaxUnstakeRequestsExceeded)?; + + Ok(()) + } - // Check delegation type and blueprint_id - match &delegation.blueprint_selection { - DelegatorBlueprintSelection::Fixed(blueprints) => { - // For fixed delegation, ensure the blueprint_id is in the list - ensure!(blueprints.contains(&blueprint_id), Error::::BlueprintNotSelected); - }, - DelegatorBlueprintSelection::All => { - // For "All" type, no need to check blueprint_id - }, + /// Helper function to process a batch of unstake requests + /// Returns aggregated updates for deposits, delegations, and operators + fn aggregate_unstake_requests( + metadata: &DelegatorMetadataOf, + current_round: RoundIndex, + delay: RoundIndex, + ) -> AggregateResult { + let mut indices_to_remove = Vec::new(); + let mut delegation_updates = BTreeMap::new(); + let mut deposit_updates = BTreeMap::new(); + let mut operator_updates = BTreeMap::new(); + + for (idx, request) in metadata.delegator_unstake_requests.iter().enumerate() { + if current_round < delay + request.requested_round { + continue; } - // Calculate and apply slash - let slash_amount = percentage.mul_floor(delegation.amount); - delegation.amount = delegation - .amount - .checked_sub(&slash_amount) - .ok_or(Error::::InsufficientStakeRemaining)?; - - match delegation.asset_id { - Asset::Custom(asset_id) => { - // Transfer slashed amount to the treasury - let _ = T::Fungibles::transfer( - asset_id, - &Self::pallet_account(), - &T::SlashedAmountRecipient::get(), - slash_amount, - Preservation::Expendable, - ); - }, - Asset::Erc20(address) => { - let slashed_amount_recipient_evm = - T::EvmAddressMapping::into_address(T::SlashedAmountRecipient::get()); - let (success, _weight) = Self::erc20_transfer( - address, - &Self::pallet_evm_account(), - slashed_amount_recipient_evm, - slash_amount, - ) - .map_err(|_| Error::::ERC20TransferFailed)?; - ensure!(success, Error::::ERC20TransferFailed); - }, - } + *deposit_updates.entry(request.asset).or_default() += request.amount; - // emit event - Self::deposit_event(Event::DelegatorSlashed { - who: delegator.clone(), - amount: slash_amount, - }); + let delegation_key = (request.operator.clone(), request.asset); + if let Some(delegation_idx) = metadata.delegations.iter().position(|d| { + d.operator == request.operator && d.asset == request.asset && !d.is_nomination + }) { + let (_, total_unstake) = delegation_updates + .entry(delegation_key.clone()) + .or_insert((delegation_idx, BalanceOf::::zero())); + *total_unstake += request.amount; + } else { + return Err(Error::::NoActiveDelegation); + } - Ok(()) - }) + *operator_updates.entry(delegation_key).or_default() += request.amount; + indices_to_remove.push(idx); + } + ensure!(!indices_to_remove.is_empty(), Error::::BondLessNotReady); + Ok((deposit_updates, delegation_updates, operator_updates, indices_to_remove)) } } diff --git a/pallets/multi-asset-delegation/src/functions/deposit.rs b/pallets/multi-asset-delegation/src/functions/deposit.rs index 7058d9817..52ea67543 100644 --- a/pallets/multi-asset-delegation/src/functions/deposit.rs +++ b/pallets/multi-asset-delegation/src/functions/deposit.rs @@ -13,8 +13,8 @@ // // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . -use super::*; -use crate::{types::*, Pallet}; + +use crate::{types::*, Config, Delegators, Error, Pallet}; use frame_support::{ ensure, pallet_prelude::DispatchResult, @@ -45,13 +45,13 @@ impl Pallet { pub fn handle_transfer_to_pallet( sender: &T::AccountId, - asset_id: Asset, + asset: Asset, amount: BalanceOf, ) -> DispatchResult { - match asset_id { - Asset::Custom(asset_id) => { + match asset { + Asset::Custom(asset) => { T::Fungibles::transfer( - asset_id, + asset, sender, &Self::pallet_account(), amount, @@ -70,7 +70,7 @@ impl Pallet { /// # Arguments /// /// * `who` - The account ID of the delegator. - /// * `asset_id` - The optional asset ID of the assets to be deposited. + /// * `asset` - The optional asset ID of the assets to be deposited. /// * `amount` - The amount of assets to be deposited. /// /// # Errors @@ -79,14 +79,14 @@ impl Pallet { /// the transfer fails. pub fn process_deposit( who: T::AccountId, - asset_id: Asset, + asset: Asset, amount: BalanceOf, lock_multiplier: Option, ) -> DispatchResult { ensure!(amount >= T::MinDelegateAmount::get(), Error::::BondTooLow); // Transfer the amount to the pallet account - Self::handle_transfer_to_pallet(&who, asset_id, amount)?; + Self::handle_transfer_to_pallet(&who, asset, amount)?; let now = >::block_number(); @@ -94,17 +94,17 @@ impl Pallet { Delegators::::try_mutate(&who, |maybe_metadata| -> DispatchResult { let metadata = maybe_metadata.get_or_insert_with(Default::default); // If there's an existing deposit, increase it - if let Some(existing) = metadata.deposits.get_mut(&asset_id) { + if let Some(existing) = metadata.deposits.get_mut(&asset) { existing .increase_deposited_amount(amount, lock_multiplier, now) .map_err(|_| Error::::InsufficientBalance)?; } else { // Create a new deposit if none exists let new_deposit = Deposit::new(amount, lock_multiplier, now); - metadata.deposits.insert(asset_id, new_deposit); + metadata.deposits.insert(asset, new_deposit); } - let _ = T::RewardsManager::record_deposit(&who, asset_id, amount, lock_multiplier); + let _ = T::RewardsManager::record_deposit(&who, asset, amount, lock_multiplier); Ok(()) })?; @@ -117,7 +117,7 @@ impl Pallet { /// # Arguments /// /// * `who` - The account ID of the delegator. - /// * `asset_id` - The optional asset ID of the assets to be withdrawd. + /// * `asset` - The optional asset ID of the assets to be withdrawd. /// * `amount` - The amount of assets to be withdrawd. /// /// # Errors @@ -126,7 +126,7 @@ impl Pallet { /// asset is not supported. pub fn process_schedule_withdraw( who: T::AccountId, - asset_id: Asset, + asset: Asset, amount: BalanceOf, ) -> DispatchResult { Delegators::::try_mutate(&who, |maybe_metadata| { @@ -136,7 +136,7 @@ impl Pallet { // Ensure there is enough deposited balance let deposit = - metadata.deposits.get_mut(&asset_id).ok_or(Error::::InsufficientBalance)?; + metadata.deposits.get_mut(&asset).ok_or(Error::::InsufficientBalance)?; deposit .decrease_deposited_amount(amount, now) .map_err(|_| Error::::InsufficientBalance)?; @@ -145,11 +145,11 @@ impl Pallet { let current_round = Self::current_round(); let mut withdraw_requests = metadata.withdraw_requests.clone(); withdraw_requests - .try_push(WithdrawRequest { asset_id, amount, requested_round: current_round }) + .try_push(WithdrawRequest { asset, amount, requested_round: current_round }) .map_err(|_| Error::::MaxWithdrawRequestsExceeded)?; metadata.withdraw_requests = withdraw_requests; - let _ = T::RewardsManager::record_withdrawal(&who, asset_id, amount); + let _ = T::RewardsManager::record_withdrawal(&who, asset, amount); Ok(()) }) @@ -204,7 +204,7 @@ impl Pallet { let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; // Ensure there are outstanding withdraw requests - ensure!(!metadata.withdraw_requests.is_empty(), Error::::NowithdrawRequests); + ensure!(!metadata.withdraw_requests.is_empty(), Error::::NoWithdrawRequests); let current_round = Self::current_round(); let delay = T::LeaveDelegatorsDelay::get(); @@ -212,9 +212,9 @@ impl Pallet { // Process all ready withdraw requests using retain metadata.withdraw_requests.retain(|request| { if current_round >= delay + request.requested_round { - let transfer_success = match request.asset_id { - Asset::Custom(asset_id) => T::Fungibles::transfer( - asset_id, + let transfer_success = match request.asset { + Asset::Custom(asset) => T::Fungibles::transfer( + asset, &Self::pallet_account(), &who, request.amount, @@ -246,7 +246,7 @@ impl Pallet { /// # Arguments /// /// * `who` - The account ID of the delegator. - /// * `asset_id` - The asset ID of the withdraw request to cancel. + /// * `asset` - The asset ID of the withdraw request to cancel. /// * `amount` - The amount of the withdraw request to cancel. /// /// # Errors @@ -254,7 +254,7 @@ impl Pallet { /// Returns an error if the user is not a delegator or if there is no matching withdraw request. pub fn process_cancel_withdraw( who: T::AccountId, - asset_id: Asset, + asset: Asset, amount: BalanceOf, ) -> DispatchResult { Delegators::::try_mutate(&who, |maybe_metadata| { @@ -265,13 +265,13 @@ impl Pallet { let request_index = metadata .withdraw_requests .iter() - .position(|r| r.asset_id == asset_id && r.amount == amount) + .position(|r| r.asset == asset && r.amount == amount) .ok_or(Error::::NoMatchingwithdrawRequest)?; let withdraw_request = metadata.withdraw_requests.remove(request_index); // Add the amount back to the delegator's deposits - if let Some(deposit) = metadata.deposits.get_mut(&withdraw_request.asset_id) { + if let Some(deposit) = metadata.deposits.get_mut(&withdraw_request.asset) { deposit .increase_deposited_amount(withdraw_request.amount, None, now) .map_err(|_| Error::::InsufficientBalance)?; @@ -279,7 +279,7 @@ impl Pallet { // we are only able to withdraw from existing deposits without any locks // so when we add back, add it without any locks let new_deposit = Deposit::new(withdraw_request.amount, None, now); - metadata.deposits.insert(withdraw_request.asset_id, new_deposit); + metadata.deposits.insert(withdraw_request.asset, new_deposit); } // Update the status if no more delegations exist diff --git a/pallets/multi-asset-delegation/src/functions/evm.rs b/pallets/multi-asset-delegation/src/functions/evm.rs index f72391e95..2330a713c 100644 --- a/pallets/multi-asset-delegation/src/functions/evm.rs +++ b/pallets/multi-asset-delegation/src/functions/evm.rs @@ -1,5 +1,20 @@ -use super::*; -use crate::types::BalanceOf; +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use crate::{types::*, Config, Error, Event, Pallet}; use ethabi::{Function, StateMutability, Token}; use frame_support::{ dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo}, diff --git a/pallets/multi-asset-delegation/src/functions.rs b/pallets/multi-asset-delegation/src/functions/mod.rs similarity index 96% rename from pallets/multi-asset-delegation/src/functions.rs rename to pallets/multi-asset-delegation/src/functions/mod.rs index 42dd7c25a..926c4630f 100644 --- a/pallets/multi-asset-delegation/src/functions.rs +++ b/pallets/multi-asset-delegation/src/functions/mod.rs @@ -14,16 +14,16 @@ // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . +use crate::{Config, Pallet}; use frame_system::RawOrigin; use sp_runtime::traits::BadOrigin; -use super::*; - pub mod delegate; pub mod deposit; pub mod evm; pub mod operator; pub mod session_manager; +pub mod slash; /// Ensure that the origin `o` represents the current pallet (i.e. transaction). /// Returns `Ok` if the origin is the current pallet, `Err` otherwise. diff --git a/pallets/multi-asset-delegation/src/functions/operator.rs b/pallets/multi-asset-delegation/src/functions/operator.rs index ef603e778..090e599e2 100644 --- a/pallets/multi-asset-delegation/src/functions/operator.rs +++ b/pallets/multi-asset-delegation/src/functions/operator.rs @@ -14,21 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . -/// Functions for the pallet. -use super::*; -use crate::{types::*, Pallet}; +use crate::{types::*, Config, Error, Operators, Pallet}; use frame_support::{ ensure, pallet_prelude::DispatchResult, - traits::{Currency, ExistenceRequirement, Get, ReservableCurrency}, + traits::{Get, ReservableCurrency}, BoundedVec, }; use sp_runtime::{ traits::{CheckedAdd, CheckedSub}, - DispatchError, Percent, + DispatchError, }; use tangle_primitives::traits::ServiceManager; -use tangle_primitives::BlueprintId; impl Pallet { /// Handles the deposit of stake amount and creation of an operator. @@ -279,7 +276,10 @@ impl Pallet { pub fn process_go_offline(who: &T::AccountId) -> Result<(), DispatchError> { let mut operator = Operators::::get(who).ok_or(Error::::NotAnOperator)?; ensure!(operator.status == OperatorStatus::Active, Error::::NotActiveOperator); - + ensure!( + !T::ServiceManager::has_active_services(who), + Error::::CannotGoOfflineWithActiveServices + ); operator.status = OperatorStatus::Inactive; Operators::::insert(who, operator); @@ -304,43 +304,4 @@ impl Pallet { Ok(()) } - - pub fn slash_operator( - operator: &T::AccountId, - blueprint_id: BlueprintId, - percentage: Percent, - ) -> Result<(), DispatchError> { - Operators::::try_mutate(operator, |maybe_operator| { - let operator_data = maybe_operator.as_mut().ok_or(Error::::NotAnOperator)?; - ensure!(operator_data.status == OperatorStatus::Active, Error::::NotActiveOperator); - - // Slash operator stake - let amount = percentage.mul_floor(operator_data.stake); - operator_data.stake = operator_data - .stake - .checked_sub(&amount) - .ok_or(Error::::InsufficientStakeRemaining)?; - - // Slash each delegator - for delegator in operator_data.delegations.iter() { - // Ignore errors from individual delegator slashing - let _ = - Self::slash_delegator(&delegator.delegator, operator, blueprint_id, percentage); - } - - // transfer the slashed amount to the treasury - T::Currency::unreserve(operator, amount); - let _ = T::Currency::transfer( - operator, - &T::SlashedAmountRecipient::get(), - amount, - ExistenceRequirement::AllowDeath, - ); - - // emit event - Self::deposit_event(Event::OperatorSlashed { who: operator.clone(), amount }); - - Ok(()) - }) - } } diff --git a/pallets/multi-asset-delegation/src/functions/session_manager.rs b/pallets/multi-asset-delegation/src/functions/session_manager.rs index a7024ed05..8d48af9c6 100644 --- a/pallets/multi-asset-delegation/src/functions/session_manager.rs +++ b/pallets/multi-asset-delegation/src/functions/session_manager.rs @@ -13,8 +13,8 @@ // // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . -use super::*; -use crate::{types::*, Pallet}; + +use crate::{types::*, AtStake, Config, CurrentRound, Operators, Pallet}; impl Pallet { pub fn handle_round_change(i: u32) { diff --git a/pallets/multi-asset-delegation/src/functions/slash.rs b/pallets/multi-asset-delegation/src/functions/slash.rs new file mode 100644 index 000000000..8cbd44374 --- /dev/null +++ b/pallets/multi-asset-delegation/src/functions/slash.rs @@ -0,0 +1,199 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use crate::{types::*, Config, Delegators, Error, Event, Operators, Pallet}; +use frame_support::{ + dispatch::DispatchResult, + ensure, + traits::{ + fungibles::Mutate, tokens::Preservation, Currency, ExistenceRequirement, Get, + ReservableCurrency, + }, + weights::Weight, +}; +use sp_runtime::{traits::CheckedSub, DispatchError}; +use tangle_primitives::services::EvmAddressMapping; +use tangle_primitives::{ + services::{Asset, UnappliedSlash}, + traits::SlashManager, +}; + +impl Pallet { + /// Helper function to update operator storage for a slash + pub(crate) fn do_slash_operator( + unapplied_slash: &UnappliedSlash, + ) -> Result { + let mut weight = T::DbWeight::get().reads(1); + + Operators::::try_mutate( + &unapplied_slash.operator, + |maybe_operator| -> DispatchResult { + let operator_data = maybe_operator.as_mut().ok_or(Error::::NotAnOperator)?; + ensure!( + operator_data.status == OperatorStatus::Active, + Error::::NotActiveOperator + ); + // Slash operator stake + let amount = unapplied_slash.slash_percent.mul_floor(operator_data.stake); + operator_data.stake = operator_data + .stake + .checked_sub(&amount) + .ok_or(Error::::InsufficientStakeRemaining)?; + + // transfer the slashed amount to the treasury + T::Currency::unreserve(&unapplied_slash.operator, amount); + let _ = T::Currency::transfer( + &unapplied_slash.operator, + &T::SlashRecipient::get(), + amount, + ExistenceRequirement::AllowDeath, + ); + + // Emit event for operator slash + Self::deposit_event(Event::OperatorSlashed { + operator: unapplied_slash.operator.clone(), + amount, + service_id: unapplied_slash.service_id, + blueprint_id: unapplied_slash.blueprint_id, + era: unapplied_slash.era, + }); + + // Slash each delegator + for delegator in operator_data.delegations.iter() { + // Ignore errors from individual delegator slashing + let _ = Self::do_slash_delegator(unapplied_slash, &delegator.delegator); + } + + weight += T::DbWeight::get().writes(1); + Ok(()) + }, + )?; + + Ok(weight) + } + + /// Helper function to update delegator storage for a slash + pub(crate) fn do_slash_delegator( + unapplied_slash: &UnappliedSlash, + delegator: &T::AccountId, + ) -> Result { + let weight = T::DbWeight::get().reads(1); + + Delegators::::try_mutate(delegator, |maybe_metadata| -> DispatchResult { + let metadata = maybe_metadata.as_mut().ok_or(Error::::NotDelegator)?; + + // Find the delegation to the slashed operator + let delegation = metadata + .delegations + .iter_mut() + .find(|d| { + d.operator == unapplied_slash.operator + && d.blueprint_selection.contains(&unapplied_slash.blueprint_id) + }) + .ok_or(Error::::NoActiveDelegation)?; + + // Update delegator's stake + let slash_amount = unapplied_slash.slash_percent.mul_floor(delegation.amount); + delegation.amount = delegation + .amount + .checked_sub(&slash_amount) + .ok_or(Error::::InsufficientStakeRemaining)?; + + if delegation.is_nomination { + Self::apply_nominated_delegation_slash( + delegator, + &unapplied_slash.operator, + slash_amount, + )?; + } else { + Self::handle_asset_transfer(delegation.asset, slash_amount)?; + } + + Self::deposit_event(Event::DelegatorSlashed { + delegator: delegator.clone(), + asset: delegation.asset, + amount: slash_amount, + service_id: unapplied_slash.service_id, + blueprint_id: unapplied_slash.blueprint_id, + era: unapplied_slash.era, + }); + Ok(()) + })?; + + Ok(weight) + } + + /// Apply a slash for native asset delegations (both nominated and non-nominated) + fn apply_nominated_delegation_slash( + _delegator: &T::AccountId, + _operator: &T::AccountId, + _slash_amount: BalanceOf, + ) -> Result { + let weight: Weight = Weight::zero(); + + // TODO: Slash the nomination + + Ok(weight) + } + + /// Apply a slash for non-native asset delegations (custom assets and ERC20) + fn handle_asset_transfer( + asset: Asset, + slash_amount: BalanceOf, + ) -> Result { + let weight: Weight = Weight::zero(); + + match asset { + Asset::Custom(asset_id) => { + // Transfer slashed amount to the treasury + let _ = T::Fungibles::transfer( + asset_id, + &Self::pallet_account(), + &T::SlashRecipient::get(), + slash_amount, + Preservation::Expendable, + ); + }, + Asset::Erc20(address) => { + let slashed_amount_recipient_evm = + T::EvmAddressMapping::into_address(T::SlashRecipient::get()); + let (success, _weight) = Self::erc20_transfer( + address, + &Self::pallet_evm_account(), + slashed_amount_recipient_evm, + slash_amount, + ) + .map_err(|_| Error::::ERC20TransferFailed)?; + ensure!(success, Error::::ERC20TransferFailed); + }, + } + + Ok(weight) + } +} + +impl SlashManager for Pallet { + /// Updates operator storage to reflect a slash. + /// This only updates the storage items and does not handle asset transfers. + /// + /// # Arguments + /// * `unapplied_slash` - The unapplied slash record containing slash details + fn slash_operator( + unapplied_slash: &UnappliedSlash, + ) -> Result { + Self::do_slash_operator(unapplied_slash) + } +} diff --git a/pallets/multi-asset-delegation/src/lib.rs b/pallets/multi-asset-delegation/src/lib.rs index 8c66cf9ed..8e19a0be1 100644 --- a/pallets/multi-asset-delegation/src/lib.rs +++ b/pallets/multi-asset-delegation/src/lib.rs @@ -66,6 +66,9 @@ pub mod mock_evm; #[cfg(test)] mod tests; +#[cfg(any(test, feature = "fuzzing"))] +mod extra; + pub mod weights; #[cfg(feature = "runtime-benchmarks")] @@ -74,7 +77,6 @@ mod benchmarking; pub mod functions; pub mod traits; pub mod types; -pub use functions::*; /// The log target of this pallet. pub const LOG_TARGET: &str = "runtime::multi-asset-delegation"; @@ -92,8 +94,8 @@ pub mod pallet { use pallet_session::SessionManager; use scale_info::TypeInfo; use sp_core::H160; - use sp_runtime::traits::{MaybeSerializeDeserialize, Member}; - use sp_staking::SessionIndex; + use sp_runtime::traits::{MaybeSerializeDeserialize, Member, Zero}; + use sp_staking::{SessionIndex, StakingInterface}; use sp_std::{fmt::Debug, prelude::*, vec::Vec}; use tangle_primitives::traits::RewardsManager; use tangle_primitives::types::rewards::LockMultiplier; @@ -123,7 +125,8 @@ pub mod pallet { + MaxEncodedLen + Encode + Decode - + TypeInfo; + + TypeInfo + + Zero; /// The maximum number of blueprints a delegator can have in Fixed mode. #[pallet::constant] @@ -187,9 +190,6 @@ pub mod pallet { /// The origin with privileged access type ForceOrigin: EnsureOrigin; - /// The address that receives slashed funds - type SlashedAmountRecipient: Get; - /// A type that implements the `EvmRunner` trait for the execution of EVM /// transactions. type EvmRunner: tangle_primitives::services::EvmRunner; @@ -211,6 +211,19 @@ pub mod pallet { /// A type representing the weights required by the dispatchables of this pallet. type WeightInfo: crate::weights::WeightInfo; + + /// Currency to vote conversion + type CurrencyToVote: sp_staking::currency_to_vote::CurrencyToVote>; + + /// Interface to the staking system for nomination information + type StakingInterface: StakingInterface< + AccountId = Self::AccountId, + Balance = BalanceOf, + CurrencyToVote = Self::CurrencyToVote, + >; + + #[pallet::constant] + type SlashRecipient: Get; } /// The pallet struct. @@ -273,37 +286,108 @@ pub mod pallet { /// An operator has gone online. OperatorWentOnline { who: T::AccountId }, /// A deposit has been made. - Deposited { who: T::AccountId, amount: BalanceOf, asset_id: Asset }, + Deposited { who: T::AccountId, amount: BalanceOf, asset: Asset }, /// An withdraw has been scheduled. - Scheduledwithdraw { who: T::AccountId, amount: BalanceOf, asset_id: Asset }, + ScheduledWithdraw { who: T::AccountId, amount: BalanceOf, asset: Asset }, /// An withdraw has been executed. - Executedwithdraw { who: T::AccountId }, + ExecutedWithdraw { who: T::AccountId }, /// An withdraw has been cancelled. - Cancelledwithdraw { who: T::AccountId }, + CancelledWithdraw { who: T::AccountId }, /// A delegation has been made. Delegated { who: T::AccountId, operator: T::AccountId, amount: BalanceOf, - asset_id: Asset, + asset: Asset, }, /// A delegator unstake request has been scheduled. - ScheduledDelegatorBondLess { + DelegatorUnstakeScheduled { who: T::AccountId, operator: T::AccountId, + asset: Asset, amount: BalanceOf, - asset_id: Asset, + when: RoundIndex, }, /// A delegator unstake request has been executed. - ExecutedDelegatorBondLess { who: T::AccountId }, + DelegatorUnstakeExecuted { + who: T::AccountId, + operator: T::AccountId, + asset: Asset, + amount: BalanceOf, + }, /// A delegator unstake request has been cancelled. - CancelledDelegatorBondLess { who: T::AccountId }, - /// Operator has been slashed - OperatorSlashed { who: T::AccountId, amount: BalanceOf }, - /// Delegator has been slashed - DelegatorSlashed { who: T::AccountId, amount: BalanceOf }, + DelegatorUnstakeCancelled { + who: T::AccountId, + operator: T::AccountId, + asset: Asset, + amount: BalanceOf, + }, + /// An Operator has been slashed. + OperatorSlashed { + /// The account that has been slashed. + operator: T::AccountId, + /// The amount of the slash. + amount: BalanceOf, + /// Service ID + service_id: u64, + /// Blueprint ID + blueprint_id: u64, + /// Era index + era: u32, + }, + /// A Delegator has been slashed. + DelegatorSlashed { + /// The account that has been slashed. + delegator: T::AccountId, + /// The amount of the slash. + amount: BalanceOf, + /// The asset being slashed. + asset: Asset, + /// Service ID + service_id: u64, + /// Blueprint ID + blueprint_id: u64, + /// Era index + era: u32, + }, + /// A Delegator's nominated stake has been slashed. + NominatedSlash { + /// The account that has been slashed + delegator: T::AccountId, + /// The operator associated with the slash + operator: T::AccountId, + /// The amount of the slash + amount: BalanceOf, + /// Service ID + service_id: u64, + /// Blueprint ID + blueprint_id: u64, + /// Era index + era: u32, + }, /// EVM execution reverted with a reason. EvmReverted { from: H160, to: H160, data: Vec, reason: Vec }, + /// A nomination has been delegated + NominationDelegated { who: T::AccountId, operator: T::AccountId, amount: BalanceOf }, + /// A nomination unstake request has been scheduled. + NominationUnstakeScheduled { + who: T::AccountId, + operator: T::AccountId, + amount: BalanceOf, + when: RoundIndex, + }, + /// A nomination unstake request has been executed. + NominationUnstakeExecuted { + who: T::AccountId, + operator: T::AccountId, + amount: BalanceOf, + }, + /// A nomination unstake request has been cancelled. + NominationUnstakeCancelled { + who: T::AccountId, + operator: T::AccountId, + amount: BalanceOf, + }, } /// Errors emitted by the pallet. @@ -313,6 +397,8 @@ pub mod pallet { AlreadyOperator, /// The stake amount is too low. BondTooLow, + /// Amount is invalid + InvalidAmount, /// The account is not an operator. NotAnOperator, /// The account cannot exit. @@ -362,7 +448,7 @@ pub mod pallet { /// The blueprint ID is already whitelisted BlueprintAlreadyWhitelisted, /// No withdraw requests found - NowithdrawRequests, + NoWithdrawRequests, /// No matching withdraw reqests found NoMatchingwithdrawRequest, /// Asset already exists in a reward vault @@ -415,6 +501,10 @@ pub mod pallet { OverflowRisk, /// The asset config is not found AssetConfigNotFound, + /// Cannot go offline with active services + CannotGoOfflineWithActiveServices, + /// Not a nominator (for native restaking & delegation) + NotNominator, } /// Hooks for the pallet. @@ -622,6 +712,9 @@ pub mod pallet { /// Allows an operator to go offline. /// + /// Being offline means the operator should not be able to be + /// requested for services. + /// /// # Permissions /// /// * Must be signed by the operator account @@ -675,7 +768,7 @@ pub mod pallet { /// # Arguments /// /// * `origin` - Origin of the call - /// * `asset_id` - ID of the asset to deposit + /// * `asset` - Asset on to deposit /// * `amount` - Amount to deposit /// * `evm_address` - Optional EVM address /// @@ -687,32 +780,32 @@ pub mod pallet { #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn deposit( origin: OriginFor, - asset_id: Asset, + asset: Asset, amount: BalanceOf, evm_address: Option, lock_multiplier: Option, ) -> DispatchResult { - let who = match (asset_id, evm_address) { + let who = match (asset, evm_address) { (Asset::Custom(_), None) => ensure_signed(origin)?, (Asset::Erc20(_), Some(addr)) => { ensure_pallet::(origin)?; T::EvmAddressMapping::into_account_id(addr) }, (Asset::Erc20(_), None) => return Err(Error::::NotAuthorized.into()), - (Asset::Custom(_), Some(adress)) => { - let evm_account_id = T::EvmAddressMapping::into_account_id(adress); + (Asset::Custom(_), Some(address)) => { + let evm_account_id = T::EvmAddressMapping::into_account_id(address); let caller = ensure_signed(origin)?; ensure!(evm_account_id == caller, DispatchError::BadOrigin); evm_account_id }, }; // ensure the caps have not been exceeded - let remaining = T::RewardsManager::get_asset_deposit_cap_remaining(asset_id) + let remaining = T::RewardsManager::get_asset_deposit_cap_remaining(asset) .map_err(|_| Error::::AssetConfigNotFound)?; log::info!(target: crate::LOG_TARGET, "RewardsManager remaining: {:?}", remaining); ensure!(amount <= remaining, Error::::DepositExceedsCapForAsset); - Self::process_deposit(who.clone(), asset_id, amount, lock_multiplier)?; - Self::deposit_event(Event::Deposited { who, amount, asset_id }); + Self::process_deposit(who.clone(), asset, amount, lock_multiplier)?; + Self::deposit_event(Event::Deposited { who, amount, asset }); Ok(()) } @@ -725,7 +818,7 @@ pub mod pallet { /// # Arguments /// /// * `origin` - Origin of the call - /// * `asset_id` - ID of the asset to withdraw + /// * `asset` - Asset on to withdraw /// * `amount` - Amount to withdraw /// /// # Errors @@ -736,12 +829,12 @@ pub mod pallet { #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn schedule_withdraw( origin: OriginFor, - asset_id: Asset, + asset: Asset, amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::process_schedule_withdraw(who.clone(), asset_id, amount)?; - Self::deposit_event(Event::Scheduledwithdraw { who, amount, asset_id }); + Self::process_schedule_withdraw(who.clone(), asset, amount)?; + Self::deposit_event(Event::ScheduledWithdraw { who, amount, asset }); Ok(()) } @@ -771,7 +864,7 @@ pub mod pallet { None => ensure_signed(origin)?, }; Self::process_execute_withdraw(who.clone())?; - Self::deposit_event(Event::Executedwithdraw { who }); + Self::deposit_event(Event::ExecutedWithdraw { who }); Ok(()) } @@ -784,7 +877,7 @@ pub mod pallet { /// # Arguments /// /// * `origin` - Origin of the call - /// * `asset_id` - ID of the asset withdrawal to cancel + /// * `asset` - Asset on withdrawal to cancel /// * `amount` - Amount of the withdrawal to cancel /// /// # Errors @@ -794,12 +887,12 @@ pub mod pallet { #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn cancel_withdraw( origin: OriginFor, - asset_id: Asset, + asset: Asset, amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::process_cancel_withdraw(who.clone(), asset_id, amount)?; - Self::deposit_event(Event::Cancelledwithdraw { who }); + Self::process_cancel_withdraw(who.clone(), asset, amount)?; + Self::deposit_event(Event::CancelledWithdraw { who }); Ok(()) } @@ -813,7 +906,7 @@ pub mod pallet { /// /// * `origin` - Origin of the call /// * `operator` - Operator to delegate to - /// * `asset_id` - ID of asset to delegate + /// * `asset` - ID of asset to delegate /// * `amount` - Amount to delegate /// * `blueprint_selection` - Blueprint selection strategy /// @@ -827,7 +920,7 @@ pub mod pallet { pub fn delegate( origin: OriginFor, operator: T::AccountId, - asset_id: Asset, + asset: Asset, amount: BalanceOf, blueprint_selection: DelegatorBlueprintSelection, ) -> DispatchResult { @@ -835,11 +928,11 @@ pub mod pallet { Self::process_delegate( who.clone(), operator.clone(), - asset_id, + asset, amount, blueprint_selection, )?; - Self::deposit_event(Event::Delegated { who, operator, asset_id, amount }); + Self::deposit_event(Event::Delegated { who, operator, asset, amount }); Ok(()) } @@ -853,7 +946,7 @@ pub mod pallet { /// /// * `origin` - Origin of the call /// * `operator` - Operator to unstake from - /// * `asset_id` - ID of asset to unstake + /// * `asset` - ID of asset to unstake /// * `amount` - Amount to unstake /// /// # Errors @@ -866,21 +959,17 @@ pub mod pallet { pub fn schedule_delegator_unstake( origin: OriginFor, operator: T::AccountId, - asset_id: Asset, + asset: Asset, amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::process_schedule_delegator_unstake( - who.clone(), - operator.clone(), - asset_id, - amount, - )?; - Self::deposit_event(Event::ScheduledDelegatorBondLess { + Self::process_schedule_delegator_unstake(who.clone(), operator.clone(), asset, amount)?; + Self::deposit_event(Event::DelegatorUnstakeScheduled { who, - asset_id, operator, + asset, amount, + when: Self::current_round() + T::DelegationBondLessDelay::get(), }); Ok(()) } @@ -904,8 +993,17 @@ pub mod pallet { #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn execute_delegator_unstake(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - Self::process_execute_delegator_unstake(who.clone())?; - Self::deposit_event(Event::ExecutedDelegatorBondLess { who }); + let unstake_results = Self::process_execute_delegator_unstake(who.clone())?; + + // Emit an event for each operator/asset combination that was unstaked + for (operator, asset, amount) in unstake_results { + Self::deposit_event(Event::DelegatorUnstakeExecuted { + who: who.clone(), + operator, + asset, + amount, + }); + } Ok(()) } @@ -919,7 +1017,7 @@ pub mod pallet { /// /// * `origin` - Origin of the call /// * `operator` - Operator to cancel unstake from - /// * `asset_id` - ID of asset unstake to cancel + /// * `asset` - ID of asset unstake to cancel /// * `amount` - Amount of unstake to cancel /// /// # Errors @@ -931,12 +1029,139 @@ pub mod pallet { pub fn cancel_delegator_unstake( origin: OriginFor, operator: T::AccountId, - asset_id: Asset, + asset: Asset, amount: BalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::process_cancel_delegator_unstake(who.clone(), operator, asset_id, amount)?; - Self::deposit_event(Event::CancelledDelegatorBondLess { who }); + Self::process_cancel_delegator_unstake(who.clone(), operator.clone(), asset, amount)?; + Self::deposit_event(Event::DelegatorUnstakeCancelled { who, operator, asset, amount }); + Ok(()) + } + + /// Delegates nominated tokens to an operator. + /// + /// # Arguments + /// * `origin` - Origin of the call + /// * `operator` - The operator to delegate to + /// * `amount` - Amount of nominated tokens to delegate + /// * `blueprint_selection` - Strategy for selecting which blueprints to work with + /// + /// # Errors + /// * `NotDelegator` - Account is not a delegator + /// * `NotNominator` - Account has no nominated tokens + /// * `InsufficientBalance` - Not enough nominated tokens available + /// * `MaxDelegationsExceeded` - Would exceed maximum allowed delegations + /// * `OverflowRisk` - Arithmetic overflow during calculations + /// * `InvalidAmount` - Amount specified is zero + #[pallet::call_index(18)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + pub fn delegate_nomination( + origin: OriginFor, + operator: T::AccountId, + amount: BalanceOf, + blueprint_selection: DelegatorBlueprintSelection, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::process_delegate_nominations( + who.clone(), + operator.clone(), + amount, + blueprint_selection, + )?; + Self::deposit_event(Event::NominationDelegated { who, operator, amount }); + Ok(()) + } + + /// Schedules an unstake request for nomination delegations. + /// + /// # Arguments + /// * `origin` - Origin of the call + /// * `operator` - The operator to unstake from + /// * `amount` - Amount of nominated tokens to unstake + /// * `blueprint_selection` - The blueprint selection to use after unstaking + /// + /// # Errors + /// * `NotDelegator` - Account is not a delegator + /// * `NoActiveDelegation` - No active nomination delegation found + /// * `InsufficientBalance` - Trying to unstake more than delegated + /// * `MaxUnstakeRequestsExceeded` - Too many pending unstake requests + /// * `InvalidAmount` - Amount specified is zero + #[pallet::call_index(19)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + pub fn schedule_nomination_unstake( + origin: OriginFor, + operator: T::AccountId, + amount: BalanceOf, + blueprint_selection: DelegatorBlueprintSelection, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::process_schedule_delegator_nomination_unstake( + &who, + operator.clone(), + amount, + blueprint_selection, + )?; + Self::deposit_event(Event::NominationUnstakeScheduled { + who: who.clone(), + operator, + amount, + when: Self::current_round() + T::DelegationBondLessDelay::get(), + }); + Ok(()) + } + + /// Executes a scheduled unstake request for nomination delegations. + /// + /// # Arguments + /// * `origin` - Origin of the call + /// * `operator` - The operator to execute unstake from + /// + /// # Errors + /// * `NotDelegator` - Account is not a delegator + /// * `NoBondLessRequest` - No matching unstake request found + /// * `BondLessNotReady` - Unstake request not ready for execution + /// * `NoActiveDelegation` - No active nomination delegation found + /// * `InsufficientBalance` - Insufficient balance for unstaking + #[pallet::call_index(20)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + pub fn execute_nomination_unstake( + origin: OriginFor, + operator: T::AccountId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let amount = + Self::process_execute_delegator_nomination_unstake(&who, operator.clone())?; + Self::deposit_event(Event::NominationUnstakeExecuted { + who: who.clone(), + operator, + amount, + }); + Ok(()) + } + + /// Cancels a scheduled unstake request for nomination delegations. + /// + /// # Arguments + /// * `origin` - Origin of the call + /// * `operator` - The operator whose unstake request to cancel + /// + /// # Errors + /// * `NotDelegator` - Account is not a delegator + /// * `NoBondLessRequest` - No matching unstake request found + #[pallet::call_index(21)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + pub fn cancel_nomination_unstake( + origin: OriginFor, + operator: T::AccountId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let request = + Self::process_cancel_delegator_nomination_unstake(&who, operator.clone())?; + Self::deposit_event(Event::NominationUnstakeCancelled { + who: who.clone(), + operator, + amount: request.amount, + }); Ok(()) } diff --git a/pallets/multi-asset-delegation/src/mock.rs b/pallets/multi-asset-delegation/src/mock.rs index 26deb9af4..252629f61 100644 --- a/pallets/multi-asset-delegation/src/mock.rs +++ b/pallets/multi-asset-delegation/src/mock.rs @@ -39,10 +39,12 @@ use sp_core::{sr25519, H160}; use sp_keyring::AccountKeyring; use sp_keystore::{testing::MemoryKeystore, KeystoreExt, KeystorePtr}; use sp_runtime::{ + generic, testing::UintAuthorityId, traits::{ConvertInto, IdentityLookup, OpaqueKeys}, AccountId32, BoundToRuntimeAppPublic, BuildStorage, DispatchError, Perbill, }; +use sp_staking::currency_to_vote::U128CurrencyToVote; use std::cell::RefCell; use tangle_primitives::{ services::{EvmAddressMapping, EvmGasWeightMapping, EvmRunner}, @@ -199,7 +201,7 @@ impl pallet_staking::Config for Runtime { type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; - type CurrencyToVote = (); + type CurrencyToVote = U128CurrencyToVote; type RewardRemainder = (); type RuntimeEvent = RuntimeEvent; type Slash = (); @@ -297,6 +299,10 @@ impl tangle_primitives::traits::ServiceManager for MockServi fn get_blueprints_by_operator(_account: &AccountId) -> Vec { unimplemented!(); // we don't care } + + fn has_active_services(_operator: &AccountId) -> bool { + false + } } parameter_types! { @@ -305,15 +311,21 @@ parameter_types! { pub const MinOperatorBondAmount: u64 = 10_000; pub const BondDuration: u32 = 10; pub PID: PalletId = PalletId(*b"PotStake"); - pub SlashedAmountRecipient : AccountId = AccountKeyring::Alice.into(); + + pub const SlashRecipient: AccountId = AccountId32::new([1u8; 32]); + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxDelegatorBlueprints : u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxOperatorBlueprints : u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxWithdrawRequests: u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxUnstakeRequests: u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxDelegations: u32 = 50; } @@ -390,7 +402,10 @@ impl pallet_multi_asset_delegation::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type MinOperatorBondAmount = MinOperatorBondAmount; + type SlashRecipient = SlashRecipient; type BondDuration = BondDuration; + type CurrencyToVote = U128CurrencyToVote; + type StakingInterface = Staking; type ServiceManager = MockServiceManager; type LeaveOperatorsDelay = ConstU32<10>; type OperatorBondLessDelay = ConstU32<1>; @@ -406,7 +421,6 @@ impl pallet_multi_asset_delegation::Config for Runtime { type MaxWithdrawRequests = MaxWithdrawRequests; type MaxUnstakeRequests = MaxUnstakeRequests; type MaxDelegations = MaxDelegations; - type SlashedAmountRecipient = SlashedAmountRecipient; type EvmRunner = MockedEvmRunner; type EvmGasWeightMapping = PalletEVMGasWeightMapping; type EvmAddressMapping = PalletEVMAddressMapping; @@ -414,7 +428,82 @@ impl pallet_multi_asset_delegation::Config for Runtime { type WeightInfo = (); } -type Block = frame_system::mocking::MockBlock; +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, MaxEncodedLen, TypeInfo, +)] +pub enum ProxyType { + /// All calls can be proxied. This is the trivial/most permissive filter. + Any = 0, + /// Only extrinsics related to governance (democracy and collectives). + Governance = 1, + /// Allow to veto an announced proxy call. + CancelProxy = 2, + /// Allow extrinsic related to Balances. + Balances = 3, + /// Allow extrinsic related to Staking. + Staking = 4, +} + +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} + +impl frame_support::traits::InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::Governance => false, + ProxyType::CancelProxy => false, + ProxyType::Balances => matches!(c, RuntimeCall::Balances(..)), + ProxyType::Staking => matches!(c, RuntimeCall::Staking(..)), + } + } + fn is_superset(&self, o: &Self) -> bool { + match (self, o) { + (x, y) if x == y => true, + (ProxyType::Any, _) => true, + (_, ProxyType::Any) => false, + _ => false, + } + } +} + +impl pallet_proxy::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ConstU128<1>; + type ProxyDepositFactor = ConstU128<1>; + type MaxProxies = ConstU32<32>; + type WeightInfo = (); + type MaxPending = ConstU32<32>; + type CallHasher = sp_runtime::traits::BlakeTwo256; + type AnnouncementDepositBase = ConstU128<1>; + type AnnouncementDepositFactor = ConstU128<1>; +} + +impl pallet_utility::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); +} + +/// An unchecked extrinsic type to be used in tests. +pub type MockUncheckedExtrinsic = generic::UncheckedExtrinsic< + AccountId, + RuntimeCall, + u32, + extra::CheckNominatedRestaked, +>; + +/// An implementation of `sp_runtime::traits::Block` to be used in tests. +type Block = + generic::Block, MockUncheckedExtrinsic>; construct_runtime!( pub enum Runtime @@ -429,6 +518,8 @@ construct_runtime!( Session: pallet_session, Staking: pallet_staking, Historical: pallet_session_historical, + Proxy: pallet_proxy, + Utility: pallet_utility, } ); @@ -458,6 +549,9 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext } +pub const TNT: AssetId = 0; +pub const USDC: AssetId = 1; +pub const WETH: AssetId = 2; pub const USDC_ERC20: H160 = H160([0x23; 20]); pub const VDOT: AssetId = 4; @@ -516,6 +610,14 @@ pub fn new_test_ext_raw_authorities() -> sp_io::TestExternalities { evm_config.assimilate_storage(&mut t).unwrap(); + let staking_config = pallet_staking::GenesisConfig:: { + validator_count: 3, + invulnerables: authorities.clone(), + ..Default::default() + }; + + staking_config.assimilate_storage(&mut t).unwrap(); + // assets_config.assimilate_storage(&mut t).unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.register_extension(KeystoreExt(Arc::new(MemoryKeystore::new()) as KeystorePtr)); diff --git a/pallets/multi-asset-delegation/src/tests.rs b/pallets/multi-asset-delegation/src/tests.rs index 376a1b43b..1e9fb07db 100644 --- a/pallets/multi-asset-delegation/src/tests.rs +++ b/pallets/multi-asset-delegation/src/tests.rs @@ -18,6 +18,7 @@ use crate::{mock::*, tests::RuntimeEvent}; pub mod delegate; pub mod deposit; +pub mod native_restaking; pub mod operator; pub mod session_manager; diff --git a/pallets/multi-asset-delegation/src/tests/delegate.rs b/pallets/multi-asset-delegation/src/tests/delegate.rs index ed2e60043..c287e8dbd 100644 --- a/pallets/multi-asset-delegation/src/tests/delegate.rs +++ b/pallets/multi-asset-delegation/src/tests/delegate.rs @@ -17,7 +17,7 @@ use super::*; use crate::{CurrentRound, Error}; use frame_support::{assert_noop, assert_ok}; -use sp_keyring::AccountKeyring::{Alice, Bob}; +use sp_keyring::AccountKeyring::{Alice, Bob, Charlie}; use tangle_primitives::services::Asset; #[test] @@ -26,7 +26,7 @@ fn delegate_should_work() { // Arrange let who: AccountId = Bob.into(); let operator: AccountId = Alice.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; assert_ok!(MultiAssetDelegation::join_operators( @@ -39,7 +39,7 @@ fn delegate_should_work() { // Deposit first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id.clone(), + asset.clone(), amount, None, None, @@ -48,7 +48,7 @@ fn delegate_should_work() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, Default::default(), )); @@ -59,7 +59,7 @@ fn delegate_should_work() { let delegation = &metadata.delegations[0]; assert_eq!(delegation.operator, operator.clone()); assert_eq!(delegation.amount, amount); - assert_eq!(delegation.asset_id, asset_id); + assert_eq!(delegation.asset, asset); // Check the operator metadata let operator_metadata = MultiAssetDelegation::operator_info(operator.clone()).unwrap(); @@ -68,7 +68,7 @@ fn delegate_should_work() { let operator_delegation = &operator_metadata.delegations[0]; assert_eq!(operator_delegation.delegator, who.clone()); assert_eq!(operator_delegation.amount, amount); - assert_eq!(operator_delegation.asset_id, asset_id); + assert_eq!(operator_delegation.asset, asset); }); } @@ -78,7 +78,7 @@ fn schedule_delegator_unstake_should_work() { // Arrange let who: AccountId = Bob.into(); let operator: AccountId = Alice.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), amount); @@ -91,7 +91,7 @@ fn schedule_delegator_unstake_should_work() { // Deposit and delegate first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id.clone(), + asset.clone(), amount, None, None @@ -99,7 +99,7 @@ fn schedule_delegator_unstake_should_work() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, Default::default(), )); @@ -107,7 +107,7 @@ fn schedule_delegator_unstake_should_work() { assert_ok!(MultiAssetDelegation::schedule_delegator_unstake( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, )); @@ -116,9 +116,20 @@ fn schedule_delegator_unstake_should_work() { let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); assert!(!metadata.delegator_unstake_requests.is_empty()); let request = &metadata.delegator_unstake_requests[0]; - assert_eq!(request.asset_id, asset_id); + assert_eq!(request.asset, asset); assert_eq!(request.amount, amount); + // Check the operator metadata + let operator_metadata = MultiAssetDelegation::operator_info(operator.clone()).unwrap(); + assert_eq!(operator_metadata.delegation_count, 1); + assert_eq!(operator_metadata.delegations.len(), 1); + // Move to next round + CurrentRound::::put(10); + // Execute the unstake + assert_ok!(MultiAssetDelegation::execute_delegator_unstake(RuntimeOrigin::signed( + who.clone() + ),)); + // Check the operator metadata let operator_metadata = MultiAssetDelegation::operator_info(operator.clone()).unwrap(); assert_eq!(operator_metadata.delegation_count, 0); @@ -132,7 +143,7 @@ fn execute_delegator_unstake_should_work() { // Arrange let who: AccountId = Bob.into(); let operator: AccountId = Alice.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), amount); @@ -145,7 +156,7 @@ fn execute_delegator_unstake_should_work() { // Deposit, delegate and schedule unstake first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id.clone(), + asset.clone(), amount, None, None @@ -153,7 +164,7 @@ fn execute_delegator_unstake_should_work() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, Default::default(), )); @@ -161,7 +172,7 @@ fn execute_delegator_unstake_should_work() { assert_ok!(MultiAssetDelegation::schedule_delegator_unstake( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, )); @@ -175,8 +186,8 @@ fn execute_delegator_unstake_should_work() { // Assert let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); assert!(metadata.delegator_unstake_requests.is_empty()); - assert!(metadata.deposits.get(&asset_id).is_some()); - let deposit = metadata.deposits.get(&asset_id).unwrap(); + assert!(metadata.deposits.get(&asset).is_some()); + let deposit = metadata.deposits.get(&asset).unwrap(); assert_eq!(deposit.amount, amount); assert_eq!(deposit.delegated_amount, 0); }); @@ -188,7 +199,7 @@ fn cancel_delegator_unstake_should_work() { // Arrange let who: AccountId = Bob.into(); let operator: AccountId = Alice.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), amount); @@ -201,7 +212,7 @@ fn cancel_delegator_unstake_should_work() { // Deposit, delegate and schedule unstake first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id.clone(), + asset.clone(), amount, None, None @@ -209,7 +220,7 @@ fn cancel_delegator_unstake_should_work() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, Default::default(), )); @@ -217,7 +228,7 @@ fn cancel_delegator_unstake_should_work() { assert_ok!(MultiAssetDelegation::schedule_delegator_unstake( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, )); @@ -226,18 +237,18 @@ fn cancel_delegator_unstake_should_work() { let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); assert!(!metadata.delegator_unstake_requests.is_empty()); let request = &metadata.delegator_unstake_requests[0]; - assert_eq!(request.asset_id, asset_id); + assert_eq!(request.asset, asset); assert_eq!(request.amount, amount); // Check the operator metadata let operator_metadata = MultiAssetDelegation::operator_info(operator.clone()).unwrap(); - assert_eq!(operator_metadata.delegation_count, 0); - assert_eq!(operator_metadata.delegations.len(), 0); + assert_eq!(operator_metadata.delegation_count, 1); + assert_eq!(operator_metadata.delegations.len(), 1); assert_ok!(MultiAssetDelegation::cancel_delegator_unstake( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount )); @@ -253,7 +264,7 @@ fn cancel_delegator_unstake_should_work() { let operator_delegation = &operator_metadata.delegations[0]; assert_eq!(operator_delegation.delegator, who.clone()); assert_eq!(operator_delegation.amount, amount); // Amount added back - assert_eq!(operator_delegation.asset_id, asset_id); + assert_eq!(operator_delegation.asset, asset); }); } @@ -263,7 +274,7 @@ fn cancel_delegator_unstake_should_update_already_existing() { // Arrange let who: AccountId = Bob.into(); let operator: AccountId = Alice.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), amount); @@ -276,7 +287,7 @@ fn cancel_delegator_unstake_should_update_already_existing() { // Deposit, delegate and schedule unstake first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id.clone(), + asset.clone(), amount, None, None @@ -284,7 +295,7 @@ fn cancel_delegator_unstake_should_update_already_existing() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, Default::default(), )); @@ -292,7 +303,7 @@ fn cancel_delegator_unstake_should_update_already_existing() { assert_ok!(MultiAssetDelegation::schedule_delegator_unstake( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), 10, )); @@ -301,7 +312,7 @@ fn cancel_delegator_unstake_should_update_already_existing() { let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); assert!(!metadata.delegator_unstake_requests.is_empty()); let request = &metadata.delegator_unstake_requests[0]; - assert_eq!(request.asset_id, asset_id); + assert_eq!(request.asset, asset); assert_eq!(request.amount, 10); // Check the operator metadata @@ -310,13 +321,13 @@ fn cancel_delegator_unstake_should_update_already_existing() { assert_eq!(operator_metadata.delegations.len(), 1); let operator_delegation = &operator_metadata.delegations[0]; assert_eq!(operator_delegation.delegator, who.clone()); - assert_eq!(operator_delegation.amount, amount - 10); - assert_eq!(operator_delegation.asset_id, asset_id); + assert_eq!(operator_delegation.amount, amount); + assert_eq!(operator_delegation.asset, asset); assert_ok!(MultiAssetDelegation::cancel_delegator_unstake( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), 10 )); @@ -332,7 +343,7 @@ fn cancel_delegator_unstake_should_update_already_existing() { let operator_delegation = &operator_metadata.delegations[0]; assert_eq!(operator_delegation.delegator, who.clone()); assert_eq!(operator_delegation.amount, amount); // Amount added back - assert_eq!(operator_delegation.asset_id, asset_id); + assert_eq!(operator_delegation.asset, asset); }); } @@ -342,7 +353,7 @@ fn delegate_should_fail_if_not_enough_balance() { // Arrange let who: AccountId = Bob.into(); let operator: AccountId = Alice.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 10_000; create_and_mint_tokens(VDOT, who.clone(), amount); @@ -354,7 +365,7 @@ fn delegate_should_fail_if_not_enough_balance() { assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id.clone(), + asset.clone(), amount - 20, None, None @@ -364,7 +375,7 @@ fn delegate_should_fail_if_not_enough_balance() { MultiAssetDelegation::delegate( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, Default::default(), ), @@ -379,7 +390,7 @@ fn schedule_delegator_unstake_should_fail_if_no_delegation() { // Arrange let who: AccountId = Bob.into(); let operator: AccountId = Alice.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), amount); @@ -392,7 +403,7 @@ fn schedule_delegator_unstake_should_fail_if_no_delegation() { // Deposit first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id.clone(), + asset.clone(), amount, None, None @@ -402,7 +413,7 @@ fn schedule_delegator_unstake_should_fail_if_no_delegation() { MultiAssetDelegation::schedule_delegator_unstake( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, ), Error::::NoActiveDelegation @@ -416,7 +427,7 @@ fn execute_delegator_unstake_should_fail_if_not_ready() { // Arrange let who: AccountId = Bob.into(); let operator: AccountId = Alice.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), amount); @@ -429,7 +440,7 @@ fn execute_delegator_unstake_should_fail_if_not_ready() { // Deposit, delegate and schedule unstake first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id.clone(), + asset.clone(), amount, None, None @@ -437,7 +448,7 @@ fn execute_delegator_unstake_should_fail_if_not_ready() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, Default::default(), )); @@ -446,7 +457,7 @@ fn execute_delegator_unstake_should_fail_if_not_ready() { MultiAssetDelegation::cancel_delegator_unstake( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount ), Error::::NoBondLessRequest @@ -455,7 +466,7 @@ fn execute_delegator_unstake_should_fail_if_not_ready() { assert_ok!(MultiAssetDelegation::schedule_delegator_unstake( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, )); @@ -472,7 +483,7 @@ fn delegate_should_not_create_multiple_on_repeat_delegation() { // Arrange let who: AccountId = Bob.into(); let operator: AccountId = Alice.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; let additional_amount = 50; @@ -486,7 +497,7 @@ fn delegate_should_not_create_multiple_on_repeat_delegation() { // Deposit first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id.clone(), + asset.clone(), amount + additional_amount, None, None @@ -496,19 +507,19 @@ fn delegate_should_not_create_multiple_on_repeat_delegation() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), amount, Default::default(), )); // Assert first delegation let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); - assert!(metadata.deposits.get(&asset_id).is_some()); + assert!(metadata.deposits.get(&asset).is_some()); assert_eq!(metadata.delegations.len(), 1); let delegation = &metadata.delegations[0]; assert_eq!(delegation.operator, operator.clone()); assert_eq!(delegation.amount, amount); - assert_eq!(delegation.asset_id, asset_id); + assert_eq!(delegation.asset, asset); // Check the operator metadata let operator_metadata = MultiAssetDelegation::operator_info(operator.clone()).unwrap(); @@ -517,13 +528,13 @@ fn delegate_should_not_create_multiple_on_repeat_delegation() { let operator_delegation = &operator_metadata.delegations[0]; assert_eq!(operator_delegation.delegator, who.clone()); assert_eq!(operator_delegation.amount, amount); - assert_eq!(operator_delegation.asset_id, asset_id); + assert_eq!(operator_delegation.asset, asset); // Delegate additional amount assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(who.clone()), operator.clone(), - asset_id.clone(), + asset.clone(), additional_amount, Default::default(), )); @@ -536,6 +547,336 @@ fn delegate_should_not_create_multiple_on_repeat_delegation() { let updated_operator_delegation = &updated_operator_metadata.delegations[0]; assert_eq!(updated_operator_delegation.delegator, who.clone()); assert_eq!(updated_operator_delegation.amount, amount + additional_amount); - assert_eq!(updated_operator_delegation.asset_id, asset_id); + assert_eq!(updated_operator_delegation.asset, asset); + }); +} + +#[test] +fn delegate_exceeds_max_delegations() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let amount = 100; + + // Setup max number of operators + let mut operators = vec![]; + for i in 0..MaxDelegations::get() { + let operator_account: AccountId = AccountId::new([i as u8; 32]); + // Give operator enough balance to join + assert_ok!(Balances::force_set_balance( + RuntimeOrigin::root(), + operator_account.clone(), + 100_000 + )); + operators.push(operator_account.clone()); + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator_account), + 10_000 + )); + } + + // Create max number of delegations with same asset + let asset = Asset::Custom(999); + create_and_mint_tokens(999, who.clone(), amount * MaxDelegations::get() as u128); + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(who.clone()), + asset.clone(), + amount * MaxDelegations::get() as u128, + None, + None, + )); + + println!("Max delegations: {}", MaxDelegations::get()); + for i in 0..MaxDelegations::get() { + assert_ok!(MultiAssetDelegation::delegate( + RuntimeOrigin::signed(who.clone()), + operators[i as usize].clone(), + asset.clone(), + 1u128, + Default::default(), + )); + } + + let operator: AccountId = Charlie.into(); + // Give operator enough balance to join + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), operator.clone(), 100_000)); + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + assert_noop!( + MultiAssetDelegation::delegate( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + asset, + amount, + Default::default(), + ), + Error::::MaxDelegationsExceeded + ); + + // Verify state + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len() as u32, MaxDelegations::get()); + }); +} + +#[test] +fn delegate_insufficient_deposit() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let deposit_amount = 100; + let delegate_amount = deposit_amount + 1; + let asset = Asset::Custom(USDC); + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + create_and_mint_tokens(USDC, who.clone(), deposit_amount); + + // Make deposit + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(who.clone()), + asset.clone(), + deposit_amount, + None, + None, + )); + + // Try to delegate more than deposited + assert_noop!( + MultiAssetDelegation::delegate( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + asset.clone(), + delegate_amount, + Default::default(), + ), + Error::::InsufficientBalance + ); + + // Verify state remains unchanged + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 0); + assert_eq!(metadata.deposits.len(), 1); + assert_eq!(metadata.deposits.get(&asset).unwrap().amount, deposit_amount); + }); +} + +#[test] +fn delegate_to_inactive_operator() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let amount = 100; + + // Setup operator but make them inactive + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + assert_ok!(MultiAssetDelegation::go_offline(RuntimeOrigin::signed(operator.clone()))); + + // Make deposit + create_and_mint_tokens(USDC, who.clone(), amount); + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(who.clone()), + Asset::Custom(USDC), + amount, + None, + None, + )); + + // Try to delegate to inactive operator + assert_noop!( + MultiAssetDelegation::delegate( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + Asset::Custom(USDC), + amount, + Default::default(), + ), + Error::::NotActiveOperator + ); + + // Verify state remains unchanged + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 0); + }); +} + +#[test] +fn delegate_repeated_same_asset() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let initial_amount = 100; + let additional_amount = 50; + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Make deposit + create_and_mint_tokens(USDC, who.clone(), initial_amount + additional_amount); + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(who.clone()), + Asset::Custom(USDC), + initial_amount + additional_amount, + None, + None, + )); + + // First delegation + assert_ok!(MultiAssetDelegation::delegate( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + Asset::Custom(USDC), + initial_amount, + Default::default(), + )); + + // Second delegation with same asset + assert_ok!(MultiAssetDelegation::delegate( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + Asset::Custom(USDC), + additional_amount, + Default::default(), + )); + + // Verify state + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 1); + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.amount, initial_amount + additional_amount); + assert_eq!(delegation.operator, operator); + assert_eq!(delegation.asset, Asset::Custom(USDC)); + + // Verify operator state + let operator_metadata = MultiAssetDelegation::operator_info(operator.clone()).unwrap(); + assert_eq!(operator_metadata.delegation_count, 1); + let operator_delegation = &operator_metadata.delegations[0]; + assert_eq!(operator_delegation.amount, initial_amount + additional_amount); + assert_eq!(operator_delegation.delegator, who); + assert_eq!(operator_delegation.asset, Asset::Custom(USDC)); + }); +} + +#[test] +fn delegate_multiple_assets_same_operator() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let amount = 100; + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Make deposits for different assets + for asset in [USDC, WETH].iter() { + create_and_mint_tokens(*asset, who.clone(), amount); + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(who.clone()), + Asset::Custom(*asset), + amount, + None, + None, + )); + assert_ok!(MultiAssetDelegation::delegate( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + Asset::Custom(*asset), + amount, + Default::default(), + )); + } + + // Verify state + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 2); + + // Verify each delegation + for (i, asset_id) in [USDC, WETH].iter().enumerate() { + let delegation = &metadata.delegations[i]; + assert_eq!(delegation.amount, amount); + assert_eq!(delegation.operator, operator); + assert_eq!(delegation.asset, Asset::Custom(*asset_id)); + } + + // Verify operator state + let operator_metadata = MultiAssetDelegation::operator_info(operator.clone()).unwrap(); + assert_eq!(operator_metadata.delegation_count, 2); + for (i, asset_id) in [USDC, WETH].iter().enumerate() { + let operator_delegation = &operator_metadata.delegations[i]; + assert_eq!(operator_delegation.amount, amount); + assert_eq!(operator_delegation.delegator, who); + assert_eq!(operator_delegation.asset, Asset::Custom(*asset_id)); + } + }); +} + +#[test] +fn delegate_zero_amount() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Try to delegate zero amount + assert_noop!( + MultiAssetDelegation::delegate( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + Asset::Custom(USDC), + 0, + Default::default(), + ), + Error::::InvalidAmount + ); + }); +} + +#[test] +fn delegate_with_no_deposit() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let amount = 100; + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Try to delegate without deposit + assert_noop!( + MultiAssetDelegation::delegate( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + Asset::Custom(USDC), + amount, + Default::default(), + ), + Error::::NotDelegator + ); + + // Verify state remains unchanged + let metadata = MultiAssetDelegation::delegators(who.clone()); + assert_eq!(metadata.is_none(), true); }); } diff --git a/pallets/multi-asset-delegation/src/tests/deposit.rs b/pallets/multi-asset-delegation/src/tests/deposit.rs index efa65e5c0..805c55e03 100644 --- a/pallets/multi-asset-delegation/src/tests/deposit.rs +++ b/pallets/multi-asset-delegation/src/tests/deposit.rs @@ -20,23 +20,22 @@ use sp_keyring::AccountKeyring::Bob; use sp_runtime::{ArithmeticError, DispatchError}; use tangle_primitives::services::{Asset, EvmAddressMapping}; -// helper function pub fn create_and_mint_tokens( - asset_id: AssetId, + asset: AssetId, recipient: ::AccountId, amount: Balance, ) { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, recipient.clone(), false, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(recipient.clone()), asset_id, recipient, amount)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset, recipient.clone(), false, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(recipient.clone()), asset, recipient, amount)); } pub fn mint_tokens( owner: ::AccountId, - asset_id: AssetId, + asset: AssetId, recipient: ::AccountId, amount: Balance, ) { - assert_ok!(Assets::mint(RuntimeOrigin::signed(owner), asset_id, recipient, amount)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(owner), asset, recipient, amount)); } #[test] @@ -66,7 +65,7 @@ fn deposit_should_work_for_fungible_asset() { RuntimeEvent::MultiAssetDelegation(crate::Event::Deposited { who: who.clone(), amount, - asset_id: Asset::Custom(VDOT), + asset: Asset::Custom(VDOT), }) ); @@ -105,7 +104,7 @@ fn deposit_should_work_for_evm_asset() { RuntimeEvent::MultiAssetDelegation(crate::Event::Deposited { who: who.clone(), amount, - asset_id: Asset::Custom(VDOT), + asset: Asset::Custom(VDOT), }) ); }); @@ -138,7 +137,7 @@ fn multiple_deposit_should_work() { RuntimeEvent::MultiAssetDelegation(crate::Event::Deposited { who: who.clone(), amount, - asset_id: Asset::Custom(VDOT), + asset: Asset::Custom(VDOT), }) ); @@ -160,7 +159,7 @@ fn multiple_deposit_should_work() { RuntimeEvent::MultiAssetDelegation(crate::Event::Deposited { who: who.clone(), amount, - asset_id: Asset::Custom(VDOT), + asset: Asset::Custom(VDOT), }) ); }); @@ -215,7 +214,7 @@ fn schedule_withdraw_should_work() { new_test_ext().execute_with(|| { // Arrange let who: AccountId = Bob.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), 100); @@ -223,7 +222,7 @@ fn schedule_withdraw_should_work() { // Deposit first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, None, None @@ -231,13 +230,13 @@ fn schedule_withdraw_should_work() { assert_ok!(MultiAssetDelegation::schedule_withdraw( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, )); // Assert let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); - let deposit = metadata.deposits.get(&asset_id).unwrap(); + let deposit = metadata.deposits.get(&asset).unwrap(); assert_eq!(deposit.amount, 0_u32.into()); assert!(!metadata.withdraw_requests.is_empty()); @@ -254,7 +253,7 @@ fn schedule_withdraw_should_fail_if_not_delegator() { new_test_ext().execute_with(|| { // Arrange let who: AccountId = Bob.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), 100); @@ -262,7 +261,7 @@ fn schedule_withdraw_should_fail_if_not_delegator() { assert_noop!( MultiAssetDelegation::schedule_withdraw( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, ), Error::::NotDelegator @@ -275,7 +274,7 @@ fn schedule_withdraw_should_fail_for_insufficient_balance() { new_test_ext().execute_with(|| { // Arrange let who: AccountId = Bob.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 200; create_and_mint_tokens(VDOT, who.clone(), 100); @@ -283,7 +282,7 @@ fn schedule_withdraw_should_fail_for_insufficient_balance() { // Deposit first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, 100, None, None @@ -292,7 +291,7 @@ fn schedule_withdraw_should_fail_for_insufficient_balance() { assert_noop!( MultiAssetDelegation::schedule_withdraw( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, ), Error::::InsufficientBalance @@ -305,7 +304,7 @@ fn schedule_withdraw_should_fail_if_withdraw_request_exists() { new_test_ext().execute_with(|| { // Arrange let who: AccountId = Bob.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), 100); @@ -313,7 +312,7 @@ fn schedule_withdraw_should_fail_if_withdraw_request_exists() { // Deposit first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, None, None @@ -322,7 +321,7 @@ fn schedule_withdraw_should_fail_if_withdraw_request_exists() { // Schedule the first withdraw assert_ok!(MultiAssetDelegation::schedule_withdraw( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, )); }); @@ -333,7 +332,7 @@ fn execute_withdraw_should_work() { new_test_ext().execute_with(|| { // Arrange let who: AccountId = Bob.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), 100); @@ -341,14 +340,14 @@ fn execute_withdraw_should_work() { // Deposit and schedule withdraw first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, None, None )); assert_ok!(MultiAssetDelegation::schedule_withdraw( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, )); @@ -367,7 +366,7 @@ fn execute_withdraw_should_work() { // Check event System::assert_last_event(RuntimeEvent::MultiAssetDelegation( - crate::Event::Executedwithdraw { who: who.clone() }, + crate::Event::ExecutedWithdraw { who: who.clone() }, )); }); } @@ -390,7 +389,7 @@ fn execute_withdraw_should_fail_if_no_withdraw_request() { new_test_ext().execute_with(|| { // Arrange let who: AccountId = Bob.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), 100); @@ -398,7 +397,7 @@ fn execute_withdraw_should_fail_if_no_withdraw_request() { // Deposit first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, None, None @@ -406,7 +405,7 @@ fn execute_withdraw_should_fail_if_no_withdraw_request() { assert_noop!( MultiAssetDelegation::execute_withdraw(RuntimeOrigin::signed(who.clone()), None), - Error::::NowithdrawRequests + Error::::NoWithdrawRequests ); }); } @@ -416,7 +415,7 @@ fn execute_withdraw_should_fail_if_withdraw_not_ready() { new_test_ext().execute_with(|| { // Arrange let who: AccountId = Bob.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), 100); @@ -424,7 +423,7 @@ fn execute_withdraw_should_fail_if_withdraw_not_ready() { // Deposit and schedule withdraw first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, None, None @@ -432,7 +431,7 @@ fn execute_withdraw_should_fail_if_withdraw_not_ready() { assert_ok!(MultiAssetDelegation::schedule_withdraw( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, )); @@ -458,14 +457,14 @@ fn execute_withdraw_should_fail_if_caller_not_pallet_from_evm() { let evm_address = mock_address(1); let pallet_account_id = MultiAssetDelegation::pallet_account(); let who = PalletEVMAddressMapping::into_account_id(evm_address); - let asset_id = Asset::Erc20(USDC_ERC20); + let asset = Asset::Erc20(USDC_ERC20); let amount = 100; // Deposit would fail because the origin is not from the pallet assert_err!( MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, Some(evm_address), None @@ -476,7 +475,7 @@ fn execute_withdraw_should_fail_if_caller_not_pallet_from_evm() { // Try with the pallet account should work assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(pallet_account_id.clone()), - asset_id, + asset, amount, Some(evm_address), None @@ -484,7 +483,7 @@ fn execute_withdraw_should_fail_if_caller_not_pallet_from_evm() { assert_ok!(MultiAssetDelegation::schedule_withdraw( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, )); @@ -514,7 +513,7 @@ fn cancel_withdraw_should_work() { new_test_ext().execute_with(|| { // Arrange let who: AccountId = Bob.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), 100); @@ -522,7 +521,7 @@ fn cancel_withdraw_should_work() { // Deposit and schedule withdraw first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, None, None @@ -530,25 +529,25 @@ fn cancel_withdraw_should_work() { assert_ok!(MultiAssetDelegation::schedule_withdraw( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, )); assert_ok!(MultiAssetDelegation::cancel_withdraw( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount )); // Assert let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); - let deposit = metadata.deposits.get(&asset_id).unwrap(); + let deposit = metadata.deposits.get(&asset).unwrap(); assert_eq!(deposit.amount, amount); assert!(metadata.withdraw_requests.is_empty()); // Check event System::assert_last_event(RuntimeEvent::MultiAssetDelegation( - crate::Event::Cancelledwithdraw { who: who.clone() }, + crate::Event::CancelledWithdraw { who: who.clone() }, )); }); } @@ -575,7 +574,7 @@ fn cancel_withdraw_should_fail_if_no_withdraw_request() { new_test_ext().execute_with(|| { // Arrange let who: AccountId = Bob.into(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount = 100; create_and_mint_tokens(VDOT, who.clone(), 100); @@ -583,7 +582,7 @@ fn cancel_withdraw_should_fail_if_no_withdraw_request() { // Deposit first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount, None, None @@ -592,7 +591,7 @@ fn cancel_withdraw_should_fail_if_no_withdraw_request() { assert_noop!( MultiAssetDelegation::cancel_withdraw( RuntimeOrigin::signed(who.clone()), - asset_id, + asset, amount ), Error::::NoMatchingwithdrawRequest diff --git a/pallets/multi-asset-delegation/src/tests/native_restaking.rs b/pallets/multi-asset-delegation/src/tests/native_restaking.rs new file mode 100644 index 000000000..420c2880a --- /dev/null +++ b/pallets/multi-asset-delegation/src/tests/native_restaking.rs @@ -0,0 +1,841 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use crate::{CurrentRound, Error}; +use extra::CheckNominatedRestaked; +use frame_support::{ + assert_err, assert_noop, assert_ok, + dispatch::DispatchInfo, + pallet_prelude::{InvalidTransaction, TransactionValidityError}, + traits::Hooks, +}; +use sp_keyring::AccountKeyring::{Alice, Bob, Charlie, Dave}; +use sp_runtime::traits::SignedExtension; +use tangle_primitives::services::Asset; + +#[test] +fn native_restaking_should_work() { + new_test_ext().execute_with(|| { + // Arrange + let who: AccountId = Dave.into(); + let validator = Staking::invulnerables()[0].clone(); + let operator: AccountId = Alice.into(); + let amount = 100_000; + let delegate_amount = amount / 2; + // Bond Some TNT + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + // Nominate the validator + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![validator.clone()])); + + System::set_block_number(2); + >::on_initialize(2); + >::on_initialize(2); + >::on_finalize(2); + >::on_finalize(2); + // Assert + let ledger = Staking::ledger(sp_staking::StakingAccount::Stash(who.clone())).unwrap(); + assert_eq!(ledger.active, amount); + assert_eq!(ledger.total, amount); + assert_eq!(ledger.unlocking.len(), 0); + + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Restake + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + delegate_amount, + Default::default(), + )); + // Assert + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 1); + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.operator, operator.clone()); + assert_eq!(delegation.amount, delegate_amount); + assert_eq!(delegation.asset, Asset::Custom(TNT)); + // Check the locks + let locks = pallet_balances::Pallet::::locks(&who); + // 1 lock for the staking + // 1 lock for the delegation + assert_eq!(locks.len(), 2); + assert_eq!(&locks[0].id, b"staking "); + assert_eq!(locks[0].amount, amount); + assert_eq!(&locks[1].id, b"delegate"); + assert_eq!(locks[1].amount, delegate_amount); + }); +} + +#[test] +fn unbond_should_fail_if_delegated_nomination() { + new_test_ext().execute_with(|| { + // Arrange + let who: AccountId = Dave.into(); + let validator = Staking::invulnerables()[0].clone(); + let operator: AccountId = Alice.into(); + let amount = 100_000; + let delegate_amount = amount / 2; + // Bond Some TNT + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + // Nominate the validator + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![validator.clone()])); + + System::set_block_number(2); + >::on_initialize(2); + >::on_initialize(2); + >::on_finalize(2); + >::on_finalize(2); + + // Verify initial staking state + let ledger = Staking::ledger(sp_staking::StakingAccount::Stash(who.clone())).unwrap(); + assert_eq!(ledger.active, amount); + assert_eq!(ledger.total, amount); + assert_eq!(ledger.unlocking.len(), 0); + + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Restake + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + delegate_amount, + Default::default(), + )); + + // Verify delegation state + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 1); + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.operator, operator); + assert_eq!(delegation.amount, delegate_amount); + assert!(delegation.is_nomination); + assert_eq!(delegation.asset, Asset::Custom(TNT)); + + // Check operator metadata + let operator_metadata = MultiAssetDelegation::operator_info(operator.clone()).unwrap(); + assert_eq!(operator_metadata.delegation_count, 1); + let operator_delegation = &operator_metadata.delegations[0]; + assert_eq!(operator_delegation.delegator, who.clone()); + assert_eq!(operator_delegation.amount, delegate_amount); + + // Check locks before unbond attempt + let locks = pallet_balances::Pallet::::locks(&who); + assert_eq!(locks.len(), 2); + assert_eq!(&locks[0].id, b"staking "); + assert_eq!(locks[0].amount, amount); + assert_eq!(&locks[1].id, b"delegate"); + assert_eq!(locks[1].amount, delegate_amount); + let call = RuntimeCall::Staking(pallet_staking::Call::unbond { value: amount }); + + // Try to unbond from the staking pallet - should fail + assert_err!( + CheckNominatedRestaked::::new().validate( + &who, + &call, + &DispatchInfo::default(), + 0 + ), + TransactionValidityError::Invalid(InvalidTransaction::Custom(1)) + ); + + // Verify state remains unchanged after failed unbond + let ledger = Staking::ledger(sp_staking::StakingAccount::Stash(who.clone())).unwrap(); + assert_eq!(ledger.active, amount); + assert_eq!(ledger.total, amount); + assert_eq!(ledger.unlocking.len(), 0); + + // Verify delegation state remains unchanged + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 1); + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.operator, operator); + assert_eq!(delegation.amount, delegate_amount); + assert!(delegation.is_nomination); + + // Verify locks remain unchanged + let locks = pallet_balances::Pallet::::locks(&who); + assert_eq!(locks.len(), 2); + assert_eq!(&locks[0].id, b"staking "); + assert_eq!(locks[0].amount, amount); + assert_eq!(&locks[1].id, b"delegate"); + assert_eq!(locks[1].amount, delegate_amount); + }); +} + +#[test] +fn successful_multiple_native_restaking() { + new_test_ext().execute_with(|| { + // Arrange + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let total_nomination = 100; + let first_restake = 40; + let second_restake = 30; + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Setup nomination + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + total_nomination, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![operator.clone()])); + + // First restake + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + first_restake, + Default::default(), + )); + + // Second restake + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + second_restake, + Default::default(), + )); + + // Assert + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 1); + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.operator, operator.clone()); + assert_eq!(delegation.amount, first_restake + second_restake); + + // Check operator metadata + let operator_metadata = MultiAssetDelegation::operator_info(operator.clone()).unwrap(); + assert_eq!(operator_metadata.delegation_count, 1); + let operator_delegation = &operator_metadata.delegations[0]; + assert_eq!(operator_delegation.delegator, who.clone()); + assert_eq!(operator_delegation.amount, first_restake + second_restake); + }); +} + +#[test] +fn native_restake_exceeding_nomination_amount() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let nomination_amount = 100; + let excessive_amount = 150; + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Setup nomination + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + nomination_amount, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![operator.clone()])); + + // Try to restake more than nominated + assert_noop!( + MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + excessive_amount, + Default::default(), + ), + Error::::InsufficientBalance + ); + }); +} + +#[test] +fn native_restake_with_no_active_nomination() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let amount = 100; + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Try to restake without nomination + assert_noop!( + MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + amount, + Default::default(), + ), + Error::::NotNominator + ); + }); +} + +#[test] +fn native_restake_to_non_operator() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let non_operator: AccountId = Charlie.into(); + let amount = 100; + + // Setup nomination + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate( + RuntimeOrigin::signed(who.clone()), + vec![non_operator.clone()] + )); + + // Try to restake to non-operator + assert_noop!( + MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + non_operator.clone(), + amount, + Default::default(), + ), + Error::::NotAnOperator + ); + }); +} + +#[test] +fn native_restake_and_unstake_flow() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let amount = 100; + let unstake_amount = 40; + + // Setup + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![operator.clone()])); + + // Restake + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + amount, + Default::default(), + )); + + // Schedule unstake + assert_ok!(MultiAssetDelegation::schedule_nomination_unstake( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + unstake_amount, + Default::default(), + )); + + // Verify unstake request + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegator_unstake_requests.len(), 1); + let request = &metadata.delegator_unstake_requests[0]; + assert_eq!(request.operator, operator.clone()); + assert_eq!(request.amount, unstake_amount); + + // Move to next round + CurrentRound::::put(10); + + // Execute unstake + assert_ok!(MultiAssetDelegation::execute_nomination_unstake( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + )); + + // Verify final state + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 1); + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.amount, amount - unstake_amount); + }); +} + +#[test] +fn native_restake_zero_amount() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let amount = 100; + + // Setup + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![operator.clone()])); + + // Try to restake zero amount + assert_noop!( + MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + 0, + Default::default(), + ), + Error::::InvalidAmount + ); + }); +} + +#[test] +fn native_restake_concurrent_operations() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let amount = 100; + + // Setup + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![operator.clone()])); + + // Perform multiple operations in same block + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + 50, + Default::default(), + )); + assert_ok!(MultiAssetDelegation::schedule_nomination_unstake( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + 20, + Default::default(), + )); + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + 30, + Default::default(), + )); + + // Verify final state + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.amount, 80); // 50 + 30 + assert_eq!(metadata.delegator_unstake_requests.len(), 1); + }); +} + +#[test] +fn native_restake_early_unstake_execution_fails() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let amount = 100; + let unstake_amount = 40; + + // Setup + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![operator.clone()])); + + // Restake + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + amount, + Default::default(), + )); + + // Verify delegation state after restaking + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 1); + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.operator, operator); + assert_eq!(delegation.amount, amount); + assert!(delegation.is_nomination); + assert_eq!(metadata.delegator_unstake_requests.len(), 0); + + // Schedule unstake + assert_ok!(MultiAssetDelegation::schedule_nomination_unstake( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + unstake_amount, + Default::default(), + )); + + // Verify unstake request state + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegator_unstake_requests.len(), 1); + let request = &metadata.delegator_unstake_requests[0]; + assert_eq!(request.operator, operator); + assert_eq!(request.amount, unstake_amount); + assert!(request.is_nomination); + + // Try to execute unstake immediately - should fail + assert_noop!( + MultiAssetDelegation::execute_nomination_unstake( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + ), + Error::::BondLessNotReady + ); + + // Verify state remains unchanged after failed execution + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 1); + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.operator, operator); + assert_eq!(delegation.amount, amount); + assert!(delegation.is_nomination); + assert_eq!(metadata.delegator_unstake_requests.len(), 1); + let request = &metadata.delegator_unstake_requests[0]; + assert_eq!(request.operator, operator); + assert_eq!(request.amount, unstake_amount); + assert!(request.is_nomination); + }); +} + +#[test] +fn native_restake_cancel_unstake() { + new_test_ext().execute_with(|| { + let who: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + let amount = 100; + let unstake_amount = 40; + + // Setup + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![operator.clone()])); + + // Restake + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + amount, + Default::default(), + )); + + // Schedule unstake + assert_ok!(MultiAssetDelegation::schedule_nomination_unstake( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + unstake_amount, + Default::default(), + )); + + // Verify unstake request exists + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegator_unstake_requests.len(), 1); + let request = &metadata.delegator_unstake_requests[0]; + assert_eq!(request.operator, operator.clone()); + assert_eq!(request.amount, unstake_amount); + + // Cancel unstake request + assert_ok!(MultiAssetDelegation::cancel_nomination_unstake( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + )); + + // Verify unstake request is removed + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegator_unstake_requests.len(), 0); + + // Verify delegation amount remains unchanged + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.amount, amount); + + // Try to execute cancelled unstake - should fail + assert_noop!( + MultiAssetDelegation::execute_nomination_unstake( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + ), + Error::::NoBondLessRequest + ); + }); +} + +#[test] +fn proxy_unbond_should_fail_if_delegated_nomination() { + new_test_ext().execute_with(|| { + // Arrange + let who: AccountId = Dave.into(); + let proxy: AccountId = Charlie.into(); + let validator = Staking::invulnerables()[0].clone(); + let operator: AccountId = Alice.into(); + let amount = 100_000; + let delegate_amount = amount / 2; + + // Setup proxy with Staking type + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(who.clone()), + proxy.clone(), + ProxyType::Staking, + 0 + )); + + // Bond Some TNT + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + // Nominate the validator + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![validator.clone()])); + + System::set_block_number(2); + >::on_initialize(2); + >::on_initialize(2); + >::on_finalize(2); + >::on_finalize(2); + + // Set up operator and delegation + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Restake through direct call + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + delegate_amount, + Default::default(), + )); + + // Try to unbond through proxy - should fail + let call = RuntimeCall::Staking(pallet_staking::Call::unbond { value: amount }); + let proxy_call = RuntimeCall::Proxy(pallet_proxy::Call::proxy { + real: who.clone(), + force_proxy_type: None, + call: Box::new(call.clone()), + }); + + assert_err!( + CheckNominatedRestaked::::new().validate( + &proxy, + &proxy_call, + &DispatchInfo::default(), + 0 + ), + TransactionValidityError::Invalid(InvalidTransaction::Custom(1)) + ); + + // Verify state remains unchanged + let ledger = Staking::ledger(sp_staking::StakingAccount::Stash(who.clone())).unwrap(); + assert_eq!(ledger.active, amount); + assert_eq!(ledger.total, amount); + assert_eq!(ledger.unlocking.len(), 0); + }); +} + +#[test] +fn batch_unbond_should_fail_if_delegated_nomination() { + new_test_ext().execute_with(|| { + // Arrange + let who: AccountId = Dave.into(); + let validator = Staking::invulnerables()[0].clone(); + let operator: AccountId = Alice.into(); + let amount = 100_000; + let delegate_amount = amount / 2; + + // Bond Some TNT + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + // Nominate the validator + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![validator.clone()])); + + System::set_block_number(2); + >::on_initialize(2); + >::on_initialize(2); + >::on_finalize(2); + >::on_finalize(2); + + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Restake + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + delegate_amount, + Default::default(), + )); + + // Try to unbond through batch call - should fail + let unbond_call = RuntimeCall::Staking(pallet_staking::Call::unbond { value: amount }); + let batch_call = + RuntimeCall::Utility(pallet_utility::Call::batch { calls: vec![unbond_call] }); + + assert_err!( + CheckNominatedRestaked::::new().validate( + &who, + &batch_call, + &DispatchInfo::default(), + 0 + ), + TransactionValidityError::Invalid(InvalidTransaction::Custom(1)) + ); + + // Verify state remains unchanged + let ledger = Staking::ledger(sp_staking::StakingAccount::Stash(who.clone())).unwrap(); + assert_eq!(ledger.active, amount); + assert_eq!(ledger.total, amount); + assert_eq!(ledger.unlocking.len(), 0); + }); +} + +#[test] +fn proxy_batch_unbond_should_fail_if_delegated_nomination() { + new_test_ext().execute_with(|| { + // Arrange + let who: AccountId = Dave.into(); + let proxy: AccountId = Charlie.into(); + let validator = Staking::invulnerables()[0].clone(); + let operator: AccountId = Alice.into(); + let amount = 100_000; + let delegate_amount = amount / 2; + + // Setup proxy with Staking type + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(who.clone()), + proxy.clone(), + ProxyType::Staking, + 0 + )); + + // Bond Some TNT + assert_ok!(Staking::bond( + RuntimeOrigin::signed(who.clone()), + amount, + pallet_staking::RewardDestination::Staked + )); + // Nominate the validator + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who.clone()), vec![validator.clone()])); + + System::set_block_number(2); + >::on_initialize(2); + >::on_initialize(2); + >::on_finalize(2); + >::on_finalize(2); + + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Restake + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(who.clone()), + operator.clone(), + delegate_amount, + Default::default(), + )); + + // Try to unbond through proxy batch call - should fail + let unbond_call = RuntimeCall::Staking(pallet_staking::Call::unbond { value: amount }); + let batch_call = + RuntimeCall::Utility(pallet_utility::Call::batch { calls: vec![unbond_call] }); + let proxy_batch_call = RuntimeCall::Proxy(pallet_proxy::Call::proxy { + real: who.clone(), + force_proxy_type: None, + call: Box::new(batch_call.clone()), + }); + + assert_err!( + CheckNominatedRestaked::::new().validate( + &proxy, + &proxy_batch_call, + &DispatchInfo::default(), + 0 + ), + TransactionValidityError::Invalid(InvalidTransaction::Custom(1)) + ); + + // Verify state remains unchanged + let ledger = Staking::ledger(sp_staking::StakingAccount::Stash(who.clone())).unwrap(); + assert_eq!(ledger.active, amount); + assert_eq!(ledger.total, amount); + assert_eq!(ledger.unlocking.len(), 0); + + // Verify delegation state remains unchanged + let metadata = MultiAssetDelegation::delegators(who.clone()).unwrap(); + assert_eq!(metadata.delegations.len(), 1); + let delegation = &metadata.delegations[0]; + assert_eq!(delegation.operator, operator); + assert_eq!(delegation.amount, delegate_amount); + assert!(delegation.is_nomination); + }); +} diff --git a/pallets/multi-asset-delegation/src/tests/operator.rs b/pallets/multi-asset-delegation/src/tests/operator.rs index 08cba2757..3b8354e07 100644 --- a/pallets/multi-asset-delegation/src/tests/operator.rs +++ b/pallets/multi-asset-delegation/src/tests/operator.rs @@ -21,7 +21,10 @@ use crate::{ use frame_support::{assert_noop, assert_ok}; use sp_keyring::AccountKeyring::{Alice, Bob, Eve}; use sp_runtime::Percent; -use tangle_primitives::services::Asset; +use tangle_primitives::{ + services::{Asset, UnappliedSlash}, + traits::SlashManager, +}; #[test] fn join_operator_success() { @@ -625,19 +628,22 @@ fn slash_operator_success() { operator_stake )); - // Setup delegators - let delegator_stake = 5_000; - let asset_id = Asset::Custom(1); + // Setup delegators with different assets and blueprint selections + let delegator1_stake = 5_000; + let delegator2_stake = 3_000; + let asset1 = Asset::Custom(1); + let asset2 = Asset::Custom(2); let blueprint_id = 1; + let service_id = 42; - create_and_mint_tokens(1, Bob.to_account_id(), delegator_stake); - mint_tokens(Bob.to_account_id(), 1, Bob.to_account_id(), delegator_stake); + // Setup first delegator with asset1 and selected blueprint + create_and_mint_tokens(1, Bob.to_account_id(), delegator1_stake); + mint_tokens(Bob.to_account_id(), 1, Bob.to_account_id(), delegator1_stake); - // Setup delegator with fixed blueprint selection assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(Bob.to_account_id()), - asset_id, - delegator_stake, + asset1, + delegator1_stake, None, None )); @@ -650,36 +656,84 @@ fn slash_operator_success() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(Bob.to_account_id()), Alice.to_account_id(), - asset_id, - delegator_stake, + asset1, + delegator1_stake, Fixed(vec![blueprint_id].try_into().unwrap()), )); - // Slash 50% of stakes - let slash_percentage = Percent::from_percent(50); - assert_ok!(MultiAssetDelegation::slash_operator( - &Alice.to_account_id(), - blueprint_id, - slash_percentage + // Setup second delegator with asset2 but without selecting the blueprint + create_and_mint_tokens(2, Eve.to_account_id(), delegator2_stake); + mint_tokens(Eve.to_account_id(), 2, Eve.to_account_id(), delegator2_stake); + + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(Eve.to_account_id()), + asset2, + delegator2_stake, + None, + None + )); + + assert_ok!(MultiAssetDelegation::delegate( + RuntimeOrigin::signed(Eve.to_account_id()), + Alice.to_account_id(), + asset2, + delegator2_stake, + Fixed(vec![].try_into().unwrap()), )); + // Create UnappliedSlash with 50% slash for operator and first delegator only + let exposed_stake = operator_stake / 2; // 50% of operator stake + let exposed_delegation = delegator1_stake / 2; // 50% of delegator1 stake + + let unapplied_slash = UnappliedSlash { + era: 1, + blueprint_id, + service_id, + operator: Alice.to_account_id(), + slash_percent: Percent::from_percent(50), + }; + + // Apply the slash + assert_ok!(MultiAssetDelegation::slash_operator(&unapplied_slash)); + // Verify operator stake was slashed let operator_info = MultiAssetDelegation::operator_info(Alice.to_account_id()).unwrap(); - assert_eq!(operator_info.stake, operator_stake / 2); + assert_eq!(operator_info.stake, operator_stake - exposed_stake); - // Verify delegator stake was slashed - let delegator = MultiAssetDelegation::delegators(Bob.to_account_id()).unwrap(); - let delegation = delegator + // Verify first delegator (Bob) was slashed + let delegator1 = MultiAssetDelegation::delegators(Bob.to_account_id()).unwrap(); + let delegation1 = delegator1 .delegations .iter() - .find(|d| d.operator == Alice.to_account_id()) + .find(|d| d.operator == Alice.to_account_id() && d.asset == asset1) .unwrap(); - assert_eq!(delegation.amount, delegator_stake / 2); + assert_eq!(delegation1.amount, delegator1_stake - exposed_delegation); - // Verify event + // Verify second delegator (Eve) was NOT slashed since they didn't select the blueprint + let delegator2 = MultiAssetDelegation::delegators(Eve.to_account_id()).unwrap(); + let delegation2 = delegator2 + .delegations + .iter() + .find(|d| d.operator == Alice.to_account_id() && d.asset == asset2) + .unwrap(); + assert_eq!(delegation2.amount, delegator2_stake); // Amount unchanged + + // Verify events System::assert_has_event(RuntimeEvent::MultiAssetDelegation(Event::OperatorSlashed { - who: Alice.to_account_id(), - amount: operator_stake / 2, + operator: Alice.to_account_id(), + service_id, + blueprint_id, + era: 1, + amount: exposed_stake, + })); + + System::assert_has_event(RuntimeEvent::MultiAssetDelegation(Event::DelegatorSlashed { + delegator: Bob.to_account_id(), + service_id, + blueprint_id, + era: 1, + asset: asset1, + amount: exposed_delegation, })); }); } @@ -687,12 +741,16 @@ fn slash_operator_success() { #[test] fn slash_operator_not_an_operator() { new_test_ext().execute_with(|| { + let unapplied_slash = UnappliedSlash { + era: 1, + blueprint_id: 1, + service_id: 42, + operator: Alice.to_account_id(), + slash_percent: Percent::from_percent(50), + }; + assert_noop!( - MultiAssetDelegation::slash_operator( - &Alice.to_account_id(), - 1, - Percent::from_percent(50) - ), + MultiAssetDelegation::slash_operator(&unapplied_slash), Error::::NotAnOperator ); }); @@ -708,12 +766,16 @@ fn slash_operator_not_active() { )); assert_ok!(MultiAssetDelegation::go_offline(RuntimeOrigin::signed(Alice.to_account_id()))); + let unapplied_slash = UnappliedSlash { + era: 1, + blueprint_id: 1, + service_id: 42, + operator: Alice.to_account_id(), + slash_percent: Percent::from_percent(50), + }; + assert_noop!( - MultiAssetDelegation::slash_operator( - &Alice.to_account_id(), - 1, - Percent::from_percent(50) - ), + MultiAssetDelegation::slash_operator(&unapplied_slash), Error::::NotActiveOperator ); }); @@ -728,13 +790,15 @@ fn slash_delegator_fixed_blueprint_not_selected() { 10_000 )); - create_and_mint_tokens(1, Bob.to_account_id(), 10_000); - // Setup delegator with fixed blueprint selection + let delegator_stake = 5_000; + let asset = Asset::Custom(1); + create_and_mint_tokens(1, Bob.to_account_id(), delegator_stake); + assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(Bob.to_account_id()), - Asset::Custom(1), - 5_000, + asset, + delegator_stake, None, None )); @@ -747,20 +811,28 @@ fn slash_delegator_fixed_blueprint_not_selected() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(Bob.to_account_id()), Alice.to_account_id(), - Asset::Custom(1), - 5_000, - Fixed(vec![2].try_into().unwrap()), + asset, + delegator_stake, + Fixed(vec![2].try_into().unwrap()), // Selected blueprint 2, not 1 )); - // Try to slash with unselected blueprint - assert_noop!( - MultiAssetDelegation::slash_delegator( - &Bob.to_account_id(), - &Alice.to_account_id(), - 5, - Percent::from_percent(50) - ), - Error::::BlueprintNotSelected - ); + // Create UnappliedSlash for blueprint 1 + let unapplied_slash = UnappliedSlash { + era: 1, + blueprint_id: 1, + service_id: 42, + operator: Alice.to_account_id(), + slash_percent: Percent::from_percent(50), + }; + + // Verify delegator is not slashed since they didn't select blueprint 1 + assert_ok!(MultiAssetDelegation::slash_operator(&unapplied_slash)); + let delegator = MultiAssetDelegation::delegators(Bob.to_account_id()).unwrap(); + let delegation = delegator + .delegations + .iter() + .find(|d| d.operator == Alice.to_account_id()) + .unwrap(); + assert_eq!(delegation.amount, delegator_stake); // Amount unchanged }); } diff --git a/pallets/multi-asset-delegation/src/tests/session_manager.rs b/pallets/multi-asset-delegation/src/tests/session_manager.rs index 970375cbd..357c3e38b 100644 --- a/pallets/multi-asset-delegation/src/tests/session_manager.rs +++ b/pallets/multi-asset-delegation/src/tests/session_manager.rs @@ -65,7 +65,7 @@ fn handle_round_change_should_work() { assert_eq!(snapshot1.stake, 10_000); assert_eq!(snapshot1.delegations.len(), 1); assert_eq!(snapshot1.delegations[0].amount, amount); - assert_eq!(snapshot1.delegations[0].asset_id, asset_id); + assert_eq!(snapshot1.delegations[0].asset, asset_id); }); } @@ -77,7 +77,7 @@ fn handle_round_change_with_unstake_should_work() { let delegator2 = Bob.to_account_id(); let operator1 = Charlie.to_account_id(); let operator2 = Dave.to_account_id(); - let asset_id = Asset::Custom(VDOT); + let asset = Asset::Custom(VDOT); let amount1 = 100_000; let amount2 = 100_000; let unstake_amount = 50; @@ -100,7 +100,7 @@ fn handle_round_change_with_unstake_should_work() { assert_noop!( MultiAssetDelegation::deposit( RuntimeOrigin::signed(delegator1.clone()), - asset_id, + asset, 100_000_000_u32.into(), None, None, @@ -111,7 +111,7 @@ fn handle_round_change_with_unstake_should_work() { // Deposit and delegate first assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(delegator1.clone()), - asset_id, + asset, amount1, None, None, @@ -120,14 +120,14 @@ fn handle_round_change_with_unstake_should_work() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(delegator1.clone()), operator1.clone(), - asset_id, + asset, amount1, Default::default(), )); assert_ok!(MultiAssetDelegation::deposit( RuntimeOrigin::signed(delegator2.clone()), - asset_id, + asset, amount2, None, None @@ -136,7 +136,7 @@ fn handle_round_change_with_unstake_should_work() { assert_ok!(MultiAssetDelegation::delegate( RuntimeOrigin::signed(delegator2.clone()), operator2.clone(), - asset_id, + asset, amount2, Default::default(), )); @@ -145,7 +145,7 @@ fn handle_round_change_with_unstake_should_work() { assert_ok!(MultiAssetDelegation::schedule_delegator_unstake( RuntimeOrigin::signed(delegator1.clone()), operator1.clone(), - asset_id, + asset, unstake_amount, )); @@ -160,8 +160,8 @@ fn handle_round_change_with_unstake_should_work() { assert_eq!(snapshot1.stake, 10_000); assert_eq!(snapshot1.delegations.len(), 1); assert_eq!(snapshot1.delegations[0].delegator, delegator1.clone()); - assert_eq!(snapshot1.delegations[0].amount, amount1 - unstake_amount); // Amount reduced by unstake_amount - assert_eq!(snapshot1.delegations[0].asset_id, asset_id); + assert_eq!(snapshot1.delegations[0].amount, amount1); // Amount should be the same + assert_eq!(snapshot1.delegations[0].asset, asset); // Check the snapshot for operator2 let snapshot2 = MultiAssetDelegation::at_stake(current_round, operator2.clone()).unwrap(); @@ -169,7 +169,7 @@ fn handle_round_change_with_unstake_should_work() { assert_eq!(snapshot2.delegations.len(), 1); assert_eq!(snapshot2.delegations[0].delegator, delegator2.clone()); assert_eq!(snapshot2.delegations[0].amount, amount2); - assert_eq!(snapshot2.delegations[0].asset_id, asset_id); + assert_eq!(snapshot2.delegations[0].asset, asset); }); } diff --git a/pallets/multi-asset-delegation/src/traits.rs b/pallets/multi-asset-delegation/src/traits.rs index f5576ef4f..59dc1badd 100644 --- a/pallets/multi-asset-delegation/src/traits.rs +++ b/pallets/multi-asset-delegation/src/traits.rs @@ -16,18 +16,15 @@ use super::*; use crate::types::{BalanceOf, OperatorStatus}; use frame_system::pallet_prelude::BlockNumberFor; -use sp_runtime::{traits::Zero, Percent}; +use sp_runtime::traits::Zero; use sp_std::prelude::*; use tangle_primitives::types::rewards::UserDepositWithLocks; -use tangle_primitives::{ - services::Asset, traits::MultiAssetDelegationInfo, BlueprintId, RoundIndex, -}; +use tangle_primitives::{services::Asset, traits::MultiAssetDelegationInfo, RoundIndex}; -impl MultiAssetDelegationInfo, BlockNumberFor> +impl + MultiAssetDelegationInfo, BlockNumberFor, T::AssetId> for crate::Pallet { - type AssetId = T::AssetId; - fn get_current_round() -> RoundIndex { Self::current_round() } @@ -45,41 +42,37 @@ impl MultiAssetDelegationInfo, Bloc Operators::::get(operator).map_or(Zero::zero(), |metadata| metadata.stake) } - fn get_total_delegation_by_asset_id( + fn get_total_delegation_by_asset( operator: &T::AccountId, - asset_id: &Asset, + asset: &Asset, ) -> BalanceOf { Operators::::get(operator).map_or(Zero::zero(), |metadata| { metadata .delegations .iter() - .filter(|stake| &stake.asset_id == asset_id) + .filter(|stake| &stake.asset == asset) .fold(Zero::zero(), |acc, stake| acc + stake.amount) }) } fn get_delegators_for_operator( operator: &T::AccountId, - ) -> Vec<(T::AccountId, BalanceOf, Asset)> { + ) -> Vec<(T::AccountId, BalanceOf, Asset)> { Operators::::get(operator).map_or(Vec::new(), |metadata| { metadata .delegations .iter() - .map(|stake| (stake.delegator.clone(), stake.amount, stake.asset_id)) + .map(|stake| (stake.delegator.clone(), stake.amount, stake.asset)) .collect() }) } - fn slash_operator(operator: &T::AccountId, blueprint_id: BlueprintId, percentage: Percent) { - let _ = Pallet::::slash_operator(operator, blueprint_id, percentage); - } - fn get_user_deposit_with_locks( who: &T::AccountId, - asset_id: Asset, + asset: Asset, ) -> Option, BlockNumberFor>> { Delegators::::get(who).and_then(|metadata| { - metadata.deposits.get(&asset_id).map(|deposit| UserDepositWithLocks { + metadata.deposits.get(&asset).map(|deposit| UserDepositWithLocks { unlocked_amount: deposit.amount, amount_with_locks: deposit.locks.as_ref().map(|locks| locks.to_vec()), }) diff --git a/pallets/multi-asset-delegation/src/types/delegator.rs b/pallets/multi-asset-delegation/src/types/delegator.rs index 84aaaabd7..fb6bb4d8d 100644 --- a/pallets/multi-asset-delegation/src/types/delegator.rs +++ b/pallets/multi-asset-delegation/src/types/delegator.rs @@ -16,7 +16,7 @@ use super::*; use frame_support::{ensure, pallet_prelude::Get, BoundedVec}; -use sp_runtime::traits::CheckedAdd; +use sp_runtime::traits::{CheckedAdd, Saturating}; use sp_std::{fmt::Debug, vec}; use tangle_primitives::{ services::Asset, @@ -39,6 +39,29 @@ impl> Default for DelegatorBlueprintSelection> DelegatorBlueprintSelection { + pub fn contains(&self, blueprint_id: &BlueprintId) -> bool { + match self { + DelegatorBlueprintSelection::Fixed(blueprints) => blueprints.contains(blueprint_id), + DelegatorBlueprintSelection::All => true, + } + } +} +impl> From> + for DelegatorBlueprintSelection +{ + fn from(blueprints: Vec) -> Self { + if blueprints.is_empty() { + DelegatorBlueprintSelection::All + } else { + match BoundedVec::try_from(blueprints) { + Ok(bounded_blueprints) => DelegatorBlueprintSelection::Fixed(bounded_blueprints), + Err(_) => DelegatorBlueprintSelection::All, + } + } + } +} + /// Represents the status of a delegator. #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, Default)] pub enum DelegatorStatus { @@ -53,8 +76,8 @@ pub enum DelegatorStatus { #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct WithdrawRequest { /// The ID of the asset to be withdrawd. - pub asset_id: Asset, - /// The amount of the asset to be withdrawd. + pub asset: Asset, + /// The amount of the asset to be withdrawn. pub amount: Balance, /// The round in which the withdraw was requested. pub requested_round: RoundIndex, @@ -66,13 +89,31 @@ pub struct BondLessRequest, + pub asset: Asset, /// The amount by which to reduce the stake. pub amount: Balance, /// The round in which the stake reduction was requested. pub requested_round: RoundIndex, /// The blueprint selection of the delegator. pub blueprint_selection: DelegatorBlueprintSelection, + /// Whether this unstake request is for a nomination delegation + pub is_nomination: bool, +} + +/// Represents a delegation bond from a delegator to an operator. +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, Eq, PartialEq)] +pub struct BondInfoDelegator> +{ + /// The operator being delegated to. + pub operator: AccountId, + /// The amount being delegated. + pub amount: Balance, + /// The asset being delegated. + pub asset: Asset, + /// The blueprint selection for this delegation. + pub blueprint_selection: DelegatorBlueprintSelection, + /// Whether this delegation is from nominated tokens + pub is_nomination: bool, } /// Stores the state of a delegator, including deposits, delegations, and requests. @@ -184,15 +225,14 @@ impl< } /// Calculates the total delegation amount for a specific asset. - pub fn calculate_delegation_by_asset(&self, asset_id: Asset) -> Balance - // Asset) -> Balance + pub fn calculate_delegation_by_asset(&self, asset: Asset) -> Balance where Balance: Default + core::ops::AddAssign + Clone + CheckedAdd, AssetId: Eq + PartialEq, { let mut total = Balance::default(); for stake in &self.delegations { - if stake.asset_id == asset_id { + if stake.asset == asset { total = total.checked_add(&stake.amount).unwrap_or(total); } } @@ -209,6 +249,57 @@ impl< { self.delegations.iter().filter(|&stake| stake.operator == operator).collect() } + + /// Calculate total delegation amount + pub fn total_delegation_amount(&self) -> Balance + where + Balance: Default + core::ops::AddAssign + Clone + Saturating, + AssetId: Eq + PartialEq, + { + self.delegations.iter().fold(Balance::default(), |acc, delegation| { + acc.saturating_add(delegation.amount.clone()) + }) + } + + /// Calculate total non-nomination delegations + pub fn total_non_nomination_delegations(&self) -> Balance + where + Balance: Default + core::ops::AddAssign + Clone + Saturating, + AssetId: Eq + PartialEq, + { + self.delegations + .iter() + .filter(|d| !d.is_nomination) + .fold(Balance::default(), |acc, delegation| { + acc.saturating_add(delegation.amount.clone()) + }) + } + + /// Calculate total nomination delegations + pub fn total_nomination_delegations(&self) -> Balance + where + Balance: Default + core::ops::AddAssign + Clone + Saturating, + AssetId: Eq + PartialEq, + { + self.delegations + .iter() + .filter(|d| d.is_nomination) + .fold(Balance::default(), |acc, delegation| { + acc.saturating_add(delegation.amount.clone()) + }) + } + + /// Find nomination delegation by operator + pub fn get_nomination_delegation_by_operator( + &self, + operator: &AccountId, + ) -> Option<&BondInfoDelegator> + where + AccountId: PartialEq, + AssetId: Eq, + { + self.delegations.iter().find(|d| &d.operator == operator && d.is_nomination) + } } /// Represents a deposit of a specific asset. @@ -333,17 +424,3 @@ impl< Ok(()) } } - -/// Represents a stake between a delegator and an operator. -#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, Eq, PartialEq)] -pub struct BondInfoDelegator> -{ - /// The account ID of the operator. - pub operator: AccountId, - /// The amount bonded. - pub amount: Balance, - /// The ID of the bonded asset. - pub asset_id: Asset, - /// The blueprint selection mode for this delegator. - pub blueprint_selection: DelegatorBlueprintSelection, -} diff --git a/pallets/multi-asset-delegation/src/types/operator.rs b/pallets/multi-asset-delegation/src/types/operator.rs index 5c1977a62..b292a0000 100644 --- a/pallets/multi-asset-delegation/src/types/operator.rs +++ b/pallets/multi-asset-delegation/src/types/operator.rs @@ -38,10 +38,10 @@ where Balance: Default + core::ops::AddAssign + Copy + CheckedAdd, { /// Calculates the total stake for a specific asset ID from all delegations. - pub fn get_stake_by_asset_id(&self, asset_id: Asset) -> Balance { + pub fn get_stake_by_asset_id(&self, asset: Asset) -> Balance { let mut total_stake = Balance::default(); for stake in &self.delegations { - if stake.asset_id == asset_id { + if stake.asset == asset { total_stake = total_stake.checked_add(&stake.amount).unwrap_or(total_stake); } } @@ -53,7 +53,7 @@ where let mut stake_by_asset: BTreeMap, Balance> = BTreeMap::new(); for stake in &self.delegations { - let entry = stake_by_asset.entry(stake.asset_id).or_default(); + let entry = stake_by_asset.entry(stake.asset).or_default(); *entry = entry.checked_add(&stake.amount).unwrap_or(*entry); } @@ -136,5 +136,5 @@ pub struct DelegatorBond { /// The amount bonded. pub amount: Balance, /// The ID of the bonded asset. - pub asset_id: Asset, + pub asset: Asset, } diff --git a/pallets/rewards/src/functions/mod.rs b/pallets/rewards/src/functions/mod.rs index c7d9fa947..3e2447f16 100644 --- a/pallets/rewards/src/functions/mod.rs +++ b/pallets/rewards/src/functions/mod.rs @@ -23,6 +23,7 @@ use sp_std::vec::Vec; use tangle_primitives::services::Asset; pub mod rewards; +pub mod services; impl Pallet { pub fn remove_asset_from_vault( diff --git a/pallets/rewards/src/functions/rewards.rs b/pallets/rewards/src/functions/rewards.rs index 331927c57..07af1e2dd 100644 --- a/pallets/rewards/src/functions/rewards.rs +++ b/pallets/rewards/src/functions/rewards.rs @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . + use crate::{ ApyBlocks, AssetLookupRewardVaults, BalanceOf, Config, DecayRate, DecayStartPeriod, Error, Event, Pallet, RewardConfigForAssetVault, RewardConfigStorage, RewardVaultsPotAccount, diff --git a/pallets/rewards/src/functions/services.rs b/pallets/rewards/src/functions/services.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/pallets/rewards/src/functions/services.rs @@ -0,0 +1 @@ + diff --git a/pallets/rewards/src/lib.rs b/pallets/rewards/src/lib.rs index ebfa53789..a7fd01812 100644 --- a/pallets/rewards/src/lib.rs +++ b/pallets/rewards/src/lib.rs @@ -124,7 +124,7 @@ pub mod pallet { Self::AccountId, BalanceOf, BlockNumberFor, - AssetId = Self::AssetId, + Self::AssetId, >; /// The origin that can manage reward assets @@ -243,11 +243,7 @@ pub mod pallet { /// Event emitted when a blueprint is whitelisted for rewards BlueprintWhitelisted { blueprint_id: BlueprintId }, /// Asset has been updated to reward vault - AssetUpdatedInVault { - vault_id: T::VaultId, - asset_id: Asset, - action: AssetAction, - }, + AssetUpdatedInVault { vault_id: T::VaultId, asset: Asset, action: AssetAction }, /// Vault reward config updated VaultRewardConfigUpdated { vault_id: T::VaultId, @@ -410,7 +406,7 @@ pub mod pallet { /// /// * `origin` - Origin of the call /// * `vault_id` - ID of the vault - /// * `asset_id` - ID of the asset + /// * `asset` - ID of the asset /// * `action` - Action to perform (Add/Remove) /// /// # Errors @@ -422,17 +418,17 @@ pub mod pallet { pub fn manage_asset_reward_vault( origin: OriginFor, vault_id: T::VaultId, - asset_id: Asset, + asset: Asset, action: AssetAction, ) -> DispatchResult { let _who = T::ForceOrigin::ensure_origin(origin)?; match action { - AssetAction::Add => Self::add_asset_to_vault(&vault_id, &asset_id)?, - AssetAction::Remove => Self::remove_asset_from_vault(&vault_id, &asset_id)?, + AssetAction::Add => Self::add_asset_to_vault(&vault_id, &asset)?, + AssetAction::Remove => Self::remove_asset_from_vault(&vault_id, &asset)?, } - Self::deposit_event(Event::AssetUpdatedInVault { vault_id, asset_id, action }); + Self::deposit_event(Event::AssetUpdatedInVault { vault_id, asset, action }); Ok(()) } diff --git a/pallets/rewards/src/mock.rs b/pallets/rewards/src/mock.rs index f86df3119..80c63daa3 100644 --- a/pallets/rewards/src/mock.rs +++ b/pallets/rewards/src/mock.rs @@ -269,11 +269,9 @@ pub struct MockDelegationData { } pub struct MockDelegationManager; -impl tangle_primitives::traits::MultiAssetDelegationInfo +impl tangle_primitives::traits::MultiAssetDelegationInfo for MockDelegationManager { - type AssetId = AssetId; - fn get_current_round() -> tangle_primitives::types::RoundIndex { Default::default() } @@ -298,32 +296,22 @@ impl tangle_primitives::traits::MultiAssetDelegationInfo, - ) -> Balance { + fn get_total_delegation_by_asset(_operator: &AccountId, _asset_id: &Asset) -> Balance { Default::default() } fn get_delegators_for_operator( _operator: &AccountId, - ) -> Vec<(AccountId, Balance, Asset)> { + ) -> Vec<(AccountId, Balance, Asset)> { Default::default() } - fn slash_operator( - _operator: &AccountId, - _blueprint_id: tangle_primitives::BlueprintId, - _perbillage: sp_runtime::Percent, - ) { - } - fn get_user_deposit_with_locks( who: &AccountId, - asset_id: Asset, + asset: Asset, ) -> Option> { MOCK_DELEGATION_INFO.with(|delegation_info| { - delegation_info.borrow().deposits.get(&(who.clone(), asset_id)).cloned() + delegation_info.borrow().deposits.get(&(who.clone(), asset)).cloned() }) } } @@ -334,15 +322,19 @@ parameter_types! { pub const MinOperatorBondAmount: u64 = 10_000; pub const BondDuration: u32 = 10; pub PID: PalletId = PalletId(*b"PotStake"); - pub SlashedAmountRecipient : AccountId = AccountKeyring::Alice.into(); + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxDelegatorBlueprints : u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxOperatorBlueprints : u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxWithdrawRequests: u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxUnstakeRequests: u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxDelegations: u32 = 50; } diff --git a/pallets/services/Cargo.toml b/pallets/services/Cargo.toml index 3d5140594..c56cda456 100644 --- a/pallets/services/Cargo.toml +++ b/pallets/services/Cargo.toml @@ -65,11 +65,16 @@ pallet-evm-precompile-modexp = { workspace = true } pallet-evm-precompile-sha3fips = { workspace = true } pallet-evm-precompile-simple = { workspace = true } +pallet-evm-precompile-balances-erc20 = { workspace = true } +pallet-evm-precompileset-assets-erc20 = { workspace = true } + precompile-utils = { workspace = true } pallet-session = { workspace = true } pallet-staking = { workspace = true } +pallet-multi-asset-delegation = { workspace = true } sp-staking = { workspace = true } +sp-weights = { workspace = true } frame-election-provider-support = { workspace = true } [features] @@ -86,6 +91,7 @@ std = [ "sp-std/std", "sp-io/std", "sp-staking/std", + "sp-weights/std", "tangle-primitives/std", "pallet-balances/std", "pallet-timestamp/std", diff --git a/pallets/services/prompt.md b/pallets/services/prompt.md new file mode 100644 index 000000000..8993337e5 --- /dev/null +++ b/pallets/services/prompt.md @@ -0,0 +1,56 @@ +# Service Marketplace System Design Prompt + +## Core Components + +1. **Service Request Types** + +- Direct requests to specific operators +- Open market with dynamic participation +- Time-bounded auctions +- Standing orderbook mechanics + +2. **Dynamic Security Model** + +- Security pools for flexible collateral +- Dynamic operator participation +- Asset-specific security requirements +- Join/leave mechanics for operators + +3. **Market Mechanisms** + +- Continuous orderbook for standard services +- Auctions for specialized requirements +- Price discovery through market forces +- Automated matching and service creation + +## Key Abstractions + +```rust +// Market mechanisms for service creation +enum MarketMechanism { + Direct { ... } // Direct operator selection + OrderBook { ... } // Standing orders with price matching + TimedAuction { ... } // Time-bounded price discovery +} +// Dynamic security management +struct SecurityPool { + asset: Asset, + participants: Map, + requirements: SecurityRequirements +} +// Market order representation +struct MarketOrder { + operator: AccountId, + price: Balance, + security_commitment: SecurityCommitment, + expiry: BlockNumber +} +``` + +## Design Principles + +1. Support multiple service creation patterns +2. Enable market-driven pricing +3. Maintain security and reliability +4. Allow dynamic participation +5. Automate matching where possible diff --git a/pallets/services/rpc/runtime-api/src/lib.rs b/pallets/services/rpc/runtime-api/src/lib.rs index 606d0b93a..63e221283 100644 --- a/pallets/services/rpc/runtime-api/src/lib.rs +++ b/pallets/services/rpc/runtime-api/src/lib.rs @@ -21,7 +21,7 @@ use parity_scale_codec::Codec; use sp_runtime::{traits::MaybeDisplay, Serialize}; use sp_std::vec::Vec; -use tangle_primitives::services::{Constraints, RpcServicesWithBlueprint}; +use tangle_primitives::services::{AssetIdT, Constraints, RpcServicesWithBlueprint}; pub type BlockNumberOf = <::HeaderT as sp_runtime::traits::Header>::Number; @@ -31,7 +31,7 @@ sp_api::decl_runtime_apis! { where C: Constraints, AccountId: Codec + MaybeDisplay + Serialize, - AssetId: Codec + MaybeDisplay + Serialize, + AssetId: AssetIdT, { /// Query all the services that this operator is providing along with their blueprints. /// diff --git a/pallets/services/rpc/src/lib.rs b/pallets/services/rpc/src/lib.rs index ce67fb1b7..f5b749fb7 100644 --- a/pallets/services/rpc/src/lib.rs +++ b/pallets/services/rpc/src/lib.rs @@ -29,7 +29,7 @@ use sp_runtime::{ DispatchError, Serialize, }; use std::sync::Arc; -use tangle_primitives::services::{Constraints, RpcServicesWithBlueprint}; +use tangle_primitives::services::{AssetIdT, Constraints, RpcServicesWithBlueprint}; type BlockNumberOf = <::HeaderT as sp_runtime::traits::Header>::Number; @@ -41,7 +41,7 @@ where X: Constraints, AccountId: Codec + MaybeDisplay + core::fmt::Debug + Send + Sync + 'static + Serialize, BlockNumber: Codec + MaybeDisplay + core::fmt::Debug + Send + Sync + 'static + Serialize, - AssetId: Codec + MaybeDisplay + core::fmt::Debug + Send + Sync + 'static + Serialize, + AssetId: AssetIdT, { #[method(name = "services_queryServicesWithBlueprintsByOperator")] fn query_services_with_blueprints_by_operator( @@ -70,7 +70,7 @@ impl where Block: BlockT, AccountId: Codec + MaybeDisplay + core::fmt::Debug + Send + Sync + 'static + Serialize, - AssetId: Codec + MaybeDisplay + core::fmt::Debug + Send + Sync + 'static + Serialize, + AssetId: AssetIdT, X: Constraints, C: HeaderBackend + ProvideRuntimeApi + Send + Sync + 'static, C::Api: ServicesRuntimeApi, diff --git a/pallets/services/src/functions/approve.rs b/pallets/services/src/functions/approve.rs new file mode 100644 index 000000000..d59dd4e55 --- /dev/null +++ b/pallets/services/src/functions/approve.rs @@ -0,0 +1,292 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use crate::{ + types::*, Config, Error, Event, Instances, NextInstanceId, OperatorsProfile, Pallet, + ServiceRequests, ServiceStatus, StagingServicePayments, UserServices, +}; +use frame_support::{ + pallet_prelude::*, + traits::{fungibles::Mutate, tokens::Preservation, Currency, ExistenceRequirement}, +}; +use frame_system::pallet_prelude::*; +use sp_runtime::traits::Zero; +use sp_std::vec::Vec; +use tangle_primitives::{ + services::{ + ApprovalState, Asset, AssetSecurityCommitment, EvmAddressMapping, Service, ServiceRequest, + StagingServicePayment, + }, + BlueprintId, +}; + +impl Pallet { + /// Process an operator's approval for a service request. + /// + /// This function handles the approval workflow for a service request, including: + /// 1. Validating the operator's eligibility to approve + /// 2. Updating the approval state with security commitments + /// 3. Checking if all operators have approved + /// 4. Initializing the service if fully approved + /// 5. Processing payments to the MBSM + /// + /// # Arguments + /// + /// * `operator` - The account ID of the approving operator + /// * `request_id` - The ID of the service request being approved + /// * `native_exposure_percent` - Percentage of native token stake to expose + /// * `asset_exposure` - Vector of asset-specific exposure commitments + /// + /// # Returns + /// + /// Returns a DispatchResult indicating success or the specific error that occurred + pub fn do_approve( + operator: T::AccountId, + request_id: u64, + security_commitments: &[AssetSecurityCommitment], + ) -> DispatchResult { + // Retrieve and validate the service request + let mut request = Self::service_requests(request_id)?; + + // Validate operator commitments against service requirements + ensure!( + request.validate_security_commitments(security_commitments), + Error::::InvalidSecurityCommitments + ); + + // Find and update operator's approval state + let updated = request + .operators_with_approval_state + .iter_mut() + .find(|(op, _)| op == &operator) + .map(|(_, state)| { + *state = + ApprovalState::Approved { security_commitments: security_commitments.to_vec() } + }); + ensure!(updated.is_some(), Error::::ApprovalNotRequested); + + let blueprint_id = request.blueprint; + let (_, blueprint) = Self::blueprints(blueprint_id)?; + let preferences = Self::operators(blueprint_id, operator.clone())?; + + // Call approval hook + // TODO: Update the approval hook CC @shekohex @1xstj + let (allowed, _weight) = + Self::on_approve_hook(&blueprint, blueprint_id, &preferences, request_id, 0u8) + .map_err(|_| Error::::OnApproveFailure)?; + ensure!(allowed, Error::::ApprovalInterrupted); + + // Get lists of approved and pending operators + let approved = request + .operators_with_approval_state + .iter() + .filter_map(|(op, state)| { + if matches!(state, ApprovalState::Approved { .. }) { + Some(op.clone()) + } else { + None + } + }) + .collect::>(); + + let pending_approvals = request + .operators_with_approval_state + .iter() + .filter_map(|(op, state)| { + if matches!(state, ApprovalState::Pending) { + Some(op.clone()) + } else { + None + } + }) + .collect::>(); + + // Emit approval event + Self::deposit_event(Event::ServiceRequestApproved { + operator: operator.clone(), + request_id, + blueprint_id, + pending_approvals, + approved: approved.clone(), + }); + + // If all operators have approved, initialize the service + if request.is_approved() { + Self::initialize_approved_service(request_id, request)?; + } else { + // Update the service request if still pending approvals + ServiceRequests::::insert(request_id, request); + } + + Ok(()) + } + + /// Initialize a service after all operators have approved. + /// + /// This is a helper function that handles the service initialization process including: + /// - Creating the service instance + /// - Processing payments + /// - Updating operator profiles + /// - Emitting events + /// + /// # Arguments + /// + /// * `request` - The approved service request to initialize + fn initialize_approved_service( + request_id: u64, + request: ServiceRequest, T::AssetId>, + ) -> DispatchResult { + // Remove the service request since it's now approved + ServiceRequests::::remove(request_id); + + let service_id = Self::next_instance_id(); + + // Collect operator commitments + let operator_security_commitments = request + .operators_with_approval_state + .into_iter() + .filter_map(|(op, state)| match state { + ApprovalState::Approved { security_commitments } => { + // This is okay because we assert that each operators approval state contains + // a bounded list of asset exposures in the initial `do_approve` call. + let security_commitments = BoundedVec::try_from(security_commitments).unwrap(); + Some((op, security_commitments)) + }, + _ => None, + }) + .collect::>(); + + // Update operator profiles + for (operator, _) in &operator_security_commitments { + OperatorsProfile::::try_mutate_exists(operator, |profile| { + profile + .as_mut() + .and_then(|p| p.services.try_insert(service_id).ok()) + .ok_or(Error::::NotRegistered) + })?; + } + + // Create bounded vectors for service instance + let operator_security_commitments = BoundedVec::try_from(operator_security_commitments) + .map_err(|_| Error::::MaxServiceProvidersExceeded)?; + + // Create the service instance + let service = Service { + id: service_id, + blueprint: request.blueprint, + owner: request.owner.clone(), + operator_security_commitments: operator_security_commitments.clone(), + security_requirements: request.security_requirements, + permitted_callers: request.permitted_callers.clone(), + ttl: request.ttl, + membership_model: request.membership_model, + }; + + UserServices::::try_mutate(&request.owner, |service_ids| { + Instances::::insert(service_id, service.clone()); + ServiceStatus::::insert(request.blueprint, service_id, ()); + NextInstanceId::::set(service_id.saturating_add(1)); + service_ids + .try_insert(service_id) + .map_err(|_| Error::::MaxServicesPerUserExceeded) + })?; + + // Process payment if it exists + if let Some(payment) = Self::service_payment(request_id) { + Self::process_service_payment(request.blueprint, &payment)?; + StagingServicePayments::::remove(request_id); + } + + // Call service initialization hook + let (_, blueprint) = Self::blueprints(request.blueprint)?; + let (allowed, _weight) = Self::on_service_init_hook( + &blueprint, + request.blueprint, + request_id, + service_id, + &request.owner, + &request.permitted_callers, + request.ttl, + ) + .map_err(|_| Error::::OnServiceInitHook)?; + ensure!(allowed, Error::::ServiceInitializationInterrupted); + + // Emit service initiated event + Self::deposit_event(Event::ServiceInitiated { + owner: request.owner, + request_id, + service_id, + blueprint_id: request.blueprint, + operator_security_commitments, + }); + + Ok(()) + } + + /// Process a service payment by transferring funds to the MBSM. + /// + /// This function handles transferring payment from the pallet account to the MBSM account + /// based on the payment asset type (native, custom, or ERC20). + /// + /// # Arguments + /// + /// * `payment` - The payment details including asset type and amount + /// + /// # Returns + /// + /// Returns a DispatchResult indicating success or the specific error that occurred + pub(crate) fn process_service_payment( + blueprint_id: BlueprintId, + payment: &StagingServicePayment>, + ) -> DispatchResult { + let (_, blueprint) = Self::blueprints(blueprint_id)?; + + // send payments to the MBSM + let mbsm_address = Self::mbsm_address_of(&blueprint)?; + let mbsm_account_id = T::EvmAddressMapping::into_account_id(mbsm_address); + match payment.asset.clone() { + Asset::Custom(asset_id) if asset_id == Zero::zero() => { + T::Currency::transfer( + &Self::pallet_account(), + &mbsm_account_id, + payment.amount, + ExistenceRequirement::AllowDeath, + )?; + }, + Asset::Custom(asset_id) => { + T::Fungibles::transfer( + asset_id, + &Self::pallet_account(), + &mbsm_account_id, + payment.amount, + Preservation::Expendable, + )?; + }, + Asset::Erc20(token) => { + let (success, _weight) = Self::erc20_transfer( + token, + Self::pallet_evm_account(), + mbsm_address, + payment.amount, + ) + .map_err(|_| Error::::ERC20TransferFailed)?; + ensure!(success, Error::::ERC20TransferFailed); + }, + } + + Ok(()) + } +} diff --git a/pallets/services/src/functions.rs b/pallets/services/src/functions/evm_hooks.rs similarity index 68% rename from pallets/services/src/functions.rs rename to pallets/services/src/functions/evm_hooks.rs index da12fcc85..189c22545 100644 --- a/pallets/services/src/functions.rs +++ b/pallets/services/src/functions/evm_hooks.rs @@ -1,40 +1,38 @@ -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, string::String, vec, vec::Vec}; - -#[cfg(feature = "std")] -use std::{boxed::Box, string::String, vec::Vec}; - +use crate::types::BalanceOf; +use crate::{Config, Error, Event, MasterBlueprintServiceManagerRevisions, Pallet, Pays, Weight}; use ethabi::{Function, StateMutability, Token}; use frame_support::dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo}; -use sp_core::{H160, U256}; +use frame_system::pallet_prelude::BlockNumberFor; +use parity_scale_codec::Encode; +use sp_core::{Get, H160, U256}; use sp_runtime::traits::{UniqueSaturatedInto, Zero}; +use sp_std::{boxed::Box, vec, vec::Vec}; use tangle_primitives::services::{ Asset, BlueprintServiceManager, EvmAddressMapping, EvmGasWeightMapping, EvmRunner, Field, MasterBlueprintServiceManagerRevision, OperatorPreferences, Service, ServiceBlueprint, }; -use super::*; -use crate::types::BalanceOf; +#[cfg(not(feature = "std"))] +use alloc::string::String; +#[cfg(feature = "std")] +use std::string::String; #[allow(clippy::too_many_arguments)] impl Pallet { - /// Returns the account id of the pallet. + /// Returns the account ID of the pallet. + pub fn pallet_account() -> T::AccountId { + T::EvmAddressMapping::into_account_id(T::PalletEvmAccount::get()) + } + + /// Returns the EVM account id of the pallet. /// /// This function retrieves the account id associated with the pallet by converting /// the pallet evm address to an account id. /// /// # Returns /// * `T::AccountId` - The account id of the pallet. - pub fn account_id() -> T::AccountId { - T::EvmAddressMapping::into_account_id(Self::address()) - } - - /// Returns the EVM address of the pallet. - /// - /// # Returns - /// * `H160` - The address of the pallet. - pub fn address() -> H160 { - T::PalletEVMAddress::get() + pub fn pallet_evm_account() -> H160 { + T::PalletEvmAccount::get() } /// Get the address of the master blueprint service manager at a given revision. @@ -138,7 +136,7 @@ impl Pallet { let data = f.encode_input(args).map_err(|_| Error::::EVMAbiEncode)?; let gas_limit = 500_000; let value = U256::zero(); - let info = Self::evm_call(Self::address(), bsm, value, data, gas_limit)?; + let info = Self::evm_call(Self::pallet_evm_account(), bsm, value, data, gas_limit)?; let weight = Self::weight_from_call_info(&info); log::debug!( target: "evm", @@ -200,7 +198,7 @@ impl Pallet { /// # Parameters /// * `blueprint` - The service blueprint. /// * `blueprint_id` - The blueprint ID. - /// * `prefrences` - The operator preferences. + /// * `preferences` - The operator preferences. /// * `registration_args` - The registration arguments. /// * `value` - The value to be sent with the call. /// @@ -210,7 +208,7 @@ impl Pallet { pub fn on_register_hook( blueprint: &ServiceBlueprint, blueprint_id: u64, - prefrences: &OperatorPreferences, + preferences: &OperatorPreferences, registration_args: &[Field], value: BalanceOf, ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { @@ -238,7 +236,7 @@ impl Pallet { }, &[ Token::Uint(ethabi::Uint::from(blueprint_id)), - prefrences.to_ethabi(), + preferences.to_ethabi(), Token::Bytes(Field::encode_to_ethabi(registration_args)), ], value, @@ -253,7 +251,7 @@ impl Pallet { /// # Parameters /// * `blueprint` - The service blueprint. /// * `blueprint_id` - The blueprint ID. - /// * `prefrences` - The operator preferences. + /// * `preferences` - The operator preferences. /// /// # Returns /// * `Result<(bool, Weight), DispatchErrorWithPostInfo>` - A tuple containing a boolean @@ -261,7 +259,7 @@ impl Pallet { pub fn on_unregister_hook( blueprint: &ServiceBlueprint, blueprint_id: u64, - prefrences: &OperatorPreferences, + preferences: &OperatorPreferences, ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { #[allow(deprecated)] Self::dispatch_hook( @@ -280,7 +278,7 @@ impl Pallet { constant: None, state_mutability: StateMutability::NonPayable, }, - &[Token::Uint(ethabi::Uint::from(blueprint_id)), prefrences.to_ethabi()], + &[Token::Uint(ethabi::Uint::from(blueprint_id)), preferences.to_ethabi()], Zero::zero(), ) } @@ -292,7 +290,7 @@ impl Pallet { /// # Parameters /// * `blueprint` - The service blueprint. /// * `blueprint_id` - The blueprint ID. - /// * `prefrences` - The operator preferences. + /// * `preferences` - The operator preferences. /// /// # Returns /// @@ -301,7 +299,7 @@ impl Pallet { pub fn on_update_price_targets( blueprint: &ServiceBlueprint, blueprint_id: u64, - prefrences: &OperatorPreferences, + preferences: &OperatorPreferences, ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { #[allow(deprecated)] Self::dispatch_hook( @@ -320,7 +318,7 @@ impl Pallet { constant: None, state_mutability: StateMutability::Payable, }, - &[Token::Uint(ethabi::Uint::from(blueprint_id)), prefrences.to_ethabi()], + &[Token::Uint(ethabi::Uint::from(blueprint_id)), preferences.to_ethabi()], Zero::zero(), ) } @@ -333,7 +331,7 @@ impl Pallet { /// # Parameters /// * `blueprint` - The service blueprint. /// * `blueprint_id` - The blueprint ID. - /// * `prefrences` - The operator preferences. + /// * `preferences` - The operator preferences. /// * `request_id` - The request id. /// * `restaking_percent` - The restaking percent. /// @@ -343,7 +341,7 @@ impl Pallet { pub fn on_approve_hook( blueprint: &ServiceBlueprint, blueprint_id: u64, - prefrences: &OperatorPreferences, + preferences: &OperatorPreferences, request_id: u64, restaking_percent: u8, ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { @@ -376,7 +374,7 @@ impl Pallet { }, &[ Token::Uint(ethabi::Uint::from(blueprint_id)), - prefrences.to_ethabi(), + preferences.to_ethabi(), Token::Uint(ethabi::Uint::from(request_id)), Token::Uint(ethabi::Uint::from(restaking_percent)), ], @@ -391,7 +389,7 @@ impl Pallet { /// # Parameters /// * `blueprint` - The service blueprint. /// * `blueprint_id` - The blueprint ID. - /// * `prefrences` - The operator preferences. + /// * `preferences` - The operator preferences. /// * `request_id` - The request id. /// /// # Returns @@ -400,7 +398,7 @@ impl Pallet { pub fn on_reject_hook( blueprint: &ServiceBlueprint, blueprint_id: u64, - prefrences: &OperatorPreferences, + preferences: &OperatorPreferences, request_id: u64, ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { #[allow(deprecated)] @@ -427,7 +425,7 @@ impl Pallet { }, &[ Token::Uint(ethabi::Uint::from(blueprint_id)), - prefrences.to_ethabi(), + preferences.to_ethabi(), Token::Uint(ethabi::Uint::from(request_id)), ], Zero::zero(), @@ -462,9 +460,8 @@ impl Pallet { operators: &[OperatorPreferences], request_args: &[Field], permitted_callers: &[T::AccountId], - _assets: &[T::AssetId], ttl: BlockNumberFor, - paymet_asset: Asset, + payment_asset: Asset, value: BalanceOf, native_value: BalanceOf, ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { @@ -524,9 +521,8 @@ impl Pallet { }) .collect(), ), - // Token::Array(vec![]), Token::Uint(ethabi::Uint::from(ttl.into())), - paymet_asset.to_ethabi(), + payment_asset.to_ethabi(), Token::Uint(ethabi::Uint::from(value.using_encoded(U256::from_little_endian))), ]), ], @@ -557,7 +553,6 @@ impl Pallet { service_id: u64, owner: &T::AccountId, permitted_callers: &[T::AccountId], - _assets: &[T::AssetId], ttl: BlockNumberFor, ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { #[allow(deprecated)] @@ -761,7 +756,7 @@ impl Pallet { /// * `service_id` - The service ID. /// * `job` - The job index. /// * `job_call_id` - The job call ID. - /// * `prefrences` - The operator preferences. + /// * `preferences` - The operator preferences. /// * `inputs` - The input fields. /// * `outputs` - The output fields. /// @@ -774,7 +769,7 @@ impl Pallet { service_id: u64, job: u8, job_call_id: u64, - prefrences: &OperatorPreferences, + preferences: &OperatorPreferences, inputs: &[Field], outputs: &[Field], ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { @@ -825,7 +820,7 @@ impl Pallet { Token::Uint(ethabi::Uint::from(service_id)), Token::Uint(ethabi::Uint::from(job)), Token::Uint(ethabi::Uint::from(job_call_id)), - prefrences.to_ethabi(), + preferences.to_ethabi(), Token::Bytes(Field::encode_to_ethabi(inputs)), Token::Bytes(Field::encode_to_ethabi(outputs)), ], @@ -833,6 +828,348 @@ impl Pallet { ) } + /// Checks if an operator can join a service instance by calling the blueprint's EVM contract. + /// + /// This function dispatches a call to the `canJoin` function of the service blueprint's manager contract + /// to determine if an operator is allowed to join a service instance. + /// + /// # Parameters + /// * `blueprint` - The service blueprint containing the contract details + /// * `blueprint_id` - The ID of the service blueprint + /// * `instance_id` - The ID of the service instance + /// * `operator` - The account ID of the operator trying to join + /// * `preferences` - The operator's preferences for joining the service + /// + /// # Returns + /// * `Result<(bool, Weight), DispatchErrorWithPostInfo>` - A tuple containing: + /// - A boolean indicating if the operator can join + /// - The weight of the EVM operation + pub fn can_join_hook( + blueprint: &ServiceBlueprint, + blueprint_id: u64, + instance_id: u64, + operator: &T::AccountId, + preferences: &OperatorPreferences, + ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { + #[allow(deprecated)] + Self::dispatch_hook( + blueprint, + Function { + name: String::from("canJoin"), + inputs: vec![ + ethabi::Param { + name: String::from("blueprintId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("instanceId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("operator"), + kind: ethabi::ParamType::Address, + internal_type: None, + }, + OperatorPreferences::to_ethabi_param(), + ], + outputs: Default::default(), + constant: None, + state_mutability: StateMutability::NonPayable, + }, + &[ + Token::Uint(ethabi::Uint::from(blueprint_id)), + Token::Uint(ethabi::Uint::from(instance_id)), + Token::Address(T::EvmAddressMapping::into_address(operator.clone())), + preferences.to_ethabi(), + ], + Zero::zero(), + ) + } + + /// Notifies the blueprint's EVM contract that an operator has joined a service instance. + /// + /// This function dispatches a call to the `onOperatorJoined` function of the service blueprint's + /// manager contract after an operator successfully joins a service instance. + /// + /// # Parameters + /// * `blueprint` - The service blueprint containing the contract details + /// * `blueprint_id` - The ID of the service blueprint + /// * `instance_id` - The ID of the service instance + /// * `operator` - The account ID of the operator that joined + /// * `preferences` - The operator's preferences used when joining + /// + /// # Returns + /// * `Result<(bool, Weight), DispatchErrorWithPostInfo>` - A tuple containing: + /// - A boolean indicating if the notification was successful + /// - The weight of the EVM operation + pub fn on_operator_joined_hook( + blueprint: &ServiceBlueprint, + blueprint_id: u64, + instance_id: u64, + operator: &T::AccountId, + preferences: &OperatorPreferences, + ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { + #[allow(deprecated)] + Self::dispatch_hook( + blueprint, + Function { + name: String::from("onOperatorJoined"), + inputs: vec![ + ethabi::Param { + name: String::from("blueprintId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("instanceId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("operator"), + kind: ethabi::ParamType::Address, + internal_type: None, + }, + OperatorPreferences::to_ethabi_param(), + ], + outputs: Default::default(), + constant: None, + state_mutability: StateMutability::NonPayable, + }, + &[ + Token::Uint(ethabi::Uint::from(blueprint_id)), + Token::Uint(ethabi::Uint::from(instance_id)), + Token::Address(T::EvmAddressMapping::into_address(operator.clone())), + preferences.to_ethabi(), + ], + Zero::zero(), + ) + } + + /// Checks if an operator can leave a service instance by calling the blueprint's EVM contract. + /// + /// This function dispatches a call to the `canLeave` function of the service blueprint's manager contract + /// to determine if an operator is allowed to leave a service instance. + /// + /// # Parameters + /// * `blueprint` - The service blueprint containing the contract details + /// * `blueprint_id` - The ID of the service blueprint + /// * `instance_id` - The ID of the service instance + /// * `operator` - The account ID of the operator trying to leave + /// + /// # Returns + /// * `Result<(bool, Weight), DispatchErrorWithPostInfo>` - A tuple containing: + /// - A boolean indicating if the operator can leave + /// - The weight of the EVM operation + pub fn can_leave_hook( + blueprint: &ServiceBlueprint, + blueprint_id: u64, + instance_id: u64, + operator: &T::AccountId, + ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { + #[allow(deprecated)] + Self::dispatch_hook( + blueprint, + Function { + name: String::from("canLeave"), + inputs: vec![ + ethabi::Param { + name: String::from("blueprintId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("instanceId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("operator"), + kind: ethabi::ParamType::Address, + internal_type: None, + }, + ], + outputs: Default::default(), + constant: None, + state_mutability: StateMutability::NonPayable, + }, + &[ + Token::Uint(ethabi::Uint::from(blueprint_id)), + Token::Uint(ethabi::Uint::from(instance_id)), + Token::Address(T::EvmAddressMapping::into_address(operator.clone())), + ], + Zero::zero(), + ) + } + + /// Notifies the blueprint's EVM contract that an operator has left a service instance. + /// + /// This function dispatches a call to the `onOperatorLeft` function of the service blueprint's + /// manager contract after an operator successfully leaves a service instance. + /// + /// # Parameters + /// * `blueprint` - The service blueprint containing the contract details + /// * `blueprint_id` - The ID of the service blueprint + /// * `instance_id` - The ID of the service instance + /// * `operator` - The account ID of the operator that left + /// + /// # Returns + /// * `Result<(bool, Weight), DispatchErrorWithPostInfo>` - A tuple containing: + /// - A boolean indicating if the notification was successful + /// - The weight of the EVM operation + pub fn on_operator_left_hook( + blueprint: &ServiceBlueprint, + blueprint_id: u64, + instance_id: u64, + operator: &T::AccountId, + ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { + #[allow(deprecated)] + Self::dispatch_hook( + blueprint, + Function { + name: String::from("onOperatorLeft"), + inputs: vec![ + ethabi::Param { + name: String::from("blueprintId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("instanceId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("operator"), + kind: ethabi::ParamType::Address, + internal_type: None, + }, + ], + outputs: Default::default(), + constant: None, + state_mutability: StateMutability::NonPayable, + }, + &[ + Token::Uint(ethabi::Uint::from(blueprint_id)), + Token::Uint(ethabi::Uint::from(instance_id)), + Token::Address(T::EvmAddressMapping::into_address(operator.clone())), + ], + Zero::zero(), + ) + } + + /// Hook to be called when a slash is applied. + /// + /// This function is called when a slash is applied to an operator. It performs an EVM call + /// to the `onSlash` function of the service blueprint's manager contract. + /// + /// # Parameters + /// * `blueprint` - The service blueprint. + /// * `service_id` - The service ID. + /// * `offender` - The account ID of the offender being slashed. + /// * `slash_percent` - The percentage to slash. + /// + /// # Returns + /// * `Result<(bool, Weight), DispatchErrorWithPostInfo>` - A tuple containing a boolean + /// indicating whether the slash is allowed and the weight of the operation. + pub fn on_slash_hook( + blueprint: &ServiceBlueprint, + service_id: u64, + offender: &T::AccountId, + slash_percent: u8, + ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { + #[allow(deprecated)] + Self::dispatch_hook( + blueprint, + Function { + name: String::from("onSlash"), + inputs: vec![ + ethabi::Param { + name: String::from("serviceId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("offender"), + kind: ethabi::ParamType::Bytes, + internal_type: None, + }, + ethabi::Param { + name: String::from("slashPercent"), + kind: ethabi::ParamType::Uint(8), + internal_type: None, + }, + ], + outputs: Default::default(), + constant: None, + state_mutability: StateMutability::NonPayable, + }, + &[ + Token::Uint(ethabi::Uint::from(service_id)), + Token::Bytes(offender.encode()), + Token::Uint(ethabi::Uint::from(slash_percent)), + ], + Zero::zero(), + ) + } + + /// Hook to be called when a slash is unapplied. + /// + /// This function is called when a slash is unapplied from an operator. It performs an EVM call + /// to the `onUnappliedSlash` function of the service blueprint's manager contract. + /// + /// # Parameters + /// * `blueprint` - The service blueprint. + /// * `service_id` - The service ID. + /// * `offender` - The account ID of the offender being unslashed. + /// * `slash_percent` - The percentage that was slashed. + /// + /// # Returns + /// * `Result<(bool, Weight), DispatchErrorWithPostInfo>` - A tuple containing a boolean + /// indicating whether the unslash is allowed and the weight of the operation. + pub fn on_unapplied_slash_hook( + blueprint: &ServiceBlueprint, + service_id: u64, + offender: &T::AccountId, + slash_percent: u8, + ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { + #[allow(deprecated)] + Self::dispatch_hook( + blueprint, + Function { + name: String::from("onUnappliedSlash"), + inputs: vec![ + ethabi::Param { + name: String::from("serviceId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("offender"), + kind: ethabi::ParamType::Bytes, + internal_type: None, + }, + ethabi::Param { + name: String::from("slashPercent"), + kind: ethabi::ParamType::Uint(8), + internal_type: None, + }, + ], + outputs: Default::default(), + constant: None, + state_mutability: StateMutability::NonPayable, + }, + &[ + Token::Uint(ethabi::Uint::from(service_id)), + Token::Bytes(offender.encode()), + Token::Uint(ethabi::Uint::from(slash_percent)), + ], + Zero::zero(), + ) + } + /// Queries the slashing origin of a service. /// /// This function performs an EVM call to the `querySlashingOrigin` function of the @@ -965,7 +1302,19 @@ impl Pallet { Ok((dispute_origin, weight)) } - /// Moves a `value` amount of tokens from the caller's account to `to`. + /// Transfers ERC20 tokens between accounts by calling the ERC20 contract's transfer function. + /// + /// # Arguments + /// * `erc20` - The ERC20 contract address + /// * `from` - The address to transfer tokens from + /// * `to` - The address to transfer tokens to + /// * `value` - The amount of tokens to transfer + /// + /// # Returns + /// * `Ok((bool, Weight))` - A tuple containing: + /// * A boolean indicating if the transfer was successful + /// * The weight consumed by the EVM call + /// * `Err(DispatchErrorWithPostInfo)` - If the EVM call fails or the ABI encoding/decoding fails pub fn erc20_transfer( erc20: H160, from: H160, @@ -1002,6 +1351,12 @@ impl Pallet { ]; log::debug!(target: "evm", "Dispatching EVM call(0x{}): {}", hex::encode(transfer_fn.short_signature()), transfer_fn.signature()); + #[cfg(test)] + eprintln!( + "Dispatching EVM call(0x{}): {}", + hex::encode(transfer_fn.short_signature()), + transfer_fn.signature() + ); let data = transfer_fn.encode_input(&args).map_err(|_| Error::::EVMAbiEncode)?; let gas_limit = 500_000; let info = Self::evm_call(from, erc20, U256::zero(), data, gas_limit)?; @@ -1051,7 +1406,8 @@ impl Pallet { log::debug!(target: "evm", "Dispatching EVM call(0x{}): {}", hex::encode(transfer_fn.short_signature()), transfer_fn.signature()); let data = transfer_fn.encode_input(&args).map_err(|_| Error::::EVMAbiEncode)?; let gas_limit = 500_000; - let info = Self::evm_call(Self::address(), erc20, U256::zero(), data, gas_limit)?; + let info = + Self::evm_call(Self::pallet_evm_account(), erc20, U256::zero(), data, gas_limit)?; let weight = Self::weight_from_call_info(&info); // decode the result and return it @@ -1071,6 +1427,71 @@ impl Pallet { Ok((balance, weight)) } + /// Hook to notify an external contract about a slash event. + /// + /// This function performs an EVM call to notify an external contract about a slash event + /// by calling its `onSlashEvent` function. + /// + /// # Parameters + /// * `contract` - The address of the contract to notify + /// * `blueprint_id` - The ID of the blueprint + /// * `service_id` - The ID of the service + /// * `operator` - The account ID of the operator being slashed + /// * `slash_percent` - The percentage being slashed (0-100) + /// + /// # Returns + /// * `Result<(bool, Weight), DispatchErrorWithPostInfo>` - A tuple containing a boolean + /// indicating whether the notification was successful and the weight of the operation. + pub fn notify_slash_event( + contract: H160, + blueprint_id: u64, + service_id: u64, + operator: &T::AccountId, + amount: u128, + ) -> Result<(bool, Weight), DispatchErrorWithPostInfo> { + #[allow(deprecated)] + let (info, weight) = Self::dispatch_evm_call( + contract, + Function { + name: String::from("onSlashEvent"), + inputs: vec![ + ethabi::Param { + name: String::from("blueprintId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("serviceId"), + kind: ethabi::ParamType::Uint(64), + internal_type: None, + }, + ethabi::Param { + name: String::from("operator"), + kind: ethabi::ParamType::Bytes, + internal_type: None, + }, + ethabi::Param { + name: String::from("amount"), + kind: ethabi::ParamType::Uint(256), + internal_type: None, + }, + ], + outputs: Default::default(), + constant: None, + state_mutability: StateMutability::NonPayable, + }, + &[ + Token::Uint(ethabi::Uint::from(blueprint_id)), + Token::Uint(ethabi::Uint::from(service_id)), + Token::Bytes(operator.encode()), + Token::Uint(ethabi::Uint::from(U256::from(amount))), + ], + Zero::zero(), + )?; + + Ok((info.exit_reason.is_succeed(), weight)) + } + /// Dispatches a hook to the EVM and returns if the call was successful with the used weight. fn dispatch_hook( blueprint: &ServiceBlueprint, @@ -1091,10 +1512,16 @@ impl Pallet { value: BalanceOf, ) -> Result<(fp_evm::CallInfo, Weight), DispatchErrorWithPostInfo> { log::debug!(target: "evm", "Dispatching EVM call(0x{}): {}", hex::encode(f.short_signature()), f.signature()); + #[cfg(test)] + eprintln!( + "Dispatching EVM call(0x{}): {}", + hex::encode(f.short_signature()), + f.signature() + ); let data = f.encode_input(args).map_err(|_| Error::::EVMAbiEncode)?; - let gas_limit = 500_000; + let gas_limit = 2_000_000; let value = value.using_encoded(U256::from_little_endian); - let info = Self::evm_call(Self::address(), contract, value, data, gas_limit)?; + let info = Self::evm_call(Self::pallet_evm_account(), contract, value, data, gas_limit)?; let weight = Self::weight_from_call_info(&info); Ok((info, weight)) } @@ -1115,11 +1542,22 @@ impl Pallet { Ok(info) => { log::debug!( target: "evm", - "Call from: {:?}, to: {:?}, data: 0x{}, gas_limit: {:?}, result: {:?}", + "Call from: {:?}, to: {:?}, data: 0x{}, gas_limit: {:?}, value: {}, result: {:?}", + from, + to, + hex::encode(&data), + gas_limit, + value, + info, + ); + #[cfg(test)] + eprintln!( + "Call from: {:?}, to: {:?}, data: 0x{}, gas_limit: {:?}, value: {}, result: {:?}", from, to, hex::encode(&data), gas_limit, + value, info, ); // if we have a revert reason, emit an event @@ -1147,10 +1585,35 @@ impl Pallet { } Ok(info) }, - Err(e) => Err(DispatchErrorWithPostInfo { - post_info: PostDispatchInfo { actual_weight: Some(e.weight), pays_fee: Pays::Yes }, - error: e.error.into(), - }), + Err(e) => { + let actual_weight = e.weight; + let e = e.error.into(); + log::error!( + target: "evm", + "Call from: {:?}, to: {:?}, data: 0x{}, gas_limit: {:?}, error: {:?}", + from, + to, + hex::encode(&data), + gas_limit, + e, + ); + #[cfg(test)] + eprintln!( + "Call from: {:?}, to: {:?}, data: 0x{}, gas_limit: {:?}, error: {:?}", + from, + to, + hex::encode(&data), + gas_limit, + e, + ); + Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { + actual_weight: Some(actual_weight), + pays_fee: Pays::Yes, + }, + error: e, + }) + }, } } diff --git a/pallets/services/src/functions/membership.rs b/pallets/services/src/functions/membership.rs new file mode 100644 index 000000000..4ca180ec1 --- /dev/null +++ b/pallets/services/src/functions/membership.rs @@ -0,0 +1,131 @@ +use crate::{Config, Error, Instances, Pallet}; +use frame_support::pallet_prelude::*; +use sp_std::vec::Vec; +use tangle_primitives::services::{ + AssetSecurityCommitment, MembershipModel, OperatorPreferences, ServiceBlueprint, +}; + +impl Pallet { + /// Implementation of join_service extrinsic + pub(crate) fn do_join_service( + blueprint: &ServiceBlueprint, + blueprint_id: u64, + instance_id: u64, + operator: &T::AccountId, + preferences: &OperatorPreferences, + security_commitments: Vec>, + ) -> DispatchResult { + // Get service instance + let instance = Instances::::get(instance_id)?; + + // Validate membership model + match instance.membership_model { + MembershipModel::Fixed { .. } => { + return Err(Error::::DynamicMembershipNotSupported.into()) + }, + MembershipModel::Dynamic { max_operators, .. } => { + // Check max operators if set + if let Some(max) = max_operators { + ensure!( + instance.operator_security_commitments.len() < max as usize, + Error::::MaxOperatorsReached + ); + } + }, + } + + // Check if operator can join via blueprint contract + let (can_join, _) = + Self::can_join_hook(blueprint, blueprint_id, instance_id, operator, preferences) + .map_err(|e| { + log::error!("Can join hook failed: {:?}", e); + Error::::OnCanJoinFailure + })?; + ensure!(can_join, Error::::JoinRejected); + + // Add operator to instance + Instances::::try_mutate(instance_id, |maybe_instance| -> DispatchResult { + let instance = maybe_instance.as_mut().map_err(|e| { + log::error!("Service not found: {:?}", e); + Error::::ServiceNotFound + })?; + instance + .operator_security_commitments + .try_push(( + operator.clone(), + BoundedVec::try_from(security_commitments.clone()).map_err(|e| { + log::error!("Failed to convert security commitments: {:?}", e); + Error::::MaxOperatorsReached + })?, + )) + .map_err(|e| { + log::error!("Failed to push security commitments: {:?}", e); + Error::::MaxOperatorsReached + })?; + + Ok(()) + })?; + + // Notify blueprint + Self::on_operator_joined_hook(blueprint, blueprint_id, instance_id, operator, preferences) + .map_err(|e| { + log::error!("Operator joined hook failed: {:?}", e); + Error::::OnOperatorJoinFailure + })?; + + Ok(()) + } + + /// Implementation of leave_service extrinsic + pub(crate) fn do_leave_service( + blueprint: &ServiceBlueprint, + blueprint_id: u64, + instance_id: u64, + operator: &T::AccountId, + ) -> DispatchResult { + // Get service instance + let instance = Instances::::get(instance_id)?; + + // Validate membership model + match instance.membership_model { + MembershipModel::Fixed { .. } => { + return Err(Error::::DynamicMembershipNotSupported.into()) + }, + MembershipModel::Dynamic { min_operators, .. } => { + // Ensure minimum operators maintained + ensure!( + instance.operator_security_commitments.len() > min_operators as usize, + Error::::TooFewOperators + ); + }, + } + + // Check if operator can leave via blueprint contract + let (can_leave, _) = Self::can_leave_hook(blueprint, blueprint_id, instance_id, operator) + .map_err(|e| { + log::error!("Can leave hook failed: {:?}", e); + Error::::OnCanLeaveFailure + })?; + ensure!(can_leave, Error::::LeaveRejected); + + // Remove operator from instance + Instances::::try_mutate(instance_id, |maybe_instance| -> DispatchResult { + let instance = maybe_instance.as_mut().map_err(|e| { + log::error!("Service not found: {:?}", e); + Error::::ServiceNotFound + })?; + instance.operator_security_commitments.retain(|(op, _)| op != operator); + Ok(()) + })?; + + // Notify blueprint + Self::on_operator_left_hook(blueprint, blueprint_id, instance_id, operator).map_err( + |e| { + log::error!("Operator left hook failed: {:?}", e); + Error::::OnOperatorLeaveFailure + }, + )?; + + Ok(()) + } +} diff --git a/pallets/services/src/functions/mod.rs b/pallets/services/src/functions/mod.rs new file mode 100644 index 000000000..156417678 --- /dev/null +++ b/pallets/services/src/functions/mod.rs @@ -0,0 +1,6 @@ +pub mod approve; +pub mod evm_hooks; +pub mod membership; +pub mod register; +pub mod reject; +pub mod request; diff --git a/pallets/services/src/functions/register.rs b/pallets/services/src/functions/register.rs new file mode 100644 index 000000000..e73626860 --- /dev/null +++ b/pallets/services/src/functions/register.rs @@ -0,0 +1,91 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use crate::{BalanceOf, Config, Error, Event, Operators, OperatorsProfile, Pallet}; +use frame_support::{ + dispatch::DispatchResult, + pallet_prelude::*, + traits::{Currency, ExistenceRequirement}, +}; +use sp_std::vec::Vec; +use tangle_primitives::services::{Field, OperatorPreferences, OperatorProfile}; + +impl Pallet { + pub fn do_register( + operator: &T::AccountId, + blueprint_id: u64, + preferences: OperatorPreferences, + registration_args: Vec>, + value: BalanceOf, + ) -> DispatchResult { + let (_, blueprint) = Self::blueprints(blueprint_id)?; + + blueprint + .type_check_registration(®istration_args) + .map_err(Error::::TypeCheck)?; + + // Transfer the registration value to the pallet + T::Currency::transfer( + operator, + &Self::pallet_account(), + value, + ExistenceRequirement::KeepAlive, + )?; + + let (allowed, _weight) = Self::on_register_hook( + &blueprint, + blueprint_id, + &preferences, + ®istration_args, + value, + ) + .map_err(|e| { + log::error!("Error in on_register_hook: {:?}", e); + Error::::OnRegisterHookFailed + })?; + + ensure!(allowed, Error::::InvalidRegistrationInput); + + Operators::::insert(blueprint_id, &operator, preferences); + + OperatorsProfile::::try_mutate(&operator, |profile| { + match profile { + Ok(p) => { + p.blueprints + .try_insert(blueprint_id) + .map_err(|_| Error::::MaxBlueprintsPerOperatorExceeded)?; + }, + Err(_) => { + let mut blueprints = BoundedBTreeSet::new(); + blueprints + .try_insert(blueprint_id) + .map_err(|_| Error::::MaxBlueprintsPerOperatorExceeded)?; + *profile = Ok(OperatorProfile { blueprints, ..Default::default() }); + }, + }; + Result::<_, Error>::Ok(()) + })?; + + Self::deposit_event(Event::Registered { + provider: operator.clone(), + blueprint_id, + preferences, + registration_args, + }); + + Ok(()) + } +} diff --git a/pallets/services/src/functions/reject.rs b/pallets/services/src/functions/reject.rs new file mode 100644 index 000000000..3bc8be566 --- /dev/null +++ b/pallets/services/src/functions/reject.rs @@ -0,0 +1,118 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use crate::{Config, Error, Event, Pallet, StagingServicePayments}; +use frame_support::{ + pallet_prelude::*, + traits::{fungibles::Mutate, tokens::Preservation, Currency, ExistenceRequirement}, +}; +use sp_runtime::traits::Zero; +use tangle_primitives::services::{ApprovalState, Asset}; + +impl Pallet { + /// Process a rejection of a service request by an operator. + /// + /// This function handles the rejection workflow including: + /// - Updating the operator's approval state to rejected + /// - Refunding any staged payments + /// - Emitting appropriate events + /// + /// # Arguments + /// + /// * `operator` - The account ID of the operator rejecting the request + /// * `request_id` - The ID of the service request being rejected + /// + /// # Returns + /// + /// Returns a DispatchResult indicating success or the specific error that occurred + pub fn do_reject(operator: T::AccountId, request_id: u64) -> DispatchResult { + let mut request = Self::service_requests(request_id)?; + let updated = + request.operators_with_approval_state.iter_mut().find_map(|(v, ref mut s)| { + if v == &operator { + *s = ApprovalState::Rejected; + Some(()) + } else { + None + } + }); + + ensure!(updated.is_some(), Error::::ApprovalNotRequested); + + let blueprint_id = request.blueprint; + let (_, blueprint) = Self::blueprints(blueprint_id)?; + let prefs = Self::operators(blueprint_id, operator.clone())?; + + let (allowed, _weight) = Self::on_reject_hook(&blueprint, blueprint_id, &prefs, request_id) + .map_err(|_| Error::::OnRejectFailure)?; + ensure!(allowed, Error::::RejectionInterrupted); + + Self::deposit_event(Event::ServiceRequestRejected { + operator, + blueprint_id: request.blueprint, + request_id, + }); + + // Refund the payment if it exists + if let Some(payment) = Self::service_payment(request_id) { + match payment.asset { + Asset::Custom(asset_id) if asset_id == Zero::zero() => { + // For native currency, we expect an AccountId + let refund_to = payment + .refund_to + .try_into_account_id() + .map_err(|_| Error::::ExpectedAccountId)?; + T::Currency::transfer( + &Self::pallet_account(), + &refund_to, + payment.amount, + ExistenceRequirement::AllowDeath, + )?; + }, + Asset::Custom(asset_id) => { + let refund_to = payment + .refund_to + .try_into_account_id() + .map_err(|_| Error::::ExpectedAccountId)?; + T::Fungibles::transfer( + asset_id, + &Self::pallet_account(), + &refund_to, + payment.amount, + Preservation::Expendable, + )?; + }, + Asset::Erc20(token) => { + let refund_to = payment + .refund_to + .try_into_address() + .map_err(|_| Error::::ExpectedEVMAddress)?; + let (success, _weight) = Self::erc20_transfer( + token, + Self::pallet_evm_account(), + refund_to, + payment.amount, + ) + .map_err(|_| Error::::ERC20TransferFailed)?; + ensure!(success, Error::::ERC20TransferFailed); + }, + } + StagingServicePayments::::remove(request_id); + } + + Ok(()) + } +} diff --git a/pallets/services/src/functions/request.rs b/pallets/services/src/functions/request.rs new file mode 100644 index 000000000..9578fa003 --- /dev/null +++ b/pallets/services/src/functions/request.rs @@ -0,0 +1,227 @@ +use crate::{ + BalanceOf, Config, Error, Event, MaxFieldsOf, MaxOperatorsPerServiceOf, MaxPermittedCallersOf, + NextServiceRequestId, Pallet, ServiceRequests, StagingServicePayments, +}; +use frame_support::{ + pallet_prelude::*, + traits::{fungibles::Mutate, tokens::Preservation, Currency, ExistenceRequirement}, + BoundedVec, +}; +use frame_system::pallet_prelude::*; +use sp_core::H160; +use sp_runtime::{traits::Zero, Percent}; +use sp_std::vec::Vec; +use tangle_primitives::{ + services::{ + ApprovalState, Asset, AssetSecurityRequirement, EvmAddressMapping, Field, MembershipModel, + ServiceRequest, StagingServicePayment, + }, + Account, +}; + +impl Pallet { + /// Request a new service using a blueprint and specified operators. + /// + /// # Arguments + /// + /// * `caller` - The account requesting the service + /// * `evm_origin` - Optional EVM address for ERC20 payments + /// * `blueprint_id` - The identifier of the blueprint to use + /// * `permitted_callers` - Accounts allowed to call the service + /// * `operators` - List of operators that will run the service + /// * `request_args` - Blueprint initialization arguments + /// * `native_asset_requirement` - Native asset requirement for the service + /// * `security_requirements` - Non-native asset requirements for the service + /// * `ttl` - Time-to-live in blocks for the service request + /// * `payment_asset` - Asset used for payment (native, custom or ERC20) + /// * `value` - Payment amount for the service + #[allow(clippy::too_many_arguments)] + pub(crate) fn do_request( + caller: T::AccountId, + evm_origin: Option, + blueprint_id: u64, + permitted_callers: Vec, + operators: Vec, + request_args: Vec>, + mut security_requirements: Vec>, + ttl: BlockNumberFor, + payment_asset: Asset, + value: BalanceOf, + membership_model: MembershipModel, + ) -> Result { + let (_, blueprint) = Self::blueprints(blueprint_id)?; + + blueprint.type_check_request(&request_args).map_err(Error::::TypeCheck)?; + // ensure we at least have one asset and all assets are unique + ensure!(!security_requirements.is_empty(), Error::::NoAssetsProvided); + + // First validate all security requirements have non-zero exposures + for requirement in security_requirements.iter() { + ensure!( + requirement.min_exposure_percent > Percent::zero() + && requirement.max_exposure_percent > Percent::zero() + && requirement.min_exposure_percent <= requirement.max_exposure_percent + && requirement.max_exposure_percent <= Percent::from_percent(100), + Error::::InvalidSecurityRequirements + ); + } + + ensure!( + security_requirements + .iter() + .map(|req| &req.asset) + .collect::>() + .len() == security_requirements.len(), + Error::::DuplicateAsset + ); + + // Check if native asset exists in requirements + let has_native_asset = + security_requirements.iter().any(|req| req.asset == Asset::Custom(Zero::zero())); + + // If native asset not found, append it with minimum requirements + if !has_native_asset { + security_requirements.push(AssetSecurityRequirement { + asset: Asset::Custom(Zero::zero()), + min_exposure_percent: T::MinimumNativeSecurityRequirement::get(), + max_exposure_percent: Percent::from_percent(100), + }); + } + + // Get native asset requirement for validation + let native_asset_requirement = security_requirements + .iter() + .find(|req| req.asset == Asset::Custom(Zero::zero())) + .ok_or(Error::::NoNativeAsset)?; + + ensure!( + native_asset_requirement.min_exposure_percent + >= T::MinimumNativeSecurityRequirement::get(), + Error::::NativeAssetExposureTooLow + ); + + let security_requirements = BoundedVec::try_from(security_requirements) + .map_err(|_| Error::::MaxAssetsPerServiceExceeded)?; + + let mut preferences = Vec::new(); + let mut pending_approvals = Vec::new(); + for provider in &operators { + let prefs = Self::operators(blueprint_id, provider)?; + pending_approvals.push(provider.clone()); + preferences.push(prefs); + } + + let mut native_value = Zero::zero(); + let request_id = Self::next_service_request_id(); + if value != Zero::zero() { + // Payment transfer + let refund_to = match payment_asset.clone() { + // Handle the case of native currency. + Asset::Custom(asset_id) if asset_id == Zero::zero() => { + #[cfg(test)] + eprintln!( + "Transferring native value {:?} from {:?} to pallet account", + value, caller + ); + T::Currency::transfer( + &caller, + &Self::pallet_account(), + value, + ExistenceRequirement::KeepAlive, + )?; + native_value = value; + Account::id(caller.clone()) + }, + Asset::Custom(asset_id) => { + T::Fungibles::transfer( + asset_id, + &caller, + &Self::pallet_account(), + value, + Preservation::Preserve, + )?; + Account::id(caller.clone()) + }, + Asset::Erc20(token) => { + // origin check. + let evm_origin = evm_origin.ok_or(Error::::MissingEVMOrigin)?; + let mapped_origin = T::EvmAddressMapping::into_account_id(evm_origin); + ensure!(mapped_origin == caller, DispatchError::BadOrigin); + let (success, _weight) = + Self::erc20_transfer(token, evm_origin, Self::pallet_evm_account(), value) + .map_err(|_| Error::::ERC20TransferFailed)?; + ensure!(success, Error::::ERC20TransferFailed); + Account::from(evm_origin) + }, + }; + + // Save the payment information for the service request. + let payment = StagingServicePayment { + request_id, + refund_to, + asset: payment_asset.clone(), + amount: value, + }; + StagingServicePayments::::insert(request_id, payment); + } + + let (allowed, _weight) = Self::on_request_hook( + &blueprint, + blueprint_id, + &caller, + request_id, + &preferences, + &request_args, + &permitted_callers, + ttl, + payment_asset, + value, + native_value, + ) + .map_err(|_| Error::::OnRequestFailure)?; + + ensure!(allowed, Error::::InvalidRequestInput); + + let permitted_callers = + BoundedVec::<_, MaxPermittedCallersOf>::try_from(permitted_callers) + .map_err(|_| Error::::MaxPermittedCallersExceeded)?; + let operators = pending_approvals + .iter() + .cloned() + .map(|v| (v, ApprovalState::Pending)) + .collect::>(); + + let args = BoundedVec::<_, MaxFieldsOf>::try_from(request_args) + .map_err(|_| Error::::MaxFieldsExceeded)?; + + let operators_with_approval_state = + BoundedVec::<_, MaxOperatorsPerServiceOf>::try_from(operators) + .map_err(|_| Error::::MaxServiceProvidersExceeded)?; + + ServiceRequests::::insert( + request_id, + ServiceRequest { + blueprint: blueprint_id, + owner: caller.clone(), + security_requirements: security_requirements.clone(), + ttl, + args, + permitted_callers, + operators_with_approval_state, + membership_model, + }, + ); + NextServiceRequestId::::set(request_id.saturating_add(1)); + + Self::deposit_event(Event::ServiceRequested { + owner: caller, + request_id, + blueprint_id, + pending_approvals, + approved: Default::default(), + security_requirements, + }); + + Ok(request_id) + } +} diff --git a/pallets/services/src/impls.rs b/pallets/services/src/impls.rs index e4b1f5649..996cd6ff2 100644 --- a/pallets/services/src/impls.rs +++ b/pallets/services/src/impls.rs @@ -1,10 +1,6 @@ use super::*; use crate::types::BalanceOf; -#[cfg(not(feature = "std"))] -use alloc::vec::Vec; -use sp_std::vec; -#[cfg(feature = "std")] -use std::vec::Vec; +use sp_std::{vec, vec::Vec}; use tangle_primitives::{services::Constraints, traits::ServiceManager, BlueprintId}; impl Constraints for types::ConstraintsOf { @@ -66,6 +62,10 @@ impl ServiceManager> for crate::Pal .map_or(true, |profile| profile.services.is_empty() && profile.blueprints.is_empty()) } + fn has_active_services(operator: &T::AccountId) -> bool { + OperatorsProfile::::get(operator).is_ok_and(|profile| !profile.services.is_empty()) + } + fn get_blueprints_by_operator(operator: &T::AccountId) -> Vec { OperatorsProfile::::get(operator) .map_or(vec![], |profile| profile.blueprints.into_iter().collect()) @@ -73,17 +73,19 @@ impl ServiceManager> for crate::Pal } #[cfg(feature = "runtime-benchmarks")] -pub struct BenchmarkingOperatorDelegationManager( - core::marker::PhantomData<(T, Balance, AssetId)>, +pub struct BenchmarkingOperatorDelegationManager( + core::marker::PhantomData<(T, Balance)>, ); #[cfg(feature = "runtime-benchmarks")] -impl - tangle_primitives::traits::MultiAssetDelegationInfo - for BenchmarkingOperatorDelegationManager +impl + tangle_primitives::traits::MultiAssetDelegationInfo< + T::AccountId, + Balance, + BlockNumberFor, + T::AssetId, + > for BenchmarkingOperatorDelegationManager { - type AssetId = AssetId; - fn get_current_round() -> tangle_primitives::types::RoundIndex { Default::default() } @@ -100,10 +102,20 @@ impl Default::default() } - fn get_total_delegation_by_asset_id( - _operator: &T::AccountId, - _asset_id: &Self::AssetId, - ) -> Balance { + fn get_total_delegation_by_asset(_operator: &T::AccountId, _asset_id: &AssetId) -> Balance { Default::default() } + + fn get_delegators_for_operator( + _operator: &T::AccountId, + ) -> Vec<(T::AccountId, Balance, Asset)> { + Vec::new() + } + + fn get_user_deposit_with_locks( + _who: &T::AccountId, + _asset: Asset, + ) -> Option>> { + None + } } diff --git a/pallets/services/src/lib.rs b/pallets/services/src/lib.rs index c1693a1cd..9d4db7664 100644 --- a/pallets/services/src/lib.rs +++ b/pallets/services/src/lib.rs @@ -22,16 +22,27 @@ extern crate alloc; use frame_support::{ pallet_prelude::*, - traits::{Currency, ExistenceRequirement, ReservableCurrency}, + traits::{Currency, ReservableCurrency}, }; use frame_system::pallet_prelude::*; -use sp_runtime::{traits::Get, DispatchResult}; -use tangle_primitives::traits::MultiAssetDelegationInfo; +use sp_runtime::{ + traits::{Get, Zero}, + DispatchResult, +}; +use tangle_primitives::traits::SlashManager; +use tangle_primitives::{ + services::{ + AssetSecurityCommitment, AssetSecurityRequirement, MembershipModel, UnappliedSlash, + }, + traits::MultiAssetDelegationInfo, + BlueprintId, InstanceId, JobCallId, ServiceRequestId, +}; -mod functions; +pub mod functions; mod impls; mod rpc; pub mod types; +use types::*; #[cfg(test)] mod mock; @@ -46,7 +57,6 @@ mod benchmarking; pub mod weights; pub use module::*; -use tangle_primitives::BlueprintId; pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -58,22 +68,12 @@ pub mod module { use super::*; use frame_support::{ dispatch::PostDispatchInfo, - traits::{ - fungibles::{Inspect, Mutate}, - tokens::Preservation, - }, + traits::fungibles::{Inspect, Mutate}, }; use sp_core::H160; - use sp_runtime::{ - traits::{AtLeast32BitUnsigned, MaybeSerializeDeserialize, Zero}, - Percent, - }; - use sp_std::vec::Vec; - use tangle_primitives::{ - services::{MasterBlueprintServiceManagerRevision, *}, - Account, - }; - use types::*; + use sp_runtime::{traits::MaybeSerializeDeserialize, Percent}; + use sp_std::{collections::btree_set::BTreeSet, vec::Vec}; + use tangle_primitives::services::*; #[pallet::config] pub trait Config: frame_system::Config { @@ -87,9 +87,10 @@ pub mod module { type Fungibles: Inspect> + Mutate; - /// `Pallet` EVM Address. + /// PalletId used for deriving the AccountId and EVM address. + /// This account receives slashed assets upon slash event processing. #[pallet::constant] - type PalletEVMAddress: Get; + type PalletEvmAccount: Get; /// A type that implements the `EvmRunner` trait for the execution of EVM /// transactions. @@ -103,14 +104,7 @@ pub mod module { type EvmAddressMapping: tangle_primitives::services::EvmAddressMapping; /// The asset ID type. - type AssetId: AtLeast32BitUnsigned - + Parameter - + Member - + MaybeSerializeDeserialize - + Clone - + Copy - + PartialOrd - + MaxEncodedLen; + type AssetId: AssetIdT; /// Maximum number of fields in a job call. #[pallet::constant] @@ -188,8 +182,12 @@ pub mod module { Self::AccountId, BalanceOf, BlockNumberFor, + Self::AssetId, >; + /// Manager for slashing that dispatches slash operations to `pallet-multi-asset-delegation`. + type SlashManager: tangle_primitives::traits::SlashManager; + /// Number of eras that slashes are deferred by, after computation. /// /// This should be less than the bonding duration. Set to 0 if slashes @@ -200,6 +198,13 @@ pub mod module { /// The origin which can manage Add a new Master Blueprint Service Manager revision. type MasterBlueprintServiceManagerUpdateOrigin: EnsureOrigin; + /// The minimum percentage of native token stake that operators must expose for slashing. + #[pallet::constant] + type MinimumNativeSecurityRequirement: Get + + Default + + Parameter + + MaybeSerializeDeserialize; + /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; } @@ -208,10 +213,41 @@ pub mod module { impl Hooks> for Pallet { fn integrity_test() { // Ensure that the pallet's configuration is valid. - // 1. Make sure that pallet's associated AccountId value maps correctly to the EVM - // address. - let account_id = T::EvmAddressMapping::into_account_id(Self::address()); - assert_eq!(account_id, Self::account_id(), "Services: AccountId mapping is incorrect."); + // 1. Make sure that pallet's evm address maps correctly back to the Substrate account + let evm_address = T::EvmAddressMapping::into_account_id(Self::pallet_evm_account()); + assert_eq!( + evm_address, + Self::pallet_account(), + "Services: EVM address mapping is incorrect." + ); + } + + /// On initialize, we should check for any unapplied slashes and apply them. + fn on_initialize(_n: BlockNumberFor) -> Weight { + let mut weight = Zero::zero(); + let current_era = T::OperatorDelegationManager::get_current_round(); + let slash_defer_duration = T::SlashDeferDuration::get(); + + // Only process slashes from eras that have completed their deferral period + let process_era = current_era.saturating_sub(slash_defer_duration); + + // Get all unapplied slashes for this era + let prefix_iter = UnappliedSlashes::::iter_prefix(process_era); + for (index, slash) in prefix_iter { + // TODO: This call must be all or nothing. + // TODO: If fail then revert all storage changes + match T::SlashManager::slash_operator(&slash) { + Ok(weight_used) => { + weight = weight_used.checked_add(&weight).unwrap_or_else(Zero::zero); + // Remove the slash from storage after successful application + UnappliedSlashes::::remove(process_era, index); + }, + Err(_) => { + log::error!("Failed to apply slash for index: {:?}", index); + }, + } + } + weight } } @@ -223,6 +259,8 @@ pub mod module { BlueprintCreationInterrupted, /// The caller is already registered as a operator. AlreadyRegistered, + /// The caller is registering with a key that is already registered + DuplicateKey, /// The caller does not have the requirements to be a operator. InvalidRegistrationInput, /// The Operator is not allowed to unregister. @@ -275,14 +313,28 @@ pub mod module { EVMAbiDecode, /// Operator profile not found. OperatorProfileNotFound, - /// Maximum number of services per Provider reached. - MaxServicesPerProviderExceeded, + /// Maximum number of services per operator reached. + MaxServicesPerOperatorExceeded, + /// Maximum number of blueprints registered by the operator reached. + MaxBlueprintsPerOperatorExceeded, /// The operator is not active, ensure operator status is ACTIVE in multi-asset-delegation OperatorNotActive, + /// Duplicate operator registration. + DuplicateOperator, + /// Too many operators provided for the service's membership model + TooManyOperators, + /// Too few operators provided for the service's membership model + TooFewOperators, /// No assets provided for the service, at least one asset is required. NoAssetsProvided, + /// Duplicate assets provided + DuplicateAsset, /// The maximum number of assets per service has been exceeded. MaxAssetsPerServiceExceeded, + /// Native asset exposure is too low + NativeAssetExposureTooLow, + /// Native asset is not found + NoNativeAsset, /// Offender is not a registered operator. OffenderNotOperator, /// Offender is not an active operator. @@ -295,6 +347,8 @@ pub mod module { UnappliedSlashNotFound, /// The Supplied Master Blueprint Service Manager Revision is not found. MasterBlueprintServiceManagerRevisionNotFound, + /// Duplicate membership model + DuplicateMembershipModel, /// Maximum number of Master Blueprint Service Manager revisions reached. MaxMasterBlueprintServiceManagerVersionsExceeded, /// The ERC20 transfer failed. @@ -305,6 +359,46 @@ pub mod module { ExpectedEVMAddress, /// Expected the account to be an account ID. ExpectedAccountId, + /// Request hook failure + OnRequestFailure, + /// Register hook failure + OnRegisterHookFailed, + /// Approve service request hook failure + OnApproveFailure, + /// Reject service request hook failure + OnRejectFailure, + /// Service init hook + OnServiceInitHook, + /// Membership model not supported by blueprint + UnsupportedMembershipModel, + /// Service does not support dynamic membership + DynamicMembershipNotSupported, + /// Cannot join service - rejected by blueprint + JoinRejected, + /// Cannot leave service - rejected by blueprint + LeaveRejected, + /// Maximum operators reached + MaxOperatorsReached, + /// Can join hook failure + OnCanJoinFailure, + /// Can leave hook failure + OnCanLeaveFailure, + /// Operator join hook failure + OnOperatorJoinFailure, + /// Operator leave hook failure + OnOperatorLeaveFailure, + /// Operator is a member or has already joined the service + AlreadyJoined, + /// Caller is not an operator of the service + NotAnOperator, + /// Invalid slash percentage + InvalidSlashPercentage, + /// Invalid key (zero byte ECDSA key provided) + InvalidKey, + /// Invalid security commitments + InvalidSecurityCommitments, + /// Invalid Security Requirements + InvalidSecurityRequirements, } #[pallet::event] @@ -363,10 +457,11 @@ pub mod module { blueprint_id: u64, /// The list of operators that need to approve the service. pending_approvals: Vec, - /// The list of operators that atomaticaly approved the service. + /// The list of operators that automatically approved the service. approved: Vec, - /// The list of asset IDs that are being used to secure the service. - assets: Vec, + /// The list of asset security requirements for the service. + security_requirements: + BoundedVec, MaxAssetsPerServiceOf>, }, /// A service request has been approved. ServiceRequestApproved { @@ -400,8 +495,9 @@ pub mod module { service_id: u64, /// The ID of the service blueprint. blueprint_id: u64, - /// The list of asset IDs that are being used to secure the service. - assets: Vec, + /// The list of assets that are being used to secure the service. + operator_security_commitments: + OperatorSecurityCommitments, }, /// A service has been terminated. @@ -449,12 +545,12 @@ pub mod module { index: u32, /// The account that has an unapplied slash. operator: T::AccountId, - /// The amount of the slash. - amount: BalanceOf, /// Service ID service_id: u64, /// Blueprint ID blueprint_id: u64, + /// Slash percent + slash_percent: Percent, /// Era index era: u32, }, @@ -464,16 +560,15 @@ pub mod module { index: u32, /// The account that has an unapplied slash. operator: T::AccountId, - /// The amount of the slash. - amount: BalanceOf, /// Service ID service_id: u64, /// Blueprint ID blueprint_id: u64, + /// Slash percent + slash_percent: Percent, /// Era index era: u32, }, - /// The Master Blueprint Service Manager has been revised. MasterBlueprintServiceManagerRevised { /// The revision number of the Master Blueprint Service Manager. @@ -496,17 +591,17 @@ pub mod module { /// The next free ID for a service request. #[pallet::storage] #[pallet::getter(fn next_service_request_id)] - pub type NextServiceRequestId = StorageValue<_, u64, ValueQuery>; + pub type NextServiceRequestId = StorageValue<_, ServiceRequestId, ValueQuery>; /// The next free ID for a service Instance. #[pallet::storage] #[pallet::getter(fn next_instance_id)] - pub type NextInstanceId = StorageValue<_, u64, ValueQuery>; + pub type NextInstanceId = StorageValue<_, InstanceId, ValueQuery>; /// The next free ID for a service call. #[pallet::storage] #[pallet::getter(fn next_job_call_id)] - pub type NextJobCallId = StorageValue<_, u64, ValueQuery>; + pub type NextJobCallId = StorageValue<_, JobCallId, ValueQuery>; /// The next free ID for a unapplied slash. #[pallet::storage] @@ -524,6 +619,20 @@ pub mod module { ResultQuery::BlueprintNotFound>, >; + /// The services for a particular blueprint and their active status. + /// Blueprint ID -> Service ID -> active + #[pallet::storage] + #[pallet::getter(fn service_status)] + pub type ServiceStatus = StorageDoubleMap< + _, + Identity, + BlueprintId, + Identity, + InstanceId, + (), + ResultQuery::ServiceNotFound>, + >; + /// The operators for a specific service blueprint. /// Blueprint ID -> Operator -> Operator Preferences #[pallet::storage] @@ -614,7 +723,7 @@ pub mod module { u32, Identity, u32, - UnappliedSlash>, + UnappliedSlash, ResultQuery::UnappliedSlashNotFound>, >; @@ -683,6 +792,12 @@ pub mod module { ) -> DispatchResultWithPostInfo { let owner = ensure_signed(origin)?; let blueprint_id = Self::next_blueprint_id(); + // Ensure membership models are unique + let models: Vec<_> = blueprint.supported_membership_models.iter().collect(); + ensure!( + models.iter().all(|x| models.iter().filter(|y| x == *y).count() == 1), + Error::::DuplicateMembershipModel + ); // Ensure the master blueprint service manager exists and if it uses // latest, pin it to the latest revision. match blueprint.master_manager_revision { @@ -702,7 +817,6 @@ pub mod module { let (allowed, _weight) = Self::on_blueprint_created_hook(&blueprint, blueprint_id, &owner)?; - ensure!(allowed, Error::::BlueprintCreationInterrupted); Blueprints::::insert(blueprint_id, (owner.clone(), blueprint)); @@ -788,77 +902,40 @@ pub mod module { #[pallet::weight(T::WeightInfo::register())] pub fn register( origin: OriginFor, - #[pallet::compact] blueprint_id: u64, + #[pallet::compact] blueprint_id: BlueprintId, preferences: OperatorPreferences, registration_args: Vec>, #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let caller = ensure_signed(origin)?; - let (_, blueprint) = Self::blueprints(blueprint_id)?; - + // Validate the operator preferences + ensure!(preferences.key != [0u8; 65], Error::::InvalidKey); + // Check if the caller is an active operator in the delegation system ensure!( T::OperatorDelegationManager::is_operator_active(&caller), Error::::OperatorNotActive ); + // Check if operator is already registered for this blueprint + ensure!( + !Operators::::contains_key(blueprint_id, &caller), + Error::::AlreadyRegistered + ); + // Check if the key is already in use + for (_, prefs) in Operators::::iter_prefix(blueprint_id) { + if prefs.key == preferences.key { + return Err(Error::::DuplicateKey.into()); + } + } - let already_registered = Operators::::contains_key(blueprint_id, &caller); - ensure!(!already_registered, Error::::AlreadyRegistered); - blueprint - .type_check_registration(®istration_args) - .map_err(Error::::TypeCheck)?; - - // Transfer the registration value to the pallet - T::Currency::transfer( - &caller, - &Self::account_id(), - value, - ExistenceRequirement::KeepAlive, - )?; - - let (allowed, _weight) = Self::on_register_hook( - &blueprint, - blueprint_id, - &preferences, - ®istration_args, - value, - )?; - - ensure!(allowed, Error::::InvalidRegistrationInput); - - Operators::::insert(blueprint_id, &caller, preferences); - - OperatorsProfile::::try_mutate(&caller, |profile| { - match profile { - Ok(p) => { - p.blueprints - .try_insert(blueprint_id) - .map_err(|_| Error::::MaxServicesPerProviderExceeded)?; - }, - Err(_) => { - let mut blueprints = BoundedBTreeSet::new(); - blueprints - .try_insert(blueprint_id) - .map_err(|_| Error::::MaxServicesPerProviderExceeded)?; - *profile = Ok(OperatorProfile { blueprints, ..Default::default() }); - }, - }; - Result::<_, Error>::Ok(()) - })?; - - Self::deposit_event(Event::Registered { - provider: caller.clone(), - blueprint_id, - preferences, - registration_args, - }); - + Self::do_register(&caller, blueprint_id, preferences, registration_args, value)?; Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes }) } /// Unregisters a service provider from a specific service blueprint. /// - /// After unregistering, the provider will no longer receive new service assignments for this blueprint. - /// However, they must continue servicing any active assignments until completion to avoid penalties. + /// Can only be called if the no services are active for the blueprint. + /// After unregistering, the provider will no longer receive new service + /// assignments for this blueprint. /// /// # Arguments /// @@ -882,6 +959,14 @@ pub mod module { let caller = ensure_signed(origin)?; let (_, blueprint) = Self::blueprints(blueprint_id)?; let preferences = Operators::::get(blueprint_id, &caller)?; + + // Check for active services for this operator + for (service_id, _) in ServiceStatus::::iter_prefix(blueprint_id) { + ensure!( + !ServiceStatus::::contains_key(blueprint_id, service_id), + Error::::NotAllowedToUnregister + ); + } let (allowed, _weight) = Self::on_unregister_hook(&blueprint, blueprint_id, &preferences)?; ensure!(allowed, Error::::NotAllowedToUnregister); @@ -988,132 +1073,77 @@ pub mod module { permitted_callers: Vec, operators: Vec, request_args: Vec>, - assets: Vec, + asset_security_requirements: Vec>, #[pallet::compact] ttl: BlockNumberFor, payment_asset: Asset, #[pallet::compact] value: BalanceOf, + membership_model: MembershipModel, ) -> DispatchResultWithPostInfo { let caller = ensure_signed(origin)?; - let (_, blueprint) = Self::blueprints(blueprint_id)?; - - blueprint.type_check_request(&request_args).map_err(Error::::TypeCheck)?; - // ensure we at least have one asset - ensure!(!assets.is_empty(), Error::::NoAssetsProvided); + // Ensure all operators are active + for operator in operators.iter() { + ensure!( + T::OperatorDelegationManager::is_operator_active(operator), + Error::::OperatorNotActive, + ); + } - let mut preferences = Vec::new(); - let mut pending_approvals = Vec::new(); - for provider in &operators { - let prefs = Self::operators(blueprint_id, provider)?; - pending_approvals.push(provider.clone()); - preferences.push(prefs); + // Ensure each asset has non-zero exposure requirements + for requirement in asset_security_requirements.iter() { + ensure!( + requirement.min_exposure_percent > Percent::zero() + && requirement.max_exposure_percent > Percent::zero() + && requirement.min_exposure_percent <= requirement.max_exposure_percent + && requirement.max_exposure_percent <= Percent::from_percent(100), + Error::::InvalidSecurityRequirements, + ); } - let mut native_value = Zero::zero(); - let request_id = NextServiceRequestId::::get(); - - if value != Zero::zero() { - // Payment transfer - let refund_to = match payment_asset { - // Handle the case of native currency. - Asset::Custom(asset_id) if asset_id == Zero::zero() => { - T::Currency::transfer( - &caller, - &Self::account_id(), - value, - ExistenceRequirement::KeepAlive, - )?; - native_value = value; - Account::id(caller.clone()) - }, - Asset::Custom(asset_id) => { - T::Fungibles::transfer( - asset_id, - &caller, - &Self::account_id(), - value, - Preservation::Preserve, - )?; - Account::id(caller.clone()) - }, - Asset::Erc20(token) => { - // origin check. - let evm_origin = evm_origin.ok_or(Error::::MissingEVMOrigin)?; - let mapped_origin = T::EvmAddressMapping::into_account_id(evm_origin); - ensure!(mapped_origin == caller, DispatchError::BadOrigin); - let (success, _weight) = - Self::erc20_transfer(token, evm_origin, Self::address(), value)?; - ensure!(success, Error::::ERC20TransferFailed); - Account::from(evm_origin) - }, - }; + // Ensure no duplicate operators + let mut seen_operators = BTreeSet::new(); + for operator in operators.iter() { + ensure!(seen_operators.insert(operator), Error::::DuplicateOperator); + } - // Save the payment information for the service request. - let payment = StagingServicePayment { - request_id, - refund_to, - asset: payment_asset, - amount: value, - }; + let (_, blueprint) = Self::blueprints(blueprint_id)?; + let supported_membership_models = blueprint.supported_membership_models; - StagingServicePayments::::insert(request_id, payment); + // Check that the number of operators doesn't exceed the membership model max + match membership_model { + MembershipModel::Fixed { min_operators } => { + ensure!( + supported_membership_models.contains(&MembershipModelType::Fixed), + Error::::UnsupportedMembershipModel + ); + ensure!(min_operators > 0, Error::::TooFewOperators); + ensure!(operators.len() >= min_operators as usize, Error::::TooFewOperators); + }, + MembershipModel::Dynamic { min_operators, max_operators } => { + ensure!( + supported_membership_models.contains(&MembershipModelType::Dynamic), + Error::::UnsupportedMembershipModel + ); + ensure!(operators.len() >= min_operators as usize, Error::::TooFewOperators); + if let Some(max_ops) = max_operators { + ensure!(operators.len() <= max_ops as usize, Error::::TooManyOperators); + } + }, } - let (allowed, _weight) = Self::on_request_hook( - &blueprint, + Self::do_request( + caller, + evm_origin, blueprint_id, - &caller, - request_id, - &preferences, - &request_args, - &permitted_callers, - &assets, + permitted_callers, + operators, + request_args, + asset_security_requirements, ttl, payment_asset, value, - native_value, + membership_model, )?; - let permitted_callers = - BoundedVec::<_, MaxPermittedCallersOf>::try_from(permitted_callers) - .map_err(|_| Error::::MaxPermittedCallersExceeded)?; - let assets = BoundedVec::<_, MaxAssetsPerServiceOf>::try_from(assets) - .map_err(|_| Error::::MaxAssetsPerServiceExceeded)?; - let operators = pending_approvals - .iter() - .cloned() - .map(|v| (v, ApprovalState::Pending)) - .collect::>(); - - let args = BoundedVec::<_, MaxFieldsOf>::try_from(request_args) - .map_err(|_| Error::::MaxFieldsExceeded)?; - - let operators_with_approval_state = - BoundedVec::<_, MaxOperatorsPerServiceOf>::try_from(operators) - .map_err(|_| Error::::MaxServiceProvidersExceeded)?; - - let service_request = ServiceRequest { - blueprint: blueprint_id, - owner: caller.clone(), - assets: assets.clone(), - ttl, - args, - permitted_callers, - operators_with_approval_state, - }; - - ensure!(allowed, Error::::InvalidRequestInput); - ServiceRequests::::insert(request_id, service_request); - NextServiceRequestId::::set(request_id.saturating_add(1)); - - Self::deposit_event(Event::ServiceRequested { - owner: caller.clone(), - request_id, - blueprint_id, - pending_approvals, - approved: Default::default(), - assets: assets.to_vec(), - }); - Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes }) } @@ -1128,177 +1158,35 @@ pub mod module { /// /// * `origin` - The origin of the call, must be a signed account /// * `request_id` - The ID of the service request to approve - /// * `restaking_percent` - Percentage of staked tokens to expose to this service (0-100) + /// * `native_exposure_percent` - Percentage of native token stake to expose + /// * `asset_exposure` - Vector of asset-specific exposure commitments /// /// # Errors /// /// * [`Error::ApprovalNotRequested`] - Caller is not in the pending approvals list /// * [`Error::ApprovalInterrupted`] - Approval was rejected by blueprint hook + /// * [`Error::InvalidRequestInput`] - Asset exposure commitments don't meet requirements #[pallet::weight(T::WeightInfo::approve())] pub fn approve( origin: OriginFor, #[pallet::compact] request_id: u64, - #[pallet::compact] restaking_percent: Percent, + security_commitments: Vec>, ) -> DispatchResultWithPostInfo { let caller = ensure_signed(origin)?; - let mut request = Self::service_requests(request_id)?; - let updated = request - .operators_with_approval_state - .iter_mut() - .find(|(v, _)| v == &caller) - .map(|(_, s)| *s = ApprovalState::Approved { restaking_percent }); - ensure!(updated.is_some(), Error::::ApprovalNotRequested); - - let blueprint_id = request.blueprint; - let (_, blueprint) = Self::blueprints(blueprint_id)?; - let preferences = Operators::::get(blueprint_id, caller.clone())?; - let approved = request - .operators_with_approval_state - .iter() - .filter_map(|(v, s)| { - if matches!(*s, ApprovalState::Approved { .. }) { - Some(v.clone()) - } else { - None - } - }) - .collect::>(); - let pending_approvals = request - .operators_with_approval_state - .iter() - .filter_map( - |(v, s)| if *s == ApprovalState::Pending { Some(v.clone()) } else { None }, - ) - .collect::>(); - - let (allowed, _weight) = Self::on_approve_hook( - &blueprint, - blueprint_id, - &preferences, - request_id, - restaking_percent.deconstruct(), - )?; - - ensure!(allowed, Error::::ApprovalInterrupted); - // we emit this event regardless of the outcome of the approval. - Self::deposit_event(Event::ServiceRequestApproved { - operator: caller.clone(), - request_id, - blueprint_id: request.blueprint, - pending_approvals, - approved, - }); - - if request.is_approved() { - // remove the service request. - ServiceRequests::::remove(request_id); - - let service_id = Self::next_instance_id(); - let operators = request - .operators_with_approval_state - .into_iter() - .filter_map(|(v, state)| match state { - ApprovalState::Approved { restaking_percent } => { - Some((v, restaking_percent)) - }, - // N.B: this should not happen, as all operators are approved and checked - // above. - _ => None, - }) - .collect::>(); - - // add the service id to the list of services for each operator's profile. - for (operator, _) in &operators { - OperatorsProfile::::try_mutate_exists(operator, |profile| { - profile - .as_mut() - .and_then(|p| p.services.try_insert(service_id).ok()) - .ok_or(Error::::NotRegistered) - })?; - } - let operators = BoundedVec::<_, MaxOperatorsPerServiceOf>::try_from(operators) - .map_err(|_| Error::::MaxServiceProvidersExceeded)?; - let service = Service { - id: service_id, - blueprint: request.blueprint, - owner: request.owner.clone(), - assets: request.assets.clone(), - permitted_callers: request.permitted_callers.clone(), - operators, - ttl: request.ttl, - }; - - UserServices::::try_mutate(&request.owner, |service_ids| { - Instances::::insert(service_id, service); - NextInstanceId::::set(service_id.saturating_add(1)); - service_ids - .try_insert(service_id) - .map_err(|_| Error::::MaxServicesPerUserExceeded) - })?; - // Payment - if let Some(payment) = Self::service_payment(request_id) { - // send payments to the MBSM - let mbsm_address = Self::mbsm_address_of(&blueprint)?; - let mbsm_account_id = T::EvmAddressMapping::into_account_id(mbsm_address); - match payment.asset { - Asset::Custom(asset_id) if asset_id == Zero::zero() => { - T::Currency::transfer( - &Self::account_id(), - &mbsm_account_id, - payment.amount, - ExistenceRequirement::AllowDeath, - )?; - }, - Asset::Custom(asset_id) => { - T::Fungibles::transfer( - asset_id, - &Self::account_id(), - &mbsm_account_id, - payment.amount, - Preservation::Expendable, - )?; - }, - Asset::Erc20(token) => { - let (success, _weight) = Self::erc20_transfer( - token, - Self::address(), - mbsm_address, - payment.amount, - )?; - ensure!(success, Error::::ERC20TransferFailed); - }, - } - - // Remove the payment information. - StagingServicePayments::::remove(request_id); - } + // Ensure asset security commitments don't exceed max assets per service + ensure!( + security_commitments.len() <= T::MaxAssetsPerService::get() as usize, + Error::::MaxAssetsPerServiceExceeded + ); - let (allowed, _weight) = Self::on_service_init_hook( - &blueprint, - blueprint_id, - request_id, - service_id, - &request.owner, - &request.permitted_callers, - &request.assets, - request.ttl, - )?; - - ensure!(allowed, Error::::ServiceInitializationInterrupted); - - Self::deposit_event(Event::ServiceInitiated { - owner: request.owner, - request_id, - assets: request.assets.to_vec(), - service_id, - blueprint_id: request.blueprint, - }); - } else { - // Update the service request. - ServiceRequests::::insert(request_id, request); + // Ensure no duplicate assets in exposures + let mut seen_assets = sp_std::collections::btree_set::BTreeSet::new(); + for exposure in security_commitments.iter() { + ensure!(seen_assets.insert(&exposure.asset), Error::::DuplicateAsset); } + Self::do_approve(caller, request_id, &security_commitments)?; Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes }) } @@ -1328,77 +1216,7 @@ pub mod module { #[pallet::compact] request_id: u64, ) -> DispatchResultWithPostInfo { let caller = ensure_signed(origin)?; - let mut request = Self::service_requests(request_id)?; - let updated = request.operators_with_approval_state.iter_mut().find_map(|(v, s)| { - if v == &caller { - *s = ApprovalState::Rejected; - Some(()) - } else { - None - } - }); - - ensure!(updated.is_some(), Error::::ApprovalNotRequested); - - let blueprint_id = request.blueprint; - let (_, blueprint) = Self::blueprints(blueprint_id)?; - let prefs = Operators::::get(blueprint_id, caller.clone())?; - - let (allowed, _weight) = - Self::on_reject_hook(&blueprint, blueprint_id, &prefs, request_id)?; - - ensure!(allowed, Error::::RejectionInterrupted); - Self::deposit_event(Event::ServiceRequestRejected { - operator: caller, - blueprint_id: request.blueprint, - request_id, - }); - - // Refund the payment - if let Some(payment) = Self::service_payment(request_id) { - match payment.asset { - Asset::Custom(asset_id) if asset_id == Zero::zero() => { - let refund_to = payment - .refund_to - .try_into_account_id() - .map_err(|_| Error::::ExpectedAccountId)?; - T::Currency::transfer( - &Self::account_id(), - &refund_to, - payment.amount, - ExistenceRequirement::AllowDeath, - )?; - }, - Asset::Custom(asset_id) => { - let refund_to = payment - .refund_to - .try_into_account_id() - .map_err(|_| Error::::ExpectedAccountId)?; - T::Fungibles::transfer( - asset_id, - &Self::account_id(), - &refund_to, - payment.amount, - Preservation::Expendable, - )?; - }, - Asset::Erc20(token) => { - let refund_to = payment - .refund_to - .try_into_address() - .map_err(|_| Error::::ExpectedEVMAddress)?; - let (success, _weight) = Self::erc20_transfer( - token, - Self::address(), - refund_to, - payment.amount, - )?; - ensure!(success, Error::::ERC20TransferFailed); - }, - } - StagingServicePayments::::remove(request_id); - } - + Self::do_reject(caller, request_id)?; Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes }) } @@ -1427,6 +1245,28 @@ pub mod module { let caller = ensure_signed(origin)?; let service = Self::services(service_id)?; ensure!(service.owner == caller, DispatchError::BadOrigin); + + // Apply any unapplied slashes for this service before termination + let current_era = T::OperatorDelegationManager::get_current_round(); + let last_era = current_era.saturating_sub(1); + + // Get slashes from current and last era + let current_slashes: Vec<_> = UnappliedSlashes::::iter_prefix(current_era) + .filter(|(_, slash)| slash.service_id == service_id) + .collect(); + let last_slashes: Vec<_> = UnappliedSlashes::::iter_prefix(last_era) + .filter(|(_, slash)| slash.service_id == service_id) + .collect(); + + // Apply all slashes + for (_, slash) in current_slashes.into_iter().chain(last_slashes) { + T::SlashManager::slash_operator(&slash)?; + } + + // Clean up storage + let _ = UnappliedSlashes::::clear_prefix(current_era, u32::MAX, None); + let _ = UnappliedSlashes::::clear_prefix(last_era, u32::MAX, None); + let removed = UserServices::::try_mutate(&caller, |service_ids| { Result::<_, Error>::Ok(service_ids.remove(&service_id)) })?; @@ -1443,7 +1283,7 @@ pub mod module { ensure!(allowed, Error::::TerminationInterrupted); // Remove the service from the operator's profile. - for (operator, _) in &service.operators { + for (operator, _) in &service.operator_security_commitments { OperatorsProfile::::try_mutate_exists(operator, |profile| { profile .as_mut() @@ -1452,6 +1292,7 @@ pub mod module { })?; } + ServiceStatus::::remove(blueprint_id, service_id); Self::deposit_event(Event::ServiceTerminated { owner: caller.clone(), service_id, @@ -1555,9 +1396,6 @@ pub mod module { let service = Self::services(job_call.service_id)?; let blueprint_id = service.blueprint; let (_, blueprint) = Self::blueprints(blueprint_id)?; - - let is_operator = service.operators.iter().any(|(v, _)| v == &caller); - ensure!(is_operator, DispatchError::BadOrigin); let operator_preferences = Operators::::get(blueprint_id, &caller)?; let job_def = blueprint @@ -1612,7 +1450,7 @@ pub mod module { /// * `origin` - The origin of the call. Must be signed by an authorized Slash Origin. /// * `offender` - The account ID of the operator to be slashed. /// * `service_id` - The ID of the service for which to slash the operator. - /// * `percent` - The percentage of the operator's exposed stake to slash, as a `Percent` value. + /// * `slash_percent` - The percentage of the operator's exposed stake to slash, as a `Percent` value. /// /// # Errors /// @@ -1624,7 +1462,7 @@ pub mod module { origin: OriginFor, offender: T::AccountId, #[pallet::compact] service_id: u64, - #[pallet::compact] percent: Percent, + #[pallet::compact] slash_percent: Percent, ) -> DispatchResultWithPostInfo { let caller = ensure_signed(origin)?; let service = Self::services(service_id)?; @@ -1632,48 +1470,42 @@ pub mod module { let slashing_origin = maybe_slashing_origin.ok_or(Error::::NoSlashingOrigin)?; ensure!(slashing_origin == caller, DispatchError::BadOrigin); - let (operator, restake_percent) = - match service.operators.iter().find(|(operator, _)| operator == &offender) { - Some((operator, restake_percent)) => (operator, restake_percent), - None => return Err(Error::::OffenderNotOperator.into()), - }; - let operator_is_active = T::OperatorDelegationManager::is_operator_active(&offender); - ensure!(operator_is_active, Error::::OffenderNotActiveOperator); - - let total_own_stake = T::OperatorDelegationManager::get_operator_stake(operator); - // Only take the exposed restake percentage for this service. - let own_stake = restake_percent.mul_floor(total_own_stake); - let delegators = T::OperatorDelegationManager::get_delegators_for_operator(operator); - let exposed_stake = percent.mul_floor(own_stake); - let others_slash = delegators - .into_iter() - .map(|(delegator, stake, _asset_id)| (delegator, percent.mul_floor(stake))) - .collect::>(); - let total_slash = - others_slash.iter().fold(exposed_stake, |acc, (_, slash)| acc + *slash); - - // for now, we treat all assets equally, which is not the case in reality. + // Ensure slash percent is greater than 0 + ensure!(!slash_percent.is_zero(), Error::::InvalidSlashPercentage); + + // Verify offender is an operator for this service + ensure!( + service.operator_security_commitments.iter().any(|(op, _)| op == &offender), + Error::::OffenderNotOperator + ); + + // Verify operator is active in delegation system + ensure!( + T::OperatorDelegationManager::is_operator_active(&offender), + Error::::OffenderNotActiveOperator + ); + + // Calculate the slash amounts for operator and delegators let unapplied_slash = UnappliedSlash { - service_id, + era: T::OperatorDelegationManager::get_current_round(), + blueprint_id: service.blueprint, + service_id: service.id, operator: offender.clone(), - own: exposed_stake, - others: others_slash, - reporters: Vec::from([caller]), - payout: total_slash, + slash_percent, }; + // Store the slash for later processing let index = Self::next_unapplied_slash_index(); - let era = T::OperatorDelegationManager::get_current_round(); - UnappliedSlashes::::insert(era, index, unapplied_slash); + UnappliedSlashes::::insert(unapplied_slash.era, index, unapplied_slash.clone()); NextUnappliedSlashIndex::::set(index.saturating_add(1)); Self::deposit_event(Event::::UnappliedSlash { index, - operator: offender.clone(), + operator: offender, blueprint_id: service.blueprint, service_id, - amount: total_slash, - era, + slash_percent, + era: unapplied_slash.era, }); Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes }) @@ -1718,7 +1550,7 @@ pub mod module { operator: unapplied_slash.operator, blueprint_id: service.blueprint, service_id: unapplied_slash.service_id, - amount: unapplied_slash.payout, + slash_percent: unapplied_slash.slash_percent, era, }); @@ -1756,5 +1588,64 @@ pub mod module { Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes }) } + + /// Join a service instance as an operator + #[pallet::call_index(15)] + #[pallet::weight(10_000)] + pub fn join_service( + origin: OriginFor, + instance_id: u64, + security_commitments: Vec>, + ) -> DispatchResult { + let operator = ensure_signed(origin)?; + + // Get service instance + let instance = Instances::::get(instance_id)?; + + // Check if operator is already in the set + ensure!( + !instance.operator_security_commitments.iter().any(|(op, _)| op == &operator), + Error::::AlreadyJoined + ); + + ensure!( + instance.validate_security_commitments(&security_commitments), + Error::::InvalidSecurityCommitments + ); + + let (_, blueprint) = Self::blueprints(instance.blueprint)?; + let preferences = Self::operators(instance.blueprint, operator.clone())?; + + // Call membership implementation + Self::do_join_service( + &blueprint, + instance.blueprint, + instance_id, + &operator, + &preferences, + security_commitments, + )?; + + Ok(()) + } + + /// Leave a service instance as an operator + #[pallet::call_index(16)] + #[pallet::weight(10_000)] + pub fn leave_service(origin: OriginFor, instance_id: u64) -> DispatchResult { + let operator = ensure_signed(origin)?; + + // Get service instance + let instance = Instances::::get(instance_id)?; + + // Get blueprint + let (_, blueprint) = Self::blueprints(instance.blueprint)?; + let _ = Self::operators(instance.blueprint, operator.clone())?; + + // Call membership implementation + Self::do_leave_service(&blueprint, instance.blueprint, instance_id, &operator)?; + + Ok(()) + } } } diff --git a/pallets/services/src/mock.rs b/pallets/services/src/mock.rs index 7ba34ad80..311d3d275 100644 --- a/pallets/services/src/mock.rs +++ b/pallets/services/src/mock.rs @@ -14,8 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . #![allow(clippy::all)] -use super::*; +use crate::mock_evm::MockedEvmRunner; use crate::{self as pallet_services}; +use core::ops::Mul; use ethabi::Uint; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, @@ -23,29 +24,35 @@ use frame_election_provider_support::{ }; use frame_support::{ construct_runtime, derive_impl, parameter_types, - traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, OneSessionHandler}, + traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, Hooks, OneSessionHandler}, + PalletId, }; use frame_system::EnsureRoot; -use mock_evm::MockedEvmRunner; use pallet_evm::GasWeightMapping; use pallet_session::historical as pallet_session_historical; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; use serde_json::json; -use sp_core::{sr25519, H160}; +use sp_core::{sr25519, RuntimeDebug, H160}; use sp_keystore::{testing::MemoryKeystore, KeystoreExt, KeystorePtr}; use sp_runtime::{ testing::UintAuthorityId, traits::{ConvertInto, IdentityLookup}, - AccountId32, BuildStorage, Perbill, + AccountId32, BuildStorage, DispatchError, Perbill, Percent, +}; +use sp_staking::currency_to_vote::U128CurrencyToVote; +use sp_weights::Weight; +use std::{cell::RefCell, collections::BTreeMap, sync::Arc}; +use tangle_primitives::{ + services::{Asset, EvmAddressMapping, EvmGasWeightMapping, EvmRunner}, + traits::RewardsManager, + types::{rewards::LockMultiplier, BlockNumber}, }; -use tangle_primitives::rewards::UserDepositWithLocks; -use tangle_primitives::services::{Asset, EvmAddressMapping, EvmGasWeightMapping, EvmRunner}; - -use core::ops::Mul; -use std::{collections::BTreeMap, sync::Arc}; pub type AccountId = AccountId32; pub type Balance = u128; -type Nonce = u32; +pub type Nonce = u32; +pub type AssetId = u128; #[frame_support::derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Runtime { @@ -80,14 +87,14 @@ impl pallet_balances::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ConstU128<1>; type AccountStore = System; - type MaxLocks = (); + type MaxLocks = ConstU32<50>; type MaxReserves = ConstU32<50>; type ReserveIdentifier = (); type WeightInfo = (); type RuntimeHoldReason = RuntimeHoldReason; - type RuntimeFreezeReason = (); - type FreezeIdentifier = (); - type MaxFreezes = (); + type RuntimeFreezeReason = RuntimeHoldReason; + type FreezeIdentifier = [u8; 8]; + type MaxFreezes = ConstU32<50>; } parameter_types! { @@ -181,7 +188,7 @@ impl pallet_staking::Config for Runtime { type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; - type CurrencyToVote = (); + type CurrencyToVote = U128CurrencyToVote; type RewardRemainder = (); type RuntimeEvent = RuntimeEvent; type Slash = (); @@ -209,7 +216,13 @@ impl pallet_staking::Config for Runtime { } parameter_types! { - pub const ServicesEVMAddress: H160 = H160([0x11; 20]); + // Ripemd160(keccak256("ServicesPalletEvmAccount")) + pub const ServicesPalletEvmAccount: H160 = H160([ + 0x09, 0xdf, 0x6a, 0x94, 0x1e, 0xe0, 0x3b, 0x1e, + 0x63, 0x29, 0x04, 0xe3, 0x82, 0xe1, 0x08, 0x62, + 0xfa, 0x9c, 0xc0, 0xe3 + ]); + pub const SlashRecipient: AccountId = AccountId32::new([9u8; 32]); } pub struct PalletEVMGasWeightMapping; @@ -233,7 +246,9 @@ impl EvmAddressMapping for PalletEVMAddressMapping { } fn into_address(account_id: AccountId) -> H160 { - H160::from_slice(&AsRef::<[u8; 32]>::as_ref(&account_id)[0..20]) + // Convert AccountId to H160 by taking first 20 bytes + let bytes: &[u8] = account_id.as_ref(); + H160::from_slice(&bytes[0..20]) } } @@ -241,7 +256,7 @@ impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = u128; type AssetId = AssetId; - type AssetIdParameter = u32; + type AssetIdParameter = u128; type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; @@ -258,66 +273,6 @@ impl pallet_assets::Config for Runtime { type RemoveItemsLimit = ConstU32<5>; } -pub type AssetId = u32; - -pub struct MockDelegationManager; -impl tangle_primitives::traits::MultiAssetDelegationInfo - for MockDelegationManager -{ - type AssetId = AssetId; - - fn get_current_round() -> tangle_primitives::types::RoundIndex { - Default::default() - } - - fn is_operator(_operator: &AccountId) -> bool { - // don't care - true - } - - fn is_operator_active(operator: &AccountId) -> bool { - if operator == &mock_pub_key(10) { - return false; - } - true - } - - fn get_operator_stake(operator: &AccountId) -> Balance { - if operator == &mock_pub_key(10) { - Default::default() - } else { - 1000 - } - } - - fn get_total_delegation_by_asset_id( - _operator: &AccountId, - _asset_id: &Asset, - ) -> Balance { - Default::default() - } - - fn get_delegators_for_operator( - _operator: &AccountId, - ) -> Vec<(AccountId, Balance, Asset)> { - Default::default() - } - - fn slash_operator( - _operator: &AccountId, - _blueprint_id: tangle_primitives::BlueprintId, - _percentage: sp_runtime::Percent, - ) { - } - - fn get_user_deposit_with_locks( - _who: &AccountId, - _asset_id: Asset, - ) -> Option> { - None - } -} - parameter_types! { #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] @@ -406,14 +361,19 @@ parameter_types! { #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub const MaxMasterBlueprintServiceManagerRevisions: u32 = u32::MAX; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MinimumNativeSecurityRequirement: Percent = Percent::from_percent(10); } -impl Config for Runtime { +impl pallet_services::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ForceOrigin = frame_system::EnsureRoot; type Currency = Balances; type Fungibles = Assets; - type PalletEVMAddress = ServicesEVMAddress; + type PalletEvmAccount = ServicesPalletEvmAccount; + type SlashManager = (); type AssetId = AssetId; type EvmRunner = MockedEvmRunner; type EvmGasWeightMapping = PalletEVMGasWeightMapping; @@ -439,8 +399,9 @@ impl Config for Runtime { type MaxContainerImageTagLength = MaxContainerImageTagLength; type MaxAssetsPerService = MaxAssetsPerService; type MaxMasterBlueprintServiceManagerVersions = MaxMasterBlueprintServiceManagerRevisions; + type MinimumNativeSecurityRequirement = MinimumNativeSecurityRequirement; type Constraints = pallet_services::types::ConstraintsOf; - type OperatorDelegationManager = MockDelegationManager; + type OperatorDelegationManager = MultiAssetDelegation; type SlashDeferDuration = SlashDeferDuration; type MasterBlueprintServiceManagerUpdateOrigin = EnsureRoot; type WeightInfo = (); @@ -448,6 +409,133 @@ impl Config for Runtime { type Block = frame_system::mocking::MockBlock; +thread_local! { + static DEPOSIT_CALLS: RefCell, Balance, Option)>> = RefCell::new(Vec::new()); + static WITHDRAWAL_CALLS: RefCell, Balance)>> = RefCell::new(Vec::new()); +} + +pub struct MockRewardsManager; + +impl RewardsManager for MockRewardsManager { + type Error = DispatchError; + + fn record_deposit( + account_id: &AccountId, + asset: Asset, + amount: Balance, + lock_multiplier: Option, + ) -> Result<(), Self::Error> { + DEPOSIT_CALLS.with(|calls| { + calls.borrow_mut().push((account_id.clone(), asset, amount, lock_multiplier)); + }); + Ok(()) + } + + fn record_withdrawal( + account_id: &AccountId, + asset: Asset, + amount: Balance, + ) -> Result<(), Self::Error> { + WITHDRAWAL_CALLS.with(|calls| { + calls.borrow_mut().push((account_id.clone(), asset, amount)); + }); + Ok(()) + } + + fn record_service_reward( + _account_id: &AccountId, + _asset: Asset, + _amount: Balance, + ) -> Result<(), Self::Error> { + Ok(()) + } + + fn get_asset_deposit_cap_remaining(_asset: Asset) -> Result { + Ok(100_000_u32.into()) + } + + fn get_asset_incentive_cap(_asset: Asset) -> Result { + Ok(0_u32.into()) + } +} + +impl MockRewardsManager { + pub fn record_deposit_calls( + ) -> Vec<(AccountId, Asset, Balance, Option)> { + DEPOSIT_CALLS.with(|calls| calls.borrow().clone()) + } + + pub fn record_withdrawal_calls() -> Vec<(AccountId, Asset, Balance)> { + WITHDRAWAL_CALLS.with(|calls| calls.borrow().clone()) + } + + pub fn clear_all() { + DEPOSIT_CALLS.with(|calls| calls.borrow_mut().clear()); + WITHDRAWAL_CALLS.with(|calls| calls.borrow_mut().clear()); + } +} + +parameter_types! { + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MinOperatorBondAmount: Balance = 1_000; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const BondDuration: u32 = 28; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxDelegatorBlueprints: u32 = 10; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxOperatorBlueprints: u32 = 10; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxWithdrawRequests: u32 = 10; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxUnstakeRequests: u32 = 10; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MaxDelegations: u32 = 10; + pub const PID: PalletId = PalletId(*b"tngl/mad"); +} + +impl pallet_multi_asset_delegation::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type SlashRecipient = SlashRecipient; + type MinOperatorBondAmount = MinOperatorBondAmount; + type BondDuration = BondDuration; + type CurrencyToVote = U128CurrencyToVote; + type StakingInterface = Staking; + type ServiceManager = Services; + type LeaveOperatorsDelay = ConstU32<10>; + type OperatorBondLessDelay = ConstU32<1>; + type LeaveDelegatorsDelay = ConstU32<1>; + type DelegationBondLessDelay = ConstU32<5>; + type MinDelegateAmount = ConstU128<100>; + type Fungibles = Assets; + type AssetId = AssetId; + type ForceOrigin = frame_system::EnsureRoot; + type PalletId = PID; + type MaxDelegatorBlueprints = MaxDelegatorBlueprints; + type MaxOperatorBlueprints = MaxOperatorBlueprints; + type MaxWithdrawRequests = MaxWithdrawRequests; + type MaxUnstakeRequests = MaxUnstakeRequests; + type MaxDelegations = MaxDelegations; + type EvmRunner = MockedEvmRunner; + type EvmGasWeightMapping = PalletEVMGasWeightMapping; + type EvmAddressMapping = PalletEVMAddressMapping; + type RewardsManager = MockRewardsManager; + type WeightInfo = (); +} + construct_runtime!( pub enum Runtime { @@ -461,6 +549,7 @@ construct_runtime!( Session: pallet_session, Staking: pallet_staking, Historical: pallet_session_historical, + MultiAssetDelegation: pallet_multi_asset_delegation, } ); @@ -476,6 +565,10 @@ pub fn mock_pub_key(id: u8) -> AccountId { sr25519::Public::from_raw([id; 32]).into() } +pub fn mock_pub_key_from_fixed_bytes(bytes: [u8; 32]) -> AccountId { + sr25519::Public::from_raw(bytes).into() +} + pub fn mock_address(id: u8) -> H160 { H160::from_slice(&[id; 20]) } @@ -500,7 +593,7 @@ pub fn new_test_ext(ids: Vec) -> sp_io::TestExternalities { pub const MBSM: H160 = H160([0x12; 20]); pub const CGGMP21_BLUEPRINT: H160 = H160([0x21; 20]); pub const HOOKS_TEST: H160 = H160([0x22; 20]); -pub const USDC_ERC20: H160 = H160([0x23; 20]); +pub const USDC_ERC20: H160 = H160(hex_literal::hex!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")); pub const TNT: AssetId = 0; pub const USDC: AssetId = 1; @@ -512,7 +605,12 @@ pub const WBTC: AssetId = 3; pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); // We use default for brevity, but you can configure as desired if needed. - let balances: Vec<_> = authorities.iter().map(|i| (i.clone(), 20_000_u128)).collect(); + let mut balances: Vec<_> = authorities.iter().map(|i| (i.clone(), 20_000_u128)).collect(); + // Add pallet account and MBSM account with sufficient balance + let pallet_account = Services::pallet_account(); + let mbsm_account_id = PalletEVMAddressMapping::into_account_id(MBSM); + balances.push((pallet_account, 20_000_u128)); + balances.push((mbsm_account_id, 20_000_u128)); pallet_balances::GenesisConfig:: { balances } .assimilate_storage(&mut t) .unwrap(); @@ -602,8 +700,8 @@ pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::TestE let assets_config = pallet_assets::GenesisConfig:: { assets: vec![ (USDC, authorities[0].clone(), true, 100_000), // 1 cent. - (WETH, authorities[1].clone(), true, 100), // 100 wei. - (WBTC, authorities[2].clone(), true, 100), // 100 satoshi. + (WETH, authorities[1].clone(), true, 100_000), // 100 wei. + (WBTC, authorities[2].clone(), true, 100_000), // 100 satoshi. ], metadata: vec![ (USDC, Vec::from(b"USD Coin"), Vec::from(b"USDC"), 6), @@ -636,7 +734,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::TestE >::on_initialize(1); let call = ::EvmRunner::call( - Services::address(), + Services::pallet_evm_account(), USDC_ERC20, serde_json::from_value::(json!({ "name": "initialize", @@ -677,7 +775,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::TestE // Mint for i in 1..=authorities.len() { let call = ::EvmRunner::call( - Services::address(), + Services::pallet_evm_account(), USDC_ERC20, serde_json::from_value::(json!({ "name": "mint", @@ -766,29 +864,3 @@ fn assert_evm_events_contains(expected: Vec) { panic!("No events found"); } } - -// Checks events against the latest. A contiguous set of events must be -// provided. They must include the most recent RuntimeEvent, but do not have to include -// every past RuntimeEvent. -#[track_caller] -pub fn assert_events(mut expected: Vec) { - let mut actual: Vec = System::events() - .iter() - .filter_map(|e| match e.event { - RuntimeEvent::Services(_) => Some(e.event.clone()), - _ => None, - }) - .collect(); - - expected.reverse(); - for evt in expected { - let next = actual.pop().expect("RuntimeEvent expected"); - match (&next, &evt) { - (left_val, right_val) => { - if !(*left_val == *right_val) { - panic!("Events don't match\nactual: {actual:#?}\nexpected: {evt:#?}"); - } - }, - }; - } -} diff --git a/pallets/services/src/mock_evm.rs b/pallets/services/src/mock_evm.rs index c4600040d..393e6e282 100644 --- a/pallets/services/src/mock_evm.rs +++ b/pallets/services/src/mock_evm.rs @@ -16,7 +16,7 @@ #![allow(clippy::all)] use crate as pallet_services; use crate::mock::{ - AccountId, Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Timestamp, + AccountId, AssetId, Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Timestamp, }; use fp_evm::FeeCalculator; use frame_support::{ @@ -36,19 +36,55 @@ use sp_runtime::{ ConsensusEngineId, }; +use pallet_evm_precompile_balances_erc20::{Erc20BalancesPrecompile, Erc20Metadata}; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; use pallet_evm_precompile_modexp::Modexp; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; +use pallet_evm_precompileset_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; use precompile_utils::precompile_set::{ AcceptDelegateCall, AddressU64, CallableByContract, CallableByPrecompile, PrecompileAt, - PrecompileSetBuilder, PrecompilesInRangeInclusive, + PrecompileSetBuilder, PrecompileSetStartingWith, PrecompilesInRangeInclusive, }; type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, CallableByPrecompile); +pub struct NativeErc20Metadata; + +/// ERC20 metadata for the native token. +impl Erc20Metadata for NativeErc20Metadata { + /// Returns the name of the token. + fn name() -> &'static str { + "Tangle Testnet Network Token" + } + + /// Returns the symbol of the token. + fn symbol() -> &'static str { + "tTNT" + } + + /// Returns the decimals places of the token. + fn decimals() -> u8 { + 18 + } + + /// Must return `true` only if it represents the main native currency of + /// the network. It must be the currency used in `pallet_evm`. + fn is_native_currency() -> bool { + true + } +} + +/// The asset precompile address prefix. Addresses that match against this prefix will be routed +/// to Erc20AssetsPrecompileSet being marked as foreign +pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; + +parameter_types! { + pub ForeignAssetPrefix: &'static [u8] = ASSET_PRECOMPILE_ADDRESS_PREFIX; +} + #[precompile_utils::precompile_name_from_address] pub type DefaultPrecompiles = ( // Ethereum precompiles: @@ -67,7 +103,20 @@ pub type DefaultPrecompiles = ( pub type TanglePrecompiles = PrecompileSetBuilder< R, - (PrecompilesInRangeInclusive<(AddressU64<1>, AddressU64<2095>), DefaultPrecompiles>,), + ( + PrecompilesInRangeInclusive<(AddressU64<1>, AddressU64<2095>), DefaultPrecompiles>, + PrecompileAt< + AddressU64<2050>, + Erc20BalancesPrecompile, + (CallableByContract, CallableByPrecompile), + >, + // Prefixed precompile sets (XC20) + PrecompileSetStartingWith< + ForeignAssetPrefix, + Erc20AssetsPrecompileSet, + CallableByContract, + >, + ), >; parameter_types! { @@ -83,6 +132,28 @@ impl pallet_timestamp::Config for Runtime { type WeightInfo = (); } +const ASSET_ID_SIZE: usize = core::mem::size_of::(); + +impl AddressToAssetId for Runtime { + fn address_to_asset_id(address: H160) -> Option { + let mut data = [0u8; ASSET_ID_SIZE]; + let address_bytes: [u8; 20] = address.into(); + if ASSET_PRECOMPILE_ADDRESS_PREFIX.eq(&address_bytes[0..4]) { + data.copy_from_slice(&address_bytes[4..ASSET_ID_SIZE + 4]); + Some(AssetId::from_be_bytes(data)) + } else { + None + } + } + + fn asset_id_to_address(asset_id: AssetId) -> H160 { + let mut data = [0u8; 20]; + data[0..4].copy_from_slice(ASSET_PRECOMPILE_ADDRESS_PREFIX); + data[4..ASSET_ID_SIZE + 4].copy_from_slice(&asset_id.to_be_bytes()); + H160::from(data) + } +} + pub struct FixedGasPrice; impl FeeCalculator for FixedGasPrice { fn min_gas_price() -> (U256, Weight) { @@ -160,7 +231,7 @@ impl OnChargeEVMTransaction for CustomEVMCurrencyAdapter { who: &H160, fee: U256, ) -> Result> { - let pallet_services_address = pallet_services::Pallet::::address(); + let pallet_services_address = pallet_services::Pallet::::pallet_evm_account(); // Make pallet services account free to use if who == &pallet_services_address { return Ok(None); @@ -177,7 +248,7 @@ impl OnChargeEVMTransaction for CustomEVMCurrencyAdapter { base_fee: U256, already_withdrawn: Self::LiquidityInfo, ) -> Self::LiquidityInfo { - let pallet_services_address = pallet_services::Pallet::::address(); + let pallet_services_address = pallet_services::Pallet::::pallet_evm_account(); // Make pallet services account free to use if who == &pallet_services_address { return already_withdrawn; diff --git a/pallets/services/src/test-artifacts/CGGMP21Blueprint.hex b/pallets/services/src/test-artifacts/CGGMP21Blueprint.hex index 84bb7d1ea..f44faac4d 100644 --- a/pallets/services/src/test-artifacts/CGGMP21Blueprint.hex +++ b/pallets/services/src/test-artifacts/CGGMP21Blueprint.hex @@ -1 +1 @@ -0x6080604052600436106101355760003560e01c80638b248065116100ab578063a4d91fe91161006f578063a4d91fe914610314578063bb43abd914610332578063d7deb48214610340578063e926cbd114610182578063f840766214610360578063fe0dd3711461038057600080fd5b80638b248065146102a35780639838caa3146102b1578063987ab9db146102bf5780639d0410ee146102e6578063a24e8a90146102f957600080fd5b806337c29662116100fd57806337c2966214610204578063434698bb146102175780635d79ea291461023757806374ceeb55146101e4578063821c7be21461025b578063884673ac1461027b57600080fd5b806308179f351461013a5780630af7d743146101825780630b6535d7146101a45780630d0dd399146101c457806314b4df4c146101e4575b600080fd5b34801561014657600080fd5b506101656101553660046105e3565b506002546001600160a01b031690565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561018e57600080fd5b506101a261019d36600461065f565b61038e565b005b3480156101b057600080fd5b506101a26101bf3660046106e3565b6103da565b3480156101d057600080fd5b50600054610165906001600160a01b031681565b3480156101f057600080fd5b506101656101ff3660046105e3565b503090565b6101a261021236600461073e565b610468565b34801561022357600080fd5b506101a2610232366004610804565b6104b2565b34801561024357600080fd5b5061024d60015481565b604051908152602001610179565b34801561026757600080fd5b506101a2610276366004610841565b6104f5565b34801561028757600080fd5b5061016573111111111111111111111111111111111111111181565b6101a261023236600461088f565b6101a261019d3660046108cb565b3480156102cb57600080fd5b50731111111111111111111111111111111111111111610165565b6101a26102f4366004610941565b610539565b34801561030557600080fd5b506101a26102763660046109aa565b34801561032057600080fd5b506000546001600160a01b0316610165565b6101a26102f43660046109d4565b34801561034c57600080fd5b506101a261035b366004610a29565b61057e565b34801561036c57600080fd5b50600254610165906001600160a01b031681565b6101a2610232366004610804565b6000546001600160a01b031633146103d357600054604051630c423fcf60e01b81523360048201526001600160a01b0390911660248201526044015b60405180910390fd5b5050505050565b337311111111111111111111111111111111111111111461042a576040516359afe8af60e11b815233600482015273111111111111111111111111111111111111111160248201526044016103ca565b67ffffffffffffffff92909216600155600280546001600160a01b039283166001600160a01b03199182161790915560008054929093169116179055565b6000546001600160a01b031633146104a857600054604051630c423fcf60e01b81523360048201526001600160a01b0390911660248201526044016103ca565b5050505050505050565b6000546001600160a01b031633146104f257600054604051630c423fcf60e01b81523360048201526001600160a01b0390911660248201526044016103ca565b50565b6000546001600160a01b0316331461053557600054604051630c423fcf60e01b81523360048201526001600160a01b0390911660248201526044016103ca565b5050565b6000546001600160a01b0316331461057957600054604051630c423fcf60e01b81523360048201526001600160a01b0390911660248201526044016103ca565b505050565b6000546001600160a01b031633146105be57600054604051630c423fcf60e01b81523360048201526001600160a01b0390911660248201526044016103ca565b505050505050565b803567ffffffffffffffff811681146105de57600080fd5b919050565b6000602082840312156105f557600080fd5b6105fe826105c6565b9392505050565b60008083601f84011261061757600080fd5b50813567ffffffffffffffff81111561062f57600080fd5b60208301915083602082850101111561064757600080fd5b9250929050565b803560ff811681146105de57600080fd5b60008060008060006080868803121561067757600080fd5b610680866105c6565b9450602086013567ffffffffffffffff81111561069c57600080fd5b6106a888828901610605565b90955093506106bb90506040870161064e565b949793965091946060013592915050565b80356001600160a01b03811681146105de57600080fd5b6000806000606084860312156106f857600080fd5b610701846105c6565b925061070f602085016106cc565b915061071d604085016106cc565b90509250925092565b600060c0828403121561073857600080fd5b50919050565b60008060008060008060008060c0898b03121561075a57600080fd5b610763896105c6565b975061077160208a0161064e565b965061077f60408a016105c6565b9550606089013567ffffffffffffffff8082111561079c57600080fd5b6107a88c838d01610726565b965060808b01359150808211156107be57600080fd5b6107ca8c838d01610605565b909650945060a08b01359150808211156107e357600080fd5b506107f08b828c01610605565b999c989b5096995094979396929594505050565b60006020828403121561081657600080fd5b813567ffffffffffffffff81111561082d57600080fd5b61083984828501610726565b949350505050565b6000806040838503121561085457600080fd5b823567ffffffffffffffff81111561086b57600080fd5b61087785828601610726565b925050610886602084016105c6565b90509250929050565b6000602082840312156108a157600080fd5b813567ffffffffffffffff8111156108b857600080fd5b820161012081850312156105fe57600080fd5b6000806000806000608086880312156108e357600080fd5b6108ec866105c6565b94506108fa6020870161064e565b9350610908604087016105c6565b9250606086013567ffffffffffffffff81111561092457600080fd5b61093088828901610605565b969995985093965092949392505050565b60008060006040848603121561095657600080fd5b833567ffffffffffffffff8082111561096e57600080fd5b61097a87838801610726565b9450602086013591508082111561099057600080fd5b5061099d86828701610605565b9497909650939450505050565b600080604083850312156109bd57600080fd5b6109c6836105c6565b9150610886602084016106cc565b6000806000606084860312156109e957600080fd5b833567ffffffffffffffff811115610a0057600080fd5b610a0c86828701610726565b935050610a1b602085016105c6565b915061071d6040850161064e565b60008060008060008060a08789031215610a4257600080fd5b610a4b876105c6565b9550610a59602088016105c6565b9450610a67604088016106cc565b9350606087013567ffffffffffffffff80821115610a8457600080fd5b818901915089601f830112610a9857600080fd5b813581811115610aa757600080fd5b8a60208260051b8501011115610abc57600080fd5b602083019550809450505050610ad4608088016105c6565b9050929550929550929556fea164736f6c6343000814000a +0x6080604052600436106101ba575f3560e01c806375af2f58116100f2578063a24e8a9011610092578063d7deb48211610062578063d7deb48214610449578063e926cbd114610205578063f840766214610468578063fe0dd37114610487575f80fd5b8063a24e8a9014610405578063a4d91fe91461041f578063ada603821461030e578063bb43abd91461043b575f80fd5b80638b248065116100cd5780638b248065146103b05780639838caa3146103be578063987ab9db146103cc5780639d0410ee146103f2575f80fd5b806375af2f5814610350578063821c7be21461036f578063884673ac14610389575f80fd5b806337c296621161015d57806346c578a51161013857806346c578a51461030e5780635d79ea291461032d57806361545fdd1461035057806374ceeb5514610263575f80fd5b806337c29662146102ad5780633c3aa64c146102c0578063434698bb146102ef575f80fd5b80630d0dd399116101985780630d0dd3991461024557806314b4df4c146102635780631948fdbc14610282578063216e804214610298575f80fd5b806308179f35146101be5780630af7d743146102055780630b6535d714610226575b5f80fd5b3480156101c9575f80fd5b506101e86101d836600461086e565b506002546001600160a01b031690565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610210575f80fd5b5061022461021f3660046108db565b610495565b005b348015610231575f80fd5b50610224610240366004610958565b6104df565b348015610250575f80fd5b505f546101e8906001600160a01b031681565b34801561026e575f80fd5b506101e861027d36600461086e565b503090565b34801561028d575f80fd5b506101e8627e87d581565b3480156102a3575f80fd5b50627e87d56101e8565b6102246102bb3660046109ae565b610567565b3480156102cb575f80fd5b506102df6102da366004610a6b565b6105ab565b60405190151581526020016101fc565b3480156102fa575f80fd5b50610224610309366004610aa7565b6105bf565b348015610319575f80fd5b50610224610328366004610ae0565b6105fc565b348015610338575f80fd5b5061034260015481565b6040519081526020016101fc565b34801561035b575f80fd5b506102df61036a366004610ae0565b61063a565b34801561037a575f80fd5b50610224610328366004610b2a565b348015610394575f80fd5b506101e87309df6a941ee03b1e632904e382e10862fa9cc0e381565b610224610309366004610b74565b61022461021f366004610bb2565b3480156103d7575f80fd5b507309df6a941ee03b1e632904e382e10862fa9cc0e36101e8565b610224610400366004610c22565b61067d565b348015610410575f80fd5b50610224610328366004610c85565b34801561042a575f80fd5b505f546001600160a01b03166101e8565b610224610400366004610cad565b348015610454575f80fd5b50610224610463366004610cfd565b6106bc565b348015610473575f80fd5b506002546101e8906001600160a01b031681565b610224610309366004610aa7565b5f546001600160a01b031633146104d8575f54604051630c423fcf60e01b81526104cf9133916001600160a01b0390911690600401610dab565b60405180910390fd5b5050505050565b337309df6a941ee03b1e632904e382e10862fa9cc0e31461052b57337309df6a941ee03b1e632904e382e10862fa9cc0e36040516359afe8af60e11b81526004016104cf929190610dab565b6001600160401b0392909216600155600280546001600160a01b039283166001600160a01b0319918216179091555f8054929093169116179055565b5f546001600160a01b031633146105a1575f54604051630c423fcf60e01b81526104cf9133916001600160a01b0390911690600401610dab565b5050505050505050565b5f6105b683836106fe565b90505b92915050565b5f546001600160a01b031633146105f9575f54604051630c423fcf60e01b81526104cf9133916001600160a01b0390911690600401610dab565b50565b5f546001600160a01b03163314610636575f54604051630c423fcf60e01b81526104cf9133916001600160a01b0390911690600401610dab565b5050565b5f80546001600160a01b03163314610675575f54604051630c423fcf60e01b81526104cf9133916001600160a01b0390911690600401610dab565b505f92915050565b5f546001600160a01b031633146106b7575f54604051630c423fcf60e01b81526104cf9133916001600160a01b0390911690600401610dab565b505050565b5f546001600160a01b031633146106f6575f54604051630c423fcf60e01b81526104cf9133916001600160a01b0390911690600401610dab565b505050505050565b5f61071661071136849003840184610dc5565b610767565b15610723575060016105b9565b5f61073b61073636859003850185610dc5565b6107ab565b6001600160401b0385165f90815260036020526040902090915061075f9082610812565b9150506105b9565b5f61077182610833565b156107885750602001516001600160a01b03161590565b61079182610851565b1561079f5750602001511590565b505f919050565b919050565b5f6107b582610833565b156107c257506020015190565b6107cb82610851565b156107e357602082015163ffffffff60801b176105b9565b815160018111156107f6576107f6610e29565b6040516375458b5d60e11b81526004016104cf91815260200190565b6001600160a01b0381165f90815260018301602052604081205415156105b6565b5f60015b8251600181111561084a5761084a610e29565b1492915050565b5f80610837565b80356001600160401b03811681146107a6575f80fd5b5f6020828403121561087e575f80fd5b6105b682610858565b5f8083601f840112610897575f80fd5b5081356001600160401b038111156108ad575f80fd5b6020830191508360208285010111156108c4575f80fd5b9250929050565b803560ff811681146107a6575f80fd5b5f805f805f608086880312156108ef575f80fd5b6108f886610858565b945060208601356001600160401b03811115610912575f80fd5b61091e88828901610887565b90955093506109319050604087016108cb565b949793965091946060013592915050565b80356001600160a01b03811681146107a6575f80fd5b5f805f6060848603121561096a575f80fd5b61097384610858565b925061098160208501610942565b915061098f60408501610942565b90509250925092565b5f60c082840312156109a8575f80fd5b50919050565b5f805f805f805f8060c0898b0312156109c5575f80fd5b6109ce89610858565b97506109dc60208a016108cb565b96506109ea60408a01610858565b955060608901356001600160401b0380821115610a05575f80fd5b610a118c838d01610998565b965060808b0135915080821115610a26575f80fd5b610a328c838d01610887565b909650945060a08b0135915080821115610a4a575f80fd5b50610a578b828c01610887565b999c989b5096995094979396929594505050565b5f808284036060811215610a7d575f80fd5b610a8684610858565b92506040601f1982011215610a99575f80fd5b506020830190509250929050565b5f60208284031215610ab7575f80fd5b81356001600160401b03811115610acc575f80fd5b610ad884828501610998565b949350505050565b5f8060408385031215610af1575f80fd5b610afa83610858565b915060208301356001600160401b03811115610b14575f80fd5b610b2085828601610998565b9150509250929050565b5f8060408385031215610b3b575f80fd5b82356001600160401b03811115610b50575f80fd5b610b5c85828601610998565b925050610b6b60208401610858565b90509250929050565b5f60208284031215610b84575f80fd5b81356001600160401b03811115610b99575f80fd5b82016101208185031215610bab575f80fd5b9392505050565b5f805f805f60808688031215610bc6575f80fd5b610bcf86610858565b9450610bdd602087016108cb565b9350610beb60408701610858565b925060608601356001600160401b03811115610c05575f80fd5b610c1188828901610887565b969995985093965092949392505050565b5f805f60408486031215610c34575f80fd5b83356001600160401b0380821115610c4a575f80fd5b610c5687838801610998565b94506020860135915080821115610c6b575f80fd5b50610c7886828701610887565b9497909650939450505050565b5f8060408385031215610c96575f80fd5b610c9f83610858565b9150610b6b60208401610942565b5f805f60608486031215610cbf575f80fd5b83356001600160401b03811115610cd4575f80fd5b610ce086828701610998565b935050610cef60208501610858565b915061098f604085016108cb565b5f805f805f8060a08789031215610d12575f80fd5b610d1b87610858565b9550610d2960208801610858565b9450610d3760408801610942565b935060608701356001600160401b0380821115610d52575f80fd5b818901915089601f830112610d65575f80fd5b813581811115610d73575f80fd5b8a60208260051b8501011115610d87575f80fd5b602083019550809450505050610d9f60808801610858565b90509295509295509295565b6001600160a01b0392831681529116602082015260400190565b5f60408284031215610dd5575f80fd5b604051604081018181106001600160401b0382111715610e0357634e487b7160e01b5f52604160045260245ffd5b604052823560028110610e14575f80fd5b81526020928301359281019290925250919050565b634e487b7160e01b5f52602160045260245ffdfea164736f6c6343000814000a \ No newline at end of file diff --git a/pallets/services/src/test-artifacts/HookTestBlueprintServiceManager.hex b/pallets/services/src/test-artifacts/HookTestBlueprintServiceManager.hex index f405bb353..6b75dcc18 100644 --- a/pallets/services/src/test-artifacts/HookTestBlueprintServiceManager.hex +++ b/pallets/services/src/test-artifacts/HookTestBlueprintServiceManager.hex @@ -1 +1 @@ -0x6080604052600436106101355760003560e01c80638b248065116100ab578063a4d91fe91161006f578063a4d91fe914610323578063bb43abd914610341578063d7deb48214610354578063e926cbd114610374578063f840766214610394578063fe0dd371146103b457600080fd5b80638b248065146102a35780639838caa3146102b6578063987ab9db146102c95780639d0410ee146102f0578063a24e8a901461030357600080fd5b806337c29662116100fd57806337c2966214610204578063434698bb146102175780635d79ea291461023757806374ceeb55146101e4578063821c7be21461025b578063884673ac1461027b57600080fd5b806308179f351461013a5780630af7d743146101825780630b6535d7146101a45780630d0dd399146101c457806314b4df4c146101e4575b600080fd5b34801561014657600080fd5b50610165610155366004610965565b506002546001600160a01b031690565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561018e57600080fd5b506101a261019d3660046109e1565b6103c7565b005b3480156101b057600080fd5b506101a26101bf366004610a65565b6103f7565b3480156101d057600080fd5b50600054610165906001600160a01b031681565b3480156101f057600080fd5b506101656101ff366004610965565b503090565b6101a2610212366004610ac0565b6104b6565b34801561022357600080fd5b506101a2610232366004610b86565b610525565b34801561024357600080fd5b5061024d60015481565b604051908152602001610179565b34801561026757600080fd5b506101a2610276366004610bc3565b61058d565b34801561028757600080fd5b5061016573111111111111111111111111111111111111111181565b6101a26102b1366004610c11565b6105f6565b6101a26102c4366004610c4d565b61065e565b3480156102d557600080fd5b50731111111111111111111111111111111111111111610165565b6101a26102fe366004610cc3565b6106ca565b34801561030f57600080fd5b506101a261031e366004610d2c565b610734565b34801561032f57600080fd5b506000546001600160a01b0316610165565b6101a261034f366004610d56565b61079d565b34801561036057600080fd5b506101a261036f366004610dab565b610807565b34801561038057600080fd5b506101a261038f3660046109e1565b610874565b3480156103a057600080fd5b50600254610165906001600160a01b031681565b6101a26103c2366004610b86565b6108e0565b6040517fd38931c95654e0c6958f3e427ad7c53f3a131634d634c86732f8655a28c119eb90600090a15050505050565b337311111111111111111111111111111111111111111461044c57337311111111111111111111111111111111111111116040516359afe8af60e11b8152600401610443929190610e62565b60405180910390fd5b67ffffffffffffffff8316600155600280546001600160a01b038085166001600160a01b03199283161790925560008054928416929091169190911781556040517fcf7e69bd1c708d6d282a3b1525cd1c89aa8bf25ec133174b4a7299223ceb3c4f9190a1505050565b6000546001600160a01b031633146104f257600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517f025cf7c6b8939ff50c796fa1df561a4a8d4e0de035caa3d184d5929e47e33d3590600090a15050505050505050565b6000546001600160a01b0316331461056157600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517f82b9cd36e76e3328de8315cb87dd42c248cdb31558184543b45ffd52ba422f2b90600090a150565b6000546001600160a01b031633146105c957600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517f537b387222a90168d719f1d554df67d479e37cb59651378e6d4e4939030352a790600090a15050565b6000546001600160a01b0316331461063257600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517f3582472ca72fc6a04530f08cfa31f71919e91e34971f4743873db0dbdf02b76090600090a150565b6000546001600160a01b0316331461069a57600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517f9de6d7077ebf9fc111bfdc873d12a9d37d80b3657797fa91476f82695c942a3490600090a15050505050565b6000546001600160a01b0316331461070657600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517f9add4e5824603d31f2170069522539a6971d31dc77d08e24588e3b68ce077ee690600090a1505050565b6000546001600160a01b0316331461077057600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517fbdd650f74e0fff138cdfa7cac980a24bf480423cba6de8d8daf53042b6240a0890600090a15050565b6000546001600160a01b031633146107d957600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517fd4162ed8e9bc92ece7ef39b8f199293e403fbea78e95ce1fcbfce0153eb86bb590600090a1505050565b6000546001600160a01b0316331461084357600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517fdd03b583a4b2c88c19891a8aee92431d65f349d08a744f72297b4f25b28095a890600090a1505050505050565b6000546001600160a01b031633146108b057600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517f95bfc812251a7cb44ca7b529c8854d13c0ae581729a7d2142231a3258ecd774390600090a15050505050565b6000546001600160a01b0316331461091c57600054604051630c423fcf60e01b81526104439133916001600160a01b0390911690600401610e62565b6040517f7e8e44afe7f3a9205df3a9e5ffc0c6e96006905bc7ca73b2a55eeb177ec63ff590600090a150565b803567ffffffffffffffff8116811461096057600080fd5b919050565b60006020828403121561097757600080fd5b61098082610948565b9392505050565b60008083601f84011261099957600080fd5b50813567ffffffffffffffff8111156109b157600080fd5b6020830191508360208285010111156109c957600080fd5b9250929050565b803560ff8116811461096057600080fd5b6000806000806000608086880312156109f957600080fd5b610a0286610948565b9450602086013567ffffffffffffffff811115610a1e57600080fd5b610a2a88828901610987565b9095509350610a3d9050604087016109d0565b949793965091946060013592915050565b80356001600160a01b038116811461096057600080fd5b600080600060608486031215610a7a57600080fd5b610a8384610948565b9250610a9160208501610a4e565b9150610a9f60408501610a4e565b90509250925092565b600060c08284031215610aba57600080fd5b50919050565b60008060008060008060008060c0898b031215610adc57600080fd5b610ae589610948565b9750610af360208a016109d0565b9650610b0160408a01610948565b9550606089013567ffffffffffffffff80821115610b1e57600080fd5b610b2a8c838d01610aa8565b965060808b0135915080821115610b4057600080fd5b610b4c8c838d01610987565b909650945060a08b0135915080821115610b6557600080fd5b50610b728b828c01610987565b999c989b5096995094979396929594505050565b600060208284031215610b9857600080fd5b813567ffffffffffffffff811115610baf57600080fd5b610bbb84828501610aa8565b949350505050565b60008060408385031215610bd657600080fd5b823567ffffffffffffffff811115610bed57600080fd5b610bf985828601610aa8565b925050610c0860208401610948565b90509250929050565b600060208284031215610c2357600080fd5b813567ffffffffffffffff811115610c3a57600080fd5b8201610120818503121561098057600080fd5b600080600080600060808688031215610c6557600080fd5b610c6e86610948565b9450610c7c602087016109d0565b9350610c8a60408701610948565b9250606086013567ffffffffffffffff811115610ca657600080fd5b610cb288828901610987565b969995985093965092949392505050565b600080600060408486031215610cd857600080fd5b833567ffffffffffffffff80821115610cf057600080fd5b610cfc87838801610aa8565b94506020860135915080821115610d1257600080fd5b50610d1f86828701610987565b9497909650939450505050565b60008060408385031215610d3f57600080fd5b610d4883610948565b9150610c0860208401610a4e565b600080600060608486031215610d6b57600080fd5b833567ffffffffffffffff811115610d8257600080fd5b610d8e86828701610aa8565b935050610d9d60208501610948565b9150610a9f604085016109d0565b60008060008060008060a08789031215610dc457600080fd5b610dcd87610948565b9550610ddb60208801610948565b9450610de960408801610a4e565b9350606087013567ffffffffffffffff80821115610e0657600080fd5b818901915089601f830112610e1a57600080fd5b813581811115610e2957600080fd5b8a60208260051b8501011115610e3e57600080fd5b602083019550809450505050610e5660808801610948565b90509295509295509295565b6001600160a01b039283168152911660208201526040019056fea164736f6c6343000814000a +0x6080604052600436106101ba575f3560e01c806375af2f58116100f2578063a24e8a9011610092578063d7deb48211610062578063d7deb48214610462578063e926cbd114610481578063f8407662146104a0578063fe0dd371146104bf575f80fd5b8063a24e8a9014610414578063a4d91fe914610433578063ada603821461030e578063bb43abd91461044f575f80fd5b80638b248065116100cd5780638b248065146103b55780639838caa3146103c8578063987ab9db146103db5780639d0410ee14610401575f80fd5b806375af2f5814610350578063821c7be21461036f578063884673ac1461038e575f80fd5b806337c296621161015d57806346c578a51161013857806346c578a51461030e5780635d79ea291461032d57806361545fdd1461035057806374ceeb5514610263575f80fd5b806337c29662146102ad5780633c3aa64c146102c0578063434698bb146102ef575f80fd5b80630d0dd399116101985780630d0dd3991461024557806314b4df4c146102635780631948fdbc14610282578063216e804214610298575f80fd5b806308179f35146101be5780630af7d743146102055780630b6535d714610226575b5f80fd5b3480156101c9575f80fd5b506101e86101d8366004610c34565b506002546001600160a01b031690565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610210575f80fd5b5061022461021f366004610ca1565b6104d2565b005b348015610231575f80fd5b50610224610240366004610d1e565b610501565b348015610250575f80fd5b505f546101e8906001600160a01b031681565b34801561026e575f80fd5b506101e861027d366004610c34565b503090565b34801561028d575f80fd5b506101e8627e87d581565b3480156102a3575f80fd5b50627e87d56101e8565b6102246102bb366004610d74565b6105be565b3480156102cb575f80fd5b506102df6102da366004610e31565b61062a565b60405190151581526020016101fc565b3480156102fa575f80fd5b50610224610309366004610e6d565b61063e565b348015610319575f80fd5b50610224610328366004610ea6565b6106a3565b348015610338575f80fd5b5061034260015481565b6040519081526020016101fc565b34801561035b575f80fd5b506102df61036a366004610ea6565b6106e1565b34801561037a575f80fd5b50610224610389366004610ef0565b610724565b348015610399575f80fd5b506101e87309df6a941ee03b1e632904e382e10862fa9cc0e381565b6102246103c3366004610f3a565b61078a565b6102246103d6366004610f78565b6107ef565b3480156103e6575f80fd5b507309df6a941ee03b1e632904e382e10862fa9cc0e36101e8565b61022461040f366004610fe8565b610858565b34801561041f575f80fd5b5061022461042e36600461104b565b6108bf565b34801561043e575f80fd5b505f546001600160a01b03166101e8565b61022461045d366004611073565b610925565b34801561046d575f80fd5b5061022461047c3660046110c3565b61098c565b34801561048c575f80fd5b5061022461049b366004610ca1565b6109f6565b3480156104ab575f80fd5b506002546101e8906001600160a01b031681565b6102246104cd366004610e6d565b610a5f565b6040517fd38931c95654e0c6958f3e427ad7c53f3a131634d634c86732f8655a28c119eb905f90a15050505050565b337309df6a941ee03b1e632904e382e10862fa9cc0e31461055657337309df6a941ee03b1e632904e382e10862fa9cc0e36040516359afe8af60e11b815260040161054d929190611171565b60405180910390fd5b6001600160401b038316600155600280546001600160a01b038085166001600160a01b0319928316179092555f8054928416929091169190911781556040517fcf7e69bd1c708d6d282a3b1525cd1c89aa8bf25ec133174b4a7299223ceb3c4f9190a1505050565b5f546001600160a01b031633146105f8575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517f025cf7c6b8939ff50c796fa1df561a4a8d4e0de035caa3d184d5929e47e33d35905f90a15050505050505050565b5f6106358383610ac4565b90505b92915050565b5f546001600160a01b03163314610678575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517f82b9cd36e76e3328de8315cb87dd42c248cdb31558184543b45ffd52ba422f2b905f90a150565b5f546001600160a01b031633146106dd575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b5050565b5f80546001600160a01b0316331461071c575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b505f92915050565b5f546001600160a01b0316331461075e575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517f537b387222a90168d719f1d554df67d479e37cb59651378e6d4e4939030352a7905f90a15050565b5f546001600160a01b031633146107c4575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517f3582472ca72fc6a04530f08cfa31f71919e91e34971f4743873db0dbdf02b760905f90a150565b5f546001600160a01b03163314610829575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517f9de6d7077ebf9fc111bfdc873d12a9d37d80b3657797fa91476f82695c942a34905f90a15050505050565b5f546001600160a01b03163314610892575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517f9add4e5824603d31f2170069522539a6971d31dc77d08e24588e3b68ce077ee6905f90a1505050565b5f546001600160a01b031633146108f9575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517fbdd650f74e0fff138cdfa7cac980a24bf480423cba6de8d8daf53042b6240a08905f90a15050565b5f546001600160a01b0316331461095f575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517fd4162ed8e9bc92ece7ef39b8f199293e403fbea78e95ce1fcbfce0153eb86bb5905f90a1505050565b5f546001600160a01b031633146109c6575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517fdd03b583a4b2c88c19891a8aee92431d65f349d08a744f72297b4f25b28095a8905f90a1505050505050565b5f546001600160a01b03163314610a30575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517f95bfc812251a7cb44ca7b529c8854d13c0ae581729a7d2142231a3258ecd7743905f90a15050505050565b5f546001600160a01b03163314610a99575f54604051630c423fcf60e01b815261054d9133916001600160a01b0390911690600401611171565b6040517f7e8e44afe7f3a9205df3a9e5ffc0c6e96006905bc7ca73b2a55eeb177ec63ff5905f90a150565b5f610adc610ad73684900384018461118b565b610b2d565b15610ae957506001610638565b5f610b01610afc3685900385018561118b565b610b71565b6001600160401b0385165f908152600360205260409020909150610b259082610bd8565b915050610638565b5f610b3782610bf9565b15610b4e5750602001516001600160a01b03161590565b610b5782610c17565b15610b655750602001511590565b505f919050565b919050565b5f610b7b82610bf9565b15610b8857506020015190565b610b9182610c17565b15610ba957602082015163ffffffff60801b17610638565b81516001811115610bbc57610bbc6111ef565b6040516375458b5d60e11b815260040161054d91815260200190565b6001600160a01b0381165f9081526001830160205260408120541515610635565b5f60015b82516001811115610c1057610c106111ef565b1492915050565b5f80610bfd565b80356001600160401b0381168114610b6c575f80fd5b5f60208284031215610c44575f80fd5b61063582610c1e565b5f8083601f840112610c5d575f80fd5b5081356001600160401b03811115610c73575f80fd5b602083019150836020828501011115610c8a575f80fd5b9250929050565b803560ff81168114610b6c575f80fd5b5f805f805f60808688031215610cb5575f80fd5b610cbe86610c1e565b945060208601356001600160401b03811115610cd8575f80fd5b610ce488828901610c4d565b9095509350610cf7905060408701610c91565b949793965091946060013592915050565b80356001600160a01b0381168114610b6c575f80fd5b5f805f60608486031215610d30575f80fd5b610d3984610c1e565b9250610d4760208501610d08565b9150610d5560408501610d08565b90509250925092565b5f60c08284031215610d6e575f80fd5b50919050565b5f805f805f805f8060c0898b031215610d8b575f80fd5b610d9489610c1e565b9750610da260208a01610c91565b9650610db060408a01610c1e565b955060608901356001600160401b0380821115610dcb575f80fd5b610dd78c838d01610d5e565b965060808b0135915080821115610dec575f80fd5b610df88c838d01610c4d565b909650945060a08b0135915080821115610e10575f80fd5b50610e1d8b828c01610c4d565b999c989b5096995094979396929594505050565b5f808284036060811215610e43575f80fd5b610e4c84610c1e565b92506040601f1982011215610e5f575f80fd5b506020830190509250929050565b5f60208284031215610e7d575f80fd5b81356001600160401b03811115610e92575f80fd5b610e9e84828501610d5e565b949350505050565b5f8060408385031215610eb7575f80fd5b610ec083610c1e565b915060208301356001600160401b03811115610eda575f80fd5b610ee685828601610d5e565b9150509250929050565b5f8060408385031215610f01575f80fd5b82356001600160401b03811115610f16575f80fd5b610f2285828601610d5e565b925050610f3160208401610c1e565b90509250929050565b5f60208284031215610f4a575f80fd5b81356001600160401b03811115610f5f575f80fd5b82016101208185031215610f71575f80fd5b9392505050565b5f805f805f60808688031215610f8c575f80fd5b610f9586610c1e565b9450610fa360208701610c91565b9350610fb160408701610c1e565b925060608601356001600160401b03811115610fcb575f80fd5b610fd788828901610c4d565b969995985093965092949392505050565b5f805f60408486031215610ffa575f80fd5b83356001600160401b0380821115611010575f80fd5b61101c87838801610d5e565b94506020860135915080821115611031575f80fd5b5061103e86828701610c4d565b9497909650939450505050565b5f806040838503121561105c575f80fd5b61106583610c1e565b9150610f3160208401610d08565b5f805f60608486031215611085575f80fd5b83356001600160401b0381111561109a575f80fd5b6110a686828701610d5e565b9350506110b560208501610c1e565b9150610d5560408501610c91565b5f805f805f8060a087890312156110d8575f80fd5b6110e187610c1e565b95506110ef60208801610c1e565b94506110fd60408801610d08565b935060608701356001600160401b0380821115611118575f80fd5b818901915089601f83011261112b575f80fd5b813581811115611139575f80fd5b8a60208260051b850101111561114d575f80fd5b60208301955080945050505061116560808801610c1e565b90509295509295509295565b6001600160a01b0392831681529116602082015260400190565b5f6040828403121561119b575f80fd5b604051604081018181106001600160401b03821117156111c957634e487b7160e01b5f52604160045260245ffd5b6040528235600281106111da575f80fd5b81526020928301359281019290925250919050565b634e487b7160e01b5f52602160045260245ffdfea164736f6c6343000814000a diff --git a/pallets/services/src/test-artifacts/MasterBlueprintServiceManager.hex b/pallets/services/src/test-artifacts/MasterBlueprintServiceManager.hex index a6bc701ef..c38aedf8a 100644 --- a/pallets/services/src/test-artifacts/MasterBlueprintServiceManager.hex +++ b/pallets/services/src/test-artifacts/MasterBlueprintServiceManager.hex @@ -1 +1 @@ -0x60806040526004361061019c5760003560e01c806382a1ece4116100ec578063a6a785381161008a578063b9315d3a11610064578063b9315d3a1461047c578063c97008e01461048f578063d547741f146104af578063f9e13845146104cf57600080fd5b8063a6a7853814610443578063a8f7c5ed14610456578063b89af9041461046957600080fd5b806391d14854116100c657806391d14854146103cf578063987ab9db146103ef578063a217fddf14610410578063a4d91fe91461042557600080fd5b806382a1ece41461036d578063884673ac1461038d5780638e6f8c60146103af57600080fd5b80632f2ff15d11610159578063410fdec811610133578063410fdec81461030f5780634984ee6b1461032f5780635c975abb14610342578063727391f21461035a57600080fd5b80632f2ff15d146102af57806333db3920146102cf57806336568abe146102ef57600080fd5b806301ffc9a7146101a15780630633da77146101d65780630d0dd399146101f85780631fb27b3414610230578063248a9ca314610250578063274ef0151461028f575b600080fd5b3480156101ad57600080fd5b506101c16101bc366004611707565b6104e2565b60405190151581526020015b60405180910390f35b3480156101e257600080fd5b506101f66101f136600461176d565b610519565b005b34801561020457600080fd5b50600054610218906001600160a01b031681565b6040516001600160a01b0390911681526020016101cd565b34801561023c57600080fd5b506101f661024b3660046117cc565b610640565b34801561025c57600080fd5b5061028161026b366004611819565b6000908152600160208190526040909120015490565b6040519081526020016101cd565b34801561029b57600080fd5b506102186102aa366004611832565b610745565b3480156102bb57600080fd5b506101f66102ca366004611865565b6107d9565b3480156102db57600080fd5b506101f66102ea3660046118ee565b610805565b3480156102fb57600080fd5b506101f661030a366004611865565b610926565b34801561031b57600080fd5b506101f661032a36600461196b565b61095e565b6101f661033d3660046119c8565b610a62565b34801561034e57600080fd5b5060025460ff166101c1565b6101f6610368366004611a4d565b610b72565b34801561037957600080fd5b506101f6610388366004611ac3565b610c7d565b34801561039957600080fd5b5061021860008051602061256a83398151915281565b3480156103bb57600080fd5b506102186103ca366004611832565b610da6565b3480156103db57600080fd5b506101c16103ea366004611865565b610df5565b3480156103fb57600080fd5b5060008051602061256a833981519152610218565b34801561041c57600080fd5b50610281600081565b34801561043157600080fd5b506000546001600160a01b0316610218565b6101f66104513660046117cc565b610e20565b6101f6610464366004611b8d565b610f18565b6101f6610477366004611c65565b611040565b6101f661048a366004611cc0565b611115565b34801561049b57600080fd5b506101f66104aa3660046118ee565b61122c565b3480156104bb57600080fd5b506101f66104ca366004611865565b61133c565b6101f66104dd366004611d2e565b611362565b60006001600160e01b03198216637965db0b60e01b148061051357506301ffc9a760e01b6001600160e01b03198316145b92915050565b3360008051602061256a83398151915214610562573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b60405180910390fd5b61056a6114ad565b600061058060036001600160401b0386166114d3565b604051630a24e8a960e41b81526001600160401b03851660048201526001600160a01b0384811660248301529192509082169063a24e8a9090604401600060405180830381600087803b1580156105d657600080fd5b505af11580156105ea573d6000803e3d6000fd5b50506040516001600160a01b03851681526001600160401b038087169350871691507f5267932e6c1b678e74efba1e4d6c8b3fd7504cf0fa8eea462efac2c590a21d56906020015b60405180910390a350505050565b3360008051602061256a83398151915214610680573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b6106886114ad565b600061069e60036001600160401b0385166114d3565b60405163434698bb60e01b81529091506001600160a01b0382169063434698bb906106cd908590600401611e93565b600060405180830381600087803b1580156106e757600080fd5b505af11580156106fb573d6000803e3d6000fd5b50505050826001600160401b03167fde25c2b146db36cbc91715fdf29cb0eb556eed7678e0bf13563bf7050e8f0bbd836040516107389190611e93565b60405180910390a2505050565b60008061075c60036001600160401b0386166114d3565b6040516374ceeb5560e01b81526001600160401b03851660048201529091506001600160a01b038216906374ceeb55906024015b602060405180830381865afa1580156107ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107d19190611ea6565b949350505050565b600082815260016020819052604090912001546107f5816114e6565b6107ff83836114f3565b50505050565b3360008051602061256a83398151915214610845573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b61084d6114ad565b600061086360036001600160401b0389166114d3565b604051630af7d74360e01b81529091506001600160a01b03821690630af7d7439061089a9089908990899089908990600401611ec3565b600060405180830381600087803b1580156108b457600080fd5b505af11580156108c8573d6000803e3d6000fd5b50505050856001600160401b0316876001600160401b03167f7d2ac4c1544e07516def74ce3ada12ff791868ed07d6da82694ae2c0342c8371878787876040516109159493929190611efd565b60405180910390a350505050505050565b6001600160a01b038116331461094f5760405163334bd91960e11b815260040160405180910390fd5b610959828261156c565b505050565b3360008051602061256a8339815191521461099e573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b6109a66114ad565b60006109bc60036001600160401b0386166114d3565b60405163410e3df160e11b81529091506001600160a01b0382169063821c7be2906109ed9086908690600401611f27565b600060405180830381600087803b158015610a0757600080fd5b505af1158015610a1b573d6000803e3d6000fd5b50505050816001600160401b0316846001600160401b03167f4ca48d7cb411035a931da7a686ea77bc413069de12d844c47daf9242b765cba4856040516106329190611e93565b3360008051602061256a83398151915214610aa2573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b610aaa6114ad565b6000610ac060036001600160401b0389166114d3565b604051639838caa360e01b81529091506001600160a01b03821690639838caa390610af79089908990899089908990600401611f52565b600060405180830381600087803b158015610b1157600080fd5b505af1158015610b25573d6000803e3d6000fd5b50506040805160ff891681526001600160401b038881166020830152808b1694508b1692507fa9aaaef624d196d961d053fc29d4331e5cb45d62195d99814d29a94f46fa9d4b9101610915565b3360008051602061256a83398151915214610bb2573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b610bba6114ad565b6000610bd060036001600160401b0387166114d3565b604051634e82087760e11b81529091506001600160a01b03821690639d0410ee90610c0390879087908790600401611f93565b600060405180830381600087803b158015610c1d57600080fd5b505af1158015610c31573d6000803e3d6000fd5b50505050846001600160401b03167f7814301100cb38a35027dd63a29aa14c3f73188f15cf9f027655257c45016e8785604051610c6e9190611e93565b60405180910390a25050505050565b3360008051602061256a83398151915214610cbd573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b610cc56114ad565b6000610cdb60036001600160401b038a166114d3565b604051636bef5a4160e11b81529091506001600160a01b0382169063d7deb48290610d14908a908a908a908a908a908a90600401611fc3565b600060405180830381600087803b158015610d2e57600080fd5b505af1158015610d42573d6000803e3d6000fd5b5050604080516001600160a01b03891681526001600160401b038681166020830152808b1694508b811693508c16917f93927390d124732c66289c6e22370514ddd612a9cc6bff5671cbf2cbc15ddfe3910160405180910390a45050505050505050565b600080610dbd60036001600160401b0386166114d3565b60405163052d37d360e21b81526001600160401b03851660048201529091506001600160a01b038216906314b4df4c90602401610790565b60009182526001602090815260408084206001600160a01b0393909316845291905290205460ff1690565b3360008051602061256a83398151915214610e60573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b610e686114ad565b6000610e7e60036001600160401b0385166114d3565b60405163fe0dd37160e01b81529091506001600160a01b0382169063fe0dd37190610ead908590600401611e93565b600060405180830381600087803b158015610ec757600080fd5b505af1158015610edb573d6000803e3d6000fd5b50505050826001600160401b03167febc442018e78570634a88dbac83d4677ae1a5cf8491fb0fc166a7306ba1ff051836040516107389190611e93565b3360008051602061256a83398151915214610f58573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b610f606114ad565b6000610f7660036001600160401b038c166114d3565b604051631be14b3160e11b81529091506001600160a01b038216906337c2966290610fb3908c908c908c908c908c908c908c908c9060040161204a565b600060405180830381600087803b158015610fcd57600080fd5b505af1158015610fe1573d6000803e3d6000fd5b50505050886001600160401b03168a6001600160401b03167f08e18861659838cad8b04f4687aca1dfb93336e750acf5c3699637ed8ca4c27a8a8a8a60405161102c939291906120b6565b60405180910390a350505050505050505050565b3360008051602061256a83398151915214611080573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b6110886114ad565b6110ae6001600160401b0384166110a560408401602085016120ea565b600391906115d9565b506110c460066001600160401b038516846115d9565b50826001600160401b0316826001600160a01b03167f278018182b7c51e2da72c1f465c26c12d9606099af0f08db420378344b35222383604051611108919061211b565b60405180910390a3505050565b3360008051602061256a83398151915214611155573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b61115d6114ad565b600061117360036001600160401b0387166114d3565b60405163bb43abd960e01b81529091506001600160a01b0382169063bb43abd9906111a6908790879087906004016122be565b600060405180830381600087803b1580156111c057600080fd5b505af11580156111d4573d6000803e3d6000fd5b50505050826001600160401b0316856001600160401b03167f219c2fdfbe4f111dda584eefbcb4eb2370cec850e2a79e4fed3a8f708006e7a9868560405161121d9291906122f3565b60405180910390a35050505050565b3360008051602061256a8339815191521461126c573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b6112746114ad565b600061128a60036001600160401b0389166114d3565b60405163e926cbd160e01b81529091506001600160a01b0382169063e926cbd1906112c19089908990899089908990600401611ec3565b600060405180830381600087803b1580156112db57600080fd5b505af11580156112ef573d6000803e3d6000fd5b50505050856001600160401b0316876001600160401b03167f0145a8acdb5e19e431ea924e2c8997b01f0869cad2a2c6927c8645a5938cf0c3878787876040516109159493929190611efd565b60008281526001602081905260409091200154611358816114e6565b6107ff838361156c565b3360008051602061256a833981519152146113a2573360008051602061256a8339815191526040516359afe8af60e11b8152600401610559929190611d78565b6113aa6114ad565b60006113c060036001600160401b0385166114d3565b604051638b24806560e01b81529091506001600160a01b03821690638b248065906113ef90859060040161242d565b600060405180830381600087803b15801561140957600080fd5b505af115801561141d573d6000803e3d6000fd5b506114329250505060408301602084016120ea565b6001600160a01b03166114486020840184612523565b6001600160401b039081169085167f4def5362750954077cbc9dad1ab88058dd6d0ed894ba05f8f147a7a2fe204b2261148760c0870160a08801612523565b8660c0018761010001356040516114a09392919061253e565b60405180910390a4505050565b60025460ff16156114d15760405163d93c066560e01b815260040160405180910390fd5b565b60006114df83836115ef565b9392505050565b6114f08133611636565b50565b60006114ff8383610df5565b6115645760008381526001602081815260408084206001600160a01b0387168086529252808420805460ff19169093179092559051339286917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9190a4506001610513565b506000610513565b60006115788383610df5565b156115645760008381526001602090815260408083206001600160a01b0386168085529252808320805460ff1916905551339286917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4506001610513565b60006107d184846001600160a01b038516611673565b60008181526002830160205260408120548015801561161557506116138484611690565b155b156114df5760405163015ab34360e11b815260048101849052602401610559565b6116408282610df5565b61166f5760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610559565b5050565b600082815260028401602052604081208290556107d1848461169c565b60006114df83836116a8565b60006114df83836116c0565b600081815260018301602052604081205415156114df565b600081815260018301602052604081205461156457508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610513565b60006020828403121561171957600080fd5b81356001600160e01b0319811681146114df57600080fd5b80356001600160401b038116811461174857600080fd5b919050565b6001600160a01b03811681146114f057600080fd5b80356117488161174d565b60008060006060848603121561178257600080fd5b61178b84611731565b925061179960208501611731565b915060408401356117a98161174d565b809150509250925092565b600060c082840312156117c657600080fd5b50919050565b600080604083850312156117df57600080fd5b6117e883611731565b915060208301356001600160401b0381111561180357600080fd5b61180f858286016117b4565b9150509250929050565b60006020828403121561182b57600080fd5b5035919050565b6000806040838503121561184557600080fd5b61184e83611731565b915061185c60208401611731565b90509250929050565b6000806040838503121561187857600080fd5b82359150602083013561188a8161174d565b809150509250929050565b60008083601f8401126118a757600080fd5b5081356001600160401b038111156118be57600080fd5b6020830191508360208285010111156118d657600080fd5b9250929050565b803560ff8116811461174857600080fd5b60008060008060008060a0878903121561190757600080fd5b61191087611731565b955061191e60208801611731565b945060408701356001600160401b0381111561193957600080fd5b61194589828a01611895565b90955093506119589050606088016118dd565b9150608087013590509295509295509295565b60008060006060848603121561198057600080fd5b61198984611731565b925060208401356001600160401b038111156119a457600080fd5b6119b0868287016117b4565b9250506119bf60408501611731565b90509250925092565b60008060008060008060a087890312156119e157600080fd5b6119ea87611731565b95506119f860208801611731565b9450611a06604088016118dd565b9350611a1460608801611731565b925060808701356001600160401b03811115611a2f57600080fd5b611a3b89828a01611895565b979a9699509497509295939492505050565b60008060008060608587031215611a6357600080fd5b611a6c85611731565b935060208501356001600160401b0380821115611a8857600080fd5b611a94888389016117b4565b94506040870135915080821115611aaa57600080fd5b50611ab787828801611895565b95989497509550505050565b600080600080600080600060c0888a031215611ade57600080fd5b611ae788611731565b9650611af560208901611731565b9550611b0360408901611731565b94506060880135611b138161174d565b935060808801356001600160401b0380821115611b2f57600080fd5b818a0191508a601f830112611b4357600080fd5b813581811115611b5257600080fd5b8b60208260051b8501011115611b6757600080fd5b602083019550809450505050611b7f60a08901611731565b905092959891949750929550565b600080600080600080600080600060e08a8c031215611bab57600080fd5b611bb48a611731565b9850611bc260208b01611731565b9750611bd060408b016118dd565b9650611bde60608b01611731565b955060808a01356001600160401b0380821115611bfa57600080fd5b611c068d838e016117b4565b965060a08c0135915080821115611c1c57600080fd5b611c288d838e01611895565b909650945060c08c0135915080821115611c4157600080fd5b50611c4e8c828d01611895565b915080935050809150509295985092959850929598565b600080600060608486031215611c7a57600080fd5b611c8384611731565b92506020840135611c938161174d565b915060408401356001600160401b03811115611cae57600080fd5b8401606081870312156117a957600080fd5b60008060008060808587031215611cd657600080fd5b611cdf85611731565b935060208501356001600160401b03811115611cfa57600080fd5b611d06878288016117b4565b935050611d1560408601611731565b9150611d23606086016118dd565b905092959194509250565b60008060408385031215611d4157600080fd5b611d4a83611731565b915060208301356001600160401b03811115611d6557600080fd5b8301610120818603121561188a57600080fd5b6001600160a01b0392831681529116602082015260400190565b6000808335601e19843603018112611da957600080fd5b83016020810192503590506001600160401b03811115611dc857600080fd5b8036038213156118d657600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6000611e0c8283611d92565b60c08552611e1e60c086018284611dd7565b9150506001600160401b0380611e3660208601611731565b16602086015280611e4960408601611731565b16604086015280611e5c60608601611731565b16606086015280611e6f60808601611731565b16608086015280611e8260a08601611731565b1660a0860152508091505092915050565b6020815260006114df6020830184611e00565b600060208284031215611eb857600080fd5b81516114df8161174d565b6001600160401b0386168152608060208201526000611ee6608083018688611dd7565b60ff94909416604083015250606001529392505050565b606081526000611f11606083018688611dd7565b60ff949094166020830152506040015292915050565b604081526000611f3a6040830185611e00565b90506001600160401b03831660208301529392505050565b60006001600160401b03808816835260ff8716602084015280861660408401525060806060830152611f88608083018486611dd7565b979650505050505050565b604081526000611fa66040830186611e00565b8281036020840152611fb9818587611dd7565b9695505050505050565b6001600160401b0387811682528681166020808401919091526001600160a01b03878116604085015260a06060850181905284018690526000928792909160c08601855b8981101561202e57853561201a8161174d565b831682529483019490830190600101612007565b5080955050505080851660808501525050979650505050505050565b60006001600160401b03808b16835260ff8a16602084015280891660408401525060c0606083015261207f60c0830188611e00565b8281036080840152612092818789611dd7565b905082810360a08401526120a7818587611dd7565b9b9a5050505050505050505050565b60ff841681526001600160401b03831660208201526060604082015260006120e16060830184611e00565b95945050505050565b6000602082840312156120fc57600080fd5b81356114df8161174d565b803563ffffffff8116811461174857600080fd5b602081526000823560fe1984360301811261213557600080fd5b6060602084015283016121488180611d92565b61010080608087015261216061018087018385611dd7565b925061216f6020850185611d92565b9250607f19808886030160a0890152612189858584611dd7565b94506121986040870187611d92565b94509150808886030160c08901526121b1858584611dd7565b94506121c06060870187611d92565b94509150808886030160e08901526121d9858584611dd7565b94506121e86080870187611d92565b94509150808886030183890152612200858584611dd7565b945061220f60a0870187611d92565b945092508088860301610120890152612229858585611dd7565b945061223860c0870187611d92565b945092508088860301610140890152612252858585611dd7565b945061226160e0870187611d92565b96509350808886030161016089015250505061227e828483611dd7565b9250505061228e60208501611762565b6001600160a01b0381166040850152506122aa60408501612107565b63ffffffff81166060850152509392505050565b6060815260006122d16060830186611e00565b90506001600160401b038416602083015260ff83166040830152949350505050565b6040815260006123066040830185611e00565b905060ff831660208301529392505050565b6000808335601e1984360301811261232f57600080fd5b83016020810192503590506001600160401b0381111561234e57600080fd5b8060051b36038213156118d657600080fd5b81835260006020808501808196508560051b81019150846000805b888110156123ba578385038a52823560be1989360301811261239b578283fd5b6123a7868a8301611e00565b9a87019a9550509185019160010161237b565b509298975050505050505050565b8183526000602080850194508260005b858110156124065781356123eb8161174d565b6001600160a01b0316875295820195908201906001016123d8565b509495945050505050565b80356002811061242057600080fd5b8252602090810135910152565b602081526001600160401b0361244283611731565b166020820152600061245660208401611762565b6001600160a01b0381166040840152506124736040840184612318565b61012080606086015261248b61014086018385612360565b925061249a6060870187611d92565b9250601f19808786030160808801526124b4858584611dd7565b94506124c36080890189612318565b94509150808786030160a0880152506124dd8484836123c8565b9350506124ec60a08701611731565b6001600160401b03811660c0870152915061250d60e0860160c08801612411565b6101008601358186015250508091505092915050565b60006020828403121561253557600080fd5b6114df82611731565b6001600160401b03841681526080810161255b6020830185612411565b82606083015294935050505056fe0000000000000000000000001111111111111111111111111111111111111111a164736f6c6343000814000a +0x60806040526004361061023e575f3560e01c80637e098ee311610134578063a6a78538116100b3578063d547741f11610078578063d547741f1461064f578063d90a65471461066e578063e32916d0146106a1578063e63ab1e9146106c0578063ea1f48fa146106f3578063f9e1384514610712575f80fd5b8063a6a78538146105e4578063a8f7c5ed146105f7578063b89af9041461060a578063b9315d3a1461061d578063c97008e014610630575f80fd5b80638e6f8c60116100f95780638e6f8c601461055857806391d1485414610577578063987ab9db14610596578063a217fddf146105b5578063a4d91fe9146105c8575f80fd5b80637e098ee3146104d357806382a14aad146104f257806382a1ece4146105115780638456cb5914610524578063884673ac14610538575f80fd5b80632f2ff15d116101c0578063459ba22c11610185578063459ba22c1461044f5780634984ee6b1461046e5780635c975abb1461048157806362f3765e14610498578063727391f2146104c0575f80fd5b80632f2ff15d146103bf57806333db3920146103de57806336568abe146103fd5780633f4ba83a1461041c578063410fdec814610430575f80fd5b80631fb27b34116102065780631fb27b3414610302578063216e804214610321578063248a9ca31461033657806326c2596214610373578063274ef015146103a0575f80fd5b806301ffc9a7146102425780630633da77146102765780630d0dd3991461029757806319413268146102cd5780631948fdbc146102ec575b5f80fd5b34801561024d575f80fd5b5061026161025c366004612623565b610725565b60405190151581526020015b60405180910390f35b348015610281575f80fd5b50610295610290366004612688565b61075b565b005b3480156102a2575f80fd5b505f546102b5906001600160a01b031681565b6040516001600160a01b03909116815260200161026d565b3480156102d8575f80fd5b506102616102e73660046126e6565b61087a565b3480156102f7575f80fd5b506102b5627e87d581565b34801561030d575f80fd5b5061029561031c366004612743565b610909565b34801561032c575f80fd5b50627e87d56102b5565b348015610341575f80fd5b5061036561035036600461278f565b5f908152600160208190526040909120015490565b60405190815260200161026d565b34801561037e575f80fd5b5061039261038d36600461278f565b610a06565b60405161026d9291906127ba565b3480156103ab575f80fd5b506102b56103ba3660046127e1565b610a32565b3480156103ca575f80fd5b506102956103d9366004612818565b610ac3565b3480156103e9575f80fd5b506102956103f836600461288f565b610aee565b348015610408575f80fd5b50610295610417366004612818565b610c07565b348015610427575f80fd5b50610295610c3f565b34801561043b575f80fd5b5061029561044a36600461290b565b610c7c565b34801561045a575f80fd5b506102956104693660046126e6565b610df0565b61029561047c36600461295f565b610e69565b34801561048c575f80fd5b5060025460ff16610261565b3480156104a3575f80fd5b506104ad61271081565b60405161ffff909116815260200161026d565b6102956104ce3660046129e5565b610f71565b3480156104de575f80fd5b50600b546102b5906001600160a01b031681565b3480156104fd575f80fd5b5061029561050c366004612a58565b611074565b61029561051f366004612ac6565b6110a8565b34801561052f575f80fd5b506102956114bf565b348015610543575f80fd5b506102b55f80516020613dd983398151915281565b348015610563575f80fd5b506102b56105723660046127e1565b6114f9565b348015610582575f80fd5b50610261610591366004612818565b611547565b3480156105a1575f80fd5b505f80516020613dd98339815191526102b5565b3480156105c0575f80fd5b506103655f81565b3480156105d3575f80fd5b505f546001600160a01b03166102b5565b6102956105f2366004612743565b611571565b610295610605366004612b8d565b611661565b610295610618366004612c60565b611781565b61029561062b366004612cb8565b611854565b34801561063b575f80fd5b5061029561064a36600461288f565b611963565b34801561065a575f80fd5b50610295610669366004612818565b611a6b565b348015610679575f80fd5b506103657f682886e4aa036ce8b1761850f768c4604676f1856f596f453ecc49ee4d73545481565b3480156106ac575f80fd5b506102956106bb3660046126e6565b611a90565b3480156106cb575f80fd5b506103657f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a81565b3480156106fe575f80fd5b5061026161070d3660046126e6565b611ad6565b610295610720366004612d26565b611b1d565b5f6001600160e01b03198216637965db0b60e01b148061075557506301ffc9a760e01b6001600160e01b03198316145b92915050565b335f80516020613dd9833981519152146107a257335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b60405180910390fd5b6107aa611c98565b5f6107bf60036001600160401b038616611cbe565b604051630a24e8a960e41b81526001600160401b03851660048201526001600160a01b0384811660248301529192509082169063a24e8a90906044015f604051808303815f87803b158015610812575f80fd5b505af1158015610824573d5f803e3d5ffd5b50506040516001600160a01b03851681526001600160401b038087169350871691507f5267932e6c1b678e74efba1e4d6c8b3fd7504cf0fa8eea462efac2c590a21d56906020015b60405180910390a350505050565b5f8061089060036001600160401b038716611cbe565b6040516361545fdd60e01b81529091506001600160a01b038216906361545fdd906108c19087908790600401612e95565b602060405180830381865afa1580156108dc573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109009190612eb6565b95945050505050565b335f80516020613dd98339815191521461094757335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b61094f611c98565b5f61096460036001600160401b038516611cbe565b60405163434698bb60e01b81529091506001600160a01b0382169063434698bb90610993908590600401612ed5565b5f604051808303815f87803b1580156109aa575f80fd5b505af11580156109bc573d5f803e3d5ffd5b50505050826001600160401b03167fde25c2b146db36cbc91715fdf29cb0eb556eed7678e0bf13563bf7050e8f0bbd836040516109f99190612ed5565b60405180910390a2505050565b600a8181548110610a15575f80fd5b5f9182526020909120015460ff81169150610100900461ffff1682565b5f80610a4860036001600160401b038616611cbe565b6040516374ceeb5560e01b81526001600160401b03851660048201529091506001600160a01b038216906374ceeb55906024015b602060405180830381865afa158015610a97573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610abb9190612ee7565b949350505050565b5f8281526001602081905260409091200154610ade81611cd0565b610ae88383611cda565b50505050565b335f80516020613dd983398151915214610b2c57335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b610b34611c98565b5f610b4960036001600160401b038916611cbe565b604051630af7d74360e01b81529091506001600160a01b03821690630af7d74390610b809089908990899089908990600401612f02565b5f604051808303815f87803b158015610b97575f80fd5b505af1158015610ba9573d5f803e3d5ffd5b50505050856001600160401b0316876001600160401b03167f7d2ac4c1544e07516def74ce3ada12ff791868ed07d6da82694ae2c0342c837187878787604051610bf69493929190612f3b565b60405180910390a350505050505050565b6001600160a01b0381163314610c305760405163334bd91960e11b815260040160405180910390fd5b610c3a8282611d50565b505050565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a610c6981611cd0565b610c71611dbb565b610c79611dde565b50565b335f80516020613dd983398151915214610cba57335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b610cc2611c98565b5f610cd760036001600160401b038616611cbe565b60405163410e3df160e11b81529091506001600160a01b0382169063821c7be290610d089086908690600401612f64565b5f604051808303815f87803b158015610d1f575f80fd5b505af1158015610d31573d5f803e3d5ffd5b5050506001600160401b0383165f90815260096020526040812080546001600160e01b03191681559150610d686001830182612530565b610d75600283015f61254e565b610d82600383015f612585565b5060048101805467ffffffffffffffff1916905560058101805460ff191690555f600682018190556007909101556040516001600160401b0383811691908616907f4ca48d7cb411035a931da7a686ea77bc413069de12d844c47daf9242b765cba49061086c908790612ed5565b5f610e0560036001600160401b038616611cbe565b6040516356d301c160e11b81529091506001600160a01b0382169063ada6038290610e369086908690600401612e95565b5f604051808303815f87803b158015610e4d575f80fd5b505af1158015610e5f573d5f803e3d5ffd5b5050505050505050565b335f80516020613dd983398151915214610ea757335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b610eaf611c98565b5f610ec460036001600160401b038916611cbe565b604051639838caa360e01b81529091506001600160a01b03821690639838caa390610efb9089908990899089908990600401612f8e565b5f604051808303815f87803b158015610f12575f80fd5b505af1158015610f24573d5f803e3d5ffd5b50506040805160ff891681526001600160401b038881166020830152808b1694508b1692507fa9aaaef624d196d961d053fc29d4331e5cb45d62195d99814d29a94f46fa9d4b9101610bf6565b335f80516020613dd983398151915214610faf57335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b610fb7611c98565b5f610fcc60036001600160401b038716611cbe565b604051634e82087760e11b81529091506001600160a01b03821690639d0410ee90610fff90879087908790600401612fce565b5f604051808303815f87803b158015611016575f80fd5b505af1158015611028573d5f803e3d5ffd5b50505050846001600160401b03167f7814301100cb38a35027dd63a29aa14c3f73188f15cf9f027655257c45016e87856040516110659190612ed5565b60405180910390a25050505050565b7f682886e4aa036ce8b1761850f768c4604676f1856f596f453ecc49ee4d73545461109e81611cd0565b610c3a8383611e30565b335f80516020613dd9833981519152146110e657335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b6110ee611c98565b5f61110360036001600160401b038a16611cbe565b6001600160401b038881165f90815260096020908152604080832081516101008101835281549586168152600160401b9095046001600160a01b0316858401526001810180548351818602810186018552818152979850949691949286019391929091879084015b8282101561128b578382905f5260205f2090600302016040518060400160405290815f8201805461119b90612ffd565b80601f01602080910402602001604051908101604052809291908181526020018280546111c790612ffd565b80156112125780601f106111e957610100808354040283529160200191611212565b820191905f5260205f20905b8154815290600101906020018083116111f557829003601f168201915b50505091835250506040805160a0810182526001848101546001600160401b038082168452600160401b82048116602085810191909152600160801b8304821695850195909552600160c01b9091048116606084015260029095015490941660808201529181019190915291835292909201910161116b565b5050505081526020016002820180546112a390612ffd565b80601f01602080910402602001604051908101604052809291908181526020018280546112cf90612ffd565b801561131a5780601f106112f15761010080835404028352916020019161131a565b820191905f5260205f20905b8154815290600101906020018083116112fd57829003601f168201915b505050505081526020016003820180548060200260200160405190810160405280929190818152602001828054801561137a57602002820191905f5260205f20905b81546001600160a01b0316815260019091019060200180831161135c575b505050918352505060048201546001600160401b0316602082015260408051808201825260058401805492909301929091829060ff1660018111156113c1576113c16127a6565b60018111156113d2576113d26127a6565b8152602001600182015481525050815260200160078201548152505090506113fb828883611ef3565b604051636bef5a4160e11b81526001600160a01b0383169063d7deb48290611431908b908b908b908b908b908b9060040161302f565b5f604051808303815f87803b158015611448575f80fd5b505af115801561145a573d5f803e3d5ffd5b5050604080516001600160a01b038a1681526001600160401b038781166020830152808c1694508c811693508d16917f93927390d124732c66289c6e22370514ddd612a9cc6bff5671cbf2cbc15ddfe3910160405180910390a4505050505050505050565b7f65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a6114e981611cd0565b6114f1611c98565b610c796121cf565b5f8061150f60036001600160401b038616611cbe565b60405163052d37d360e21b81526001600160401b03851660048201529091506001600160a01b038216906314b4df4c90602401610a7c565b5f9182526001602090815260408084206001600160a01b0393909316845291905290205460ff1690565b335f80516020613dd9833981519152146115af57335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b6115b7611c98565b5f6115cc60036001600160401b038516611cbe565b60405163fe0dd37160e01b81529091506001600160a01b0382169063fe0dd371906115fb908590600401612ed5565b5f604051808303815f87803b158015611612575f80fd5b505af1158015611624573d5f803e3d5ffd5b50505050826001600160401b03167febc442018e78570634a88dbac83d4677ae1a5cf8491fb0fc166a7306ba1ff051836040516109f99190612ed5565b335f80516020613dd98339815191521461169f57335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b6116a7611c98565b5f6116bc60036001600160401b038c16611cbe565b604051631be14b3160e11b81529091506001600160a01b038216906337c29662906116f9908c908c908c908c908c908c908c908c906004016130b5565b5f604051808303815f87803b158015611710575f80fd5b505af1158015611722573d5f803e3d5ffd5b50505050886001600160401b03168a6001600160401b03167f08e18861659838cad8b04f4687aca1dfb93336e750acf5c3699637ed8ca4c27a8a8a8a60405161176d93929190613120565b60405180910390a350505050505050505050565b335f80516020613dd9833981519152146117bf57335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b6117c7611c98565b6117ed6001600160401b0384166117e4604084016020850161314a565b6003919061220c565b5061180360066001600160401b0385168461220c565b50826001600160401b0316826001600160a01b03167f278018182b7c51e2da72c1f465c26c12d9606099af0f08db420378344b352223836040516118479190613178565b60405180910390a3505050565b335f80516020613dd98339815191521461189257335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b61189a611c98565b5f6118af60036001600160401b038716611cbe565b60405163bb43abd960e01b81529091506001600160a01b0382169063bb43abd9906118e290879087908790600401613319565b5f604051808303815f87803b1580156118f9575f80fd5b505af115801561190b573d5f803e3d5ffd5b50505050826001600160401b0316856001600160401b03167f219c2fdfbe4f111dda584eefbcb4eb2370cec850e2a79e4fed3a8f708006e7a9868560405161195492919061334d565b60405180910390a35050505050565b335f80516020613dd9833981519152146119a157335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b6119a9611c98565b5f6119be60036001600160401b038916611cbe565b60405163e926cbd160e01b81529091506001600160a01b0382169063e926cbd1906119f59089908990899089908990600401612f02565b5f604051808303815f87803b158015611a0c575f80fd5b505af1158015611a1e573d5f803e3d5ffd5b50505050856001600160401b0316876001600160401b03167f0145a8acdb5e19e431ea924e2c8997b01f0869cad2a2c6927c8645a5938cf0c387878787604051610bf69493929190612f3b565b5f8281526001602081905260409091200154611a8681611cd0565b610ae88383611d50565b5f611aa560036001600160401b038616611cbe565b6040516346c578a560e01b81529091506001600160a01b038216906346c578a590610e369086908690600401612e95565b5f80611aec60036001600160401b038716611cbe565b604051630eb5e5eb60e31b81529091506001600160a01b038216906375af2f58906108c19087908790600401612e95565b335f80516020613dd983398151915214611b5b57335f80516020613dd98339815191526040516359afe8af60e11b8152600401610799929190612d6e565b611b63611c98565b5f611b7860036001600160401b038516611cbe565b604051638b24806560e01b81529091506001600160a01b03821690638b24806590611ba7908590600401613496565b5f604051808303815f87803b158015611bbe575f80fd5b505af1158015611bd0573d5f803e3d5ffd5b50849250600991505f9050611be8602084018461358f565b6001600160401b0316815260208101919091526040015f20611c0a8282613b5a565b50611c1d9050604083016020840161314a565b6001600160a01b0316611c33602084018461358f565b6001600160401b039081169085167f4def5362750954077cbc9dad1ab88058dd6d0ed894ba05f8f147a7a2fe204b22611c7260c0870160a0880161358f565b8660c001876101000135604051611c8b93929190613c60565b60405180910390a4505050565b60025460ff1615611cbc5760405163d93c066560e01b815260040160405180910390fd5b565b5f611cc98383612221565b9392505050565b610c798133612267565b5f611ce58383611547565b611d49575f8381526001602081815260408084206001600160a01b0387168086529252808420805460ff19169093179092559051339286917f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d9190a4506001610755565b505f610755565b5f611d5b8383611547565b15611d49575f8381526001602090815260408083206001600160a01b0386168085529252808320805460ff1916905551339286917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a4506001610755565b60025460ff16611cbc57604051638dfc202b60e01b815260040160405180910390fd5b611de6611dbb565b6002805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b611e8a8282808060200260200160405190810160405280939291908181526020015f905b82821015611e8057611e7160408302860136819003810190613ca6565b81526020019060010190611e54565b50505050506122a4565b611e95600a5f6125a0565b5f5b81811015610c3a57600a838383818110611eb357611eb3613d02565b83546001810185555f948552602090942060409091029290920192919091019050611ede8282613d16565b50508080611eeb90613d6c565b915050611e97565b60e08101515f819003611f065750505050565b5f805f805f5b600a54811015612039575f600a8281548110611f2a57611f2a613d02565b5f91825260209091206040805180820190915291018054829060ff166003811115611f5757611f576127a6565b6003811115611f6857611f686127a6565b8152905461ffff6101009091048116602092830152908201519192505f9161271091611f9591168a613641565b611f9f9190613d84565b90505f82516003811115611fb557611fb56127a6565b03611fc257809650612024565b600182516003811115611fd757611fd76127a6565b03611fe457809550612024565b600282516003811115611ff957611ff96127a6565b0361200657809450612024565b60038251600381111561201b5761201b6127a6565b03612024578093505b5050808061203190613d6c565b915050611f0c565b505f6120458284613da3565b6040516308179f3560e01b81526001600160401b038a1660048201529091505f906001600160a01b038b16906308179f3590602401602060405180830381865afa158015612095573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906120b99190612ee7565b90506120c88860c00151612314565b1561216c576040516001600160a01b0382169087156108fc029088905f818181858888f19350505050158015612100573d5f803e3d5ffd5b50600b546040516001600160a01b039091169086156108fc029087905f818181858888f19350505050158015612138573d5f803e3d5ffd5b50604051627e87d59083156108fc029084905f818181858888f19350505050158015612166573d5f803e3d5ffd5b506121c3565b5f61217a8960c00151612358565b90506121906001600160a01b03821683896123bf565b600b546121aa906001600160a01b038381169116886123bf565b6121c16001600160a01b038216627e87d5856123bf565b505b50505050505050505050565b6121d7611c98565b6002805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258611e133390565b5f610abb84846001600160a01b038516612411565b5f818152600283016020526040812054801580156122465750612244848461242d565b155b15611cc95760405163015ab34360e11b815260048101849052602401610799565b6122718282611547565b6122a05760405163e2517d3f60e01b81526001600160a01b038216600482015260248101839052604401610799565b5050565b5f805b82518110156122ed578281815181106122c2576122c2613d02565b602002602001015160200151826122d99190613db6565b9150806122e581613d6c565b9150506122a7565b5061ffff8116612710146122a0576040516304be90a960e51b815260040160405180910390fd5b5f61231e82612438565b156123355750602001516001600160a01b03161590565b61233e82612456565b1561234c5750602001511590565b505f919050565b919050565b5f61236282612438565b1561236f57506020015190565b61237882612456565b1561239057602082015163ffffffff60801b17610755565b815160018111156123a3576123a36127a6565b6040516375458b5d60e11b815260040161079991815260200190565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052610c3a90849061245d565b5f8281526002840160205260408120829055610abb84846124c9565b5f611cc983836124d4565b5f60015b8251600181111561244f5761244f6127a6565b1492915050565b5f8061243c565b5f8060205f8451602086015f885af18061247c576040513d5f823e3d81fd5b50505f513d915081156124935780600114156124a0565b6001600160a01b0384163b155b15610ae857604051635274afe760e01b81526001600160a01b0385166004820152602401610799565b5f611cc983836124eb565b5f8181526001830160205260408120541515611cc9565b5f818152600183016020526040812054611d4957508154600181810184555f848152602080822090930184905584548482528286019093526040902091909155610755565b5080545f8255600302905f5260205f2090810190610c7991906125bb565b50805461255a90612ffd565b5f825580601f10612569575050565b601f0160209004905f5260205f2090810190610c7991906125f4565b5080545f8255905f5260205f2090810190610c7991906125f4565b5080545f8255905f5260205f2090810190610c799190612608565b808211156125f0575f6125ce828261254e565b505f600182015560028101805467ffffffffffffffff191690556003016125bb565b5090565b5b808211156125f0575f81556001016125f5565b5b808211156125f057805462ffffff19168155600101612609565b5f60208284031215612633575f80fd5b81356001600160e01b031981168114611cc9575f80fd5b6001600160401b0381168114610c79575f80fd5b80356123538161264a565b6001600160a01b0381168114610c79575f80fd5b803561235381612669565b5f805f6060848603121561269a575f80fd5b83356126a58161264a565b925060208401356126b58161264a565b915060408401356126c581612669565b809150509250925092565b5f60c082840312156126e0575f80fd5b50919050565b5f805f606084860312156126f8575f80fd5b83356127038161264a565b925060208401356127138161264a565b915060408401356001600160401b0381111561272d575f80fd5b612739868287016126d0565b9150509250925092565b5f8060408385031215612754575f80fd5b823561275f8161264a565b915060208301356001600160401b03811115612779575f80fd5b612785858286016126d0565b9150509250929050565b5f6020828403121561279f575f80fd5b5035919050565b634e487b7160e01b5f52602160045260245ffd5b60408101600484106127ce576127ce6127a6565b92815261ffff9190911660209091015290565b5f80604083850312156127f2575f80fd5b82356127fd8161264a565b9150602083013561280d8161264a565b809150509250929050565b5f8060408385031215612829575f80fd5b82359150602083013561280d81612669565b5f8083601f84011261284b575f80fd5b5081356001600160401b03811115612861575f80fd5b602083019150836020828501011115612878575f80fd5b9250929050565b803560ff81168114612353575f80fd5b5f805f805f8060a087890312156128a4575f80fd5b86356128af8161264a565b955060208701356128bf8161264a565b945060408701356001600160401b038111156128d9575f80fd5b6128e589828a0161283b565b90955093506128f890506060880161287f565b9150608087013590509295509295509295565b5f805f6060848603121561291d575f80fd5b83356129288161264a565b925060208401356001600160401b03811115612942575f80fd5b61294e868287016126d0565b92505060408401356126c58161264a565b5f805f805f8060a08789031215612974575f80fd5b863561297f8161264a565b9550602087013561298f8161264a565b945061299d6040880161287f565b935060608701356129ad8161264a565b925060808701356001600160401b038111156129c7575f80fd5b6129d389828a0161283b565b979a9699509497509295939492505050565b5f805f80606085870312156129f8575f80fd5b8435612a038161264a565b935060208501356001600160401b0380821115612a1e575f80fd5b612a2a888389016126d0565b94506040870135915080821115612a3f575f80fd5b50612a4c8782880161283b565b95989497509550505050565b5f8060208385031215612a69575f80fd5b82356001600160401b0380821115612a7f575f80fd5b818501915085601f830112612a92575f80fd5b813581811115612aa0575f80fd5b8660208260061b8501011115612ab4575f80fd5b60209290920196919550909350505050565b5f805f805f805f60c0888a031215612adc575f80fd5b8735612ae78161264a565b96506020880135612af78161264a565b95506040880135612b078161264a565b94506060880135612b1781612669565b935060808801356001600160401b0380821115612b32575f80fd5b818a0191508a601f830112612b45575f80fd5b813581811115612b53575f80fd5b8b60208260051b8501011115612b67575f80fd5b602083019550809450505050612b7f60a0890161265e565b905092959891949750929550565b5f805f805f805f805f60e08a8c031215612ba5575f80fd5b8935612bb08161264a565b985060208a0135612bc08161264a565b9750612bce60408b0161287f565b9650612bdc60608b0161265e565b955060808a01356001600160401b0380821115612bf7575f80fd5b612c038d838e016126d0565b965060a08c0135915080821115612c18575f80fd5b612c248d838e0161283b565b909650945060c08c0135915080821115612c3c575f80fd5b50612c498c828d0161283b565b915080935050809150509295985092959850929598565b5f805f60608486031215612c72575f80fd5b8335612c7d8161264a565b92506020840135612c8d81612669565b915060408401356001600160401b03811115612ca7575f80fd5b8401606081870312156126c5575f80fd5b5f805f8060808587031215612ccb575f80fd5b8435612cd68161264a565b935060208501356001600160401b03811115612cf0575f80fd5b612cfc878288016126d0565b9350506040850135612d0d8161264a565b9150612d1b6060860161287f565b905092959194509250565b5f8060408385031215612d37575f80fd5b8235612d428161264a565b915060208301356001600160401b03811115612d5c575f80fd5b8301610120818603121561280d575f80fd5b6001600160a01b0392831681529116602082015260400190565b5f808335601e19843603018112612d9d575f80fd5b83016020810192503590506001600160401b03811115612dbb575f80fd5b803603821315612878575f80fd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b5f612dfc8283612d88565b60c08552612e0e60c086018284612dc9565b9150506020830135612e1f8161264a565b6001600160401b039081166020860152604084013590612e3e8261264a565b9081166040860152606084013590612e558261264a565b9081166060860152608084013590612e6c8261264a565b908116608086015260a084013590612e838261264a565b1660a094909401939093525090919050565b6001600160401b0383168152604060208201525f610abb6040830184612df1565b5f60208284031215612ec6575f80fd5b81518015158114611cc9575f80fd5b602081525f611cc96020830184612df1565b5f60208284031215612ef7575f80fd5b8151611cc981612669565b6001600160401b0386168152608060208201525f612f24608083018688612dc9565b60ff94909416604083015250606001529392505050565b606081525f612f4e606083018688612dc9565b60ff949094166020830152506040015292915050565b604081525f612f766040830185612df1565b90506001600160401b03831660208301529392505050565b5f6001600160401b03808816835260ff8716602084015280861660408401525060806060830152612fc3608083018486612dc9565b979650505050505050565b604081525f612fe06040830186612df1565b8281036020840152612ff3818587612dc9565b9695505050505050565b600181811c9082168061301157607f821691505b6020821081036126e057634e487b7160e01b5f52602260045260245ffd5b6001600160401b0387811682528681166020808401919091526001600160a01b03878116604085015260a06060850181905284018690525f928792909160c08601855b8981101561309957853561308581612669565b831682529483019490830190600101613072565b5080955050505080851660808501525050979650505050505050565b5f6001600160401b03808b16835260ff8a16602084015280891660408401525060c060608301526130e960c0830188612df1565b82810360808401526130fc818789612dc9565b905082810360a0840152613111818587612dc9565b9b9a5050505050505050505050565b60ff841681526001600160401b0383166020820152606060408201525f6109006060830184612df1565b5f6020828403121561315a575f80fd5b8135611cc981612669565b803563ffffffff81168114612353575f80fd5b602081525f823560fe19843603018112613190575f80fd5b6060602084015283016131a38180612d88565b6101008060808701526131bb61018087018385612dc9565b92506131ca6020850185612d88565b9250607f19808886030160a08901526131e4858584612dc9565b94506131f36040870187612d88565b94509150808886030160c089015261320c858584612dc9565b945061321b6060870187612d88565b94509150808886030160e0890152613234858584612dc9565b94506132436080870187612d88565b9450915080888603018389015261325b858584612dc9565b945061326a60a0870187612d88565b945092508088860301610120890152613284858585612dc9565b945061329360c0870187612d88565b9450925080888603016101408901526132ad858585612dc9565b94506132bc60e0870187612d88565b9650935080888603016101608901525050506132d9828483612dc9565b925050506132e96020850161267d565b6001600160a01b03811660408501525061330560408501613165565b63ffffffff81166060850152509392505050565b606081525f61332b6060830186612df1565b90506001600160401b038416602083015260ff83166040830152949350505050565b604081525f61335f6040830185612df1565b905060ff831660208301529392505050565b5f808335601e19843603018112613386575f80fd5b83016020810192503590506001600160401b038111156133a4575f80fd5b8060051b3603821315612878575f80fd5b8183525f6020808501808196508560051b81019150845f805b8881101561340d578385038a52823560be198936030181126133ee578283fd5b6133fa868a8301612df1565b9a87019a955050918501916001016133ce565b509298975050505050505050565b8183525f60208085019450825f5b8581101561345757813561343c81612669565b6001600160a01b031687529582019590820190600101613429565b509495945050505050565b60028110610c79575f80fd5b803561347981613462565b60028110613489576134896127a6565b8252602090810135910152565b602081525f82356134a68161264a565b6001600160401b0381166020840152506134c26020840161267d565b6001600160a01b0381166040840152506134df6040840184613371565b6101208060608601526134f7610140860183856133b5565b92506135066060870187612d88565b9250601f1980878603016080880152613520858584612dc9565b945061352f6080890189613371565b94509150808786030160a08801525061354984848361341b565b93505061355860a0870161265e565b6001600160401b03811660c0870152915061357960e0860160c0880161346e565b6101008601358186015250508091505092915050565b5f6020828403121561359f575f80fd5b8135611cc98161264a565b5f81356107558161264a565b5f808335601e198436030181126135cb575f80fd5b8301803591506001600160401b038211156135e4575f80fd5b6020019150600581901b3603821315612878575f80fd5b5f823560be1983360301811261360f575f80fd5b9190910192915050565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b80820281158282048414176107555761075561362d565b5b818110156122a0575f8155600101613659565b5f808335601e19843603018112613681575f80fd5b8301803591506001600160401b0382111561369a575f80fd5b602001915036819003821315612878575f80fd5b601f821115610c3a57805f5260205f20601f840160051c810160208510156136d35750805b6136e5601f850160051c830182613658565b5050505050565b6001600160401b0383111561370357613703613619565b613717836137118354612ffd565b836136ae565b5f601f841160018114613748575f85156137315750838201355b5f19600387901b1c1916600186901b1783556136e5565b5f83815260209020601f19861690835b828110156137785786850135825560209485019460019092019101613758565b5086821015613794575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b81356137b18161264a565b815467ffffffffffffffff19166001600160401b0382161782555060208201356137da8161264a565b81546fffffffffffffffff0000000000000000604092831b166fffffffffffffffff000000000000000019821681178455918401356138188161264a565b67ffffffffffffffff60801b60809190911b1677ffffffffffffffffffffffffffffffff0000000000000000198216831781178455606085013561385b8161264a565b6001600160401b0360c01b8160c01b16846001600160401b038516178317178555505050506122a061388f608084016135aa565b600183016001600160401b0382166001600160401b03198254161781555050565b6138ba828361366c565b6001600160401b038111156138d1576138d1613619565b6138e5816138df8554612ffd565b856136ae565b5f601f821160018114613916575f83156138ff5750838201355b5f19600385901b1c1916600184901b17855561396e565b5f85815260209020601f19841690835b828110156139465786850135825560209485019460019092019101613926565b5084821015613962575f1960f88660031b161c19848701351681555b505060018360011b0185555b505050506122a060208301600183016137a6565b600160401b83111561399657613996613619565b805483825580841015613a5557600381810281810483146139b9576139b961362d565b85820282810487146139cd576139cd61362d565b5f858152602081209283019291909101905b82821015613a50576139f18254612ffd565b8015613a3957601f80821160018114613a0c57838555613a36565b5f85815260209020613a2883850160051c820160018301613658565b505f85815260208120818755555b50505b505f600183018190556002830155908301906139df565b505050505b505f8181526020812083915b85811015613a9257613a7c613a7684876135fb565b836138b0565b6020929092019160039190910190600101613a61565b505050505050565b6001600160401b03831115613ab157613ab1613619565b600160401b831115613ac557613ac5613619565b805483825580841015613ae957815f5260205f20613ae7828201868301613658565b505b5081815f526020805f205f5b86811015613b18578335613b0881612669565b8282015592820192600101613af5565b50505050505050565b8135613b2c81613462565b60028110613b3c57613b3c6127a6565b60ff1982541660ff8216811783555050602082013560018201555050565b8135613b658161264a565b815467ffffffffffffffff19166001600160401b038216178255506020820135613b8e81612669565b815468010000000000000000600160e01b031916604091821b68010000000000000000600160e01b0316178255613bc7908301836135b6565b613bd5818360018601613982565b5050613be4606083018361366c565b613bf28183600286016136ec565b5050613c0160808301836135b6565b613c0f818360038601613a9a565b5050613c41613c2060a084016135aa565b600483016001600160401b0382166001600160401b03198254161781555050565b613c5160c0830160058301613b21565b61010082013560078201555050565b6001600160401b038416815260808101613c7d602083018561346e565b826060830152949350505050565b60048110610c79575f80fd5b61ffff81168114610c79575f80fd5b5f60408284031215613cb6575f80fd5b604051604081018181106001600160401b0382111715613cd857613cd8613619565b6040528235613ce681613c8b565b81526020830135613cf681613c97565b60208201529392505050565b634e487b7160e01b5f52603260045260245ffd5b8135613d2181613c8b565b60048110613d3157613d316127a6565b815460ff821691508160ff1982161783556020840135613d5081613c97565b62ffff008160081b168362ffffff198416171784555050505050565b5f60018201613d7d57613d7d61362d565b5060010190565b5f82613d9e57634e487b7160e01b5f52601260045260245ffd5b500490565b808201808211156107555761075561362d565b61ffff818116838216019080821115613dd157613dd161362d565b509291505056fe00000000000000000000000009df6a941ee03b1e632904e382e10862fa9cc0e3a164736f6c6343000814000a \ No newline at end of file diff --git a/pallets/services/src/tests.rs b/pallets/services/src/tests.rs deleted file mode 100644 index cbea3bde7..000000000 --- a/pallets/services/src/tests.rs +++ /dev/null @@ -1,1387 +0,0 @@ -// This file is part of Tangle. -// Copyright (C) 2022-2024 Tangle Foundation. -// -// Tangle is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Tangle is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Tangle. If not, see . -use crate::types::ConstraintsOf; - -use super::*; -use frame_support::{assert_err, assert_ok}; -use k256::ecdsa::{SigningKey, VerifyingKey}; -use mock::*; -use sp_core::{bounded_vec, ecdsa, ByteArray, Pair, U256}; -use sp_runtime::{KeyTypeId, Percent}; -use tangle_primitives::{services::*, traits::MultiAssetDelegationInfo}; - -const ALICE: u8 = 1; -const BOB: u8 = 2; -const CHARLIE: u8 = 3; -const DAVE: u8 = 4; -const EVE: u8 = 5; - -const KEYGEN_JOB_ID: u8 = 0; -const SIGN_JOB_ID: u8 = 1; - -fn test_ecdsa_key() -> [u8; 65] { - let (ecdsa_key, _) = ecdsa::Pair::generate(); - let secret = SigningKey::from_slice(&ecdsa_key.seed()) - .expect("Should be able to create a secret key from a seed"); - let verifying_key = VerifyingKey::from(secret); - let public_key = verifying_key.to_encoded_point(false); - public_key.to_bytes().to_vec().try_into().unwrap() -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum MachineKind { - Large, - Medium, - Small, -} - -/// All prices are specified in USD/hr (in u64, so 1e6 = 1$) -fn price_targets(kind: MachineKind) -> PriceTargets { - match kind { - MachineKind::Large => PriceTargets { - cpu: 2_000, - mem: 1_000, - storage_hdd: 100, - storage_ssd: 200, - storage_nvme: 300, - }, - MachineKind::Medium => PriceTargets { - cpu: 1_000, - mem: 500, - storage_hdd: 50, - storage_ssd: 100, - storage_nvme: 150, - }, - MachineKind::Small => { - PriceTargets { cpu: 500, mem: 250, storage_hdd: 25, storage_ssd: 50, storage_nvme: 75 } - }, - } -} - -fn cggmp21_blueprint() -> ServiceBlueprint> { - #[allow(deprecated)] - ServiceBlueprint { - metadata: ServiceMetadata { name: "CGGMP21 TSS".try_into().unwrap(), ..Default::default() }, - manager: BlueprintServiceManager::Evm(CGGMP21_BLUEPRINT), - master_manager_revision: MasterBlueprintServiceManagerRevision::Latest, - jobs: bounded_vec![ - JobDefinition { - metadata: JobMetadata { name: "keygen".try_into().unwrap(), ..Default::default() }, - params: bounded_vec![FieldType::Uint8], - result: bounded_vec![FieldType::List(Box::new(FieldType::Uint8))], - }, - JobDefinition { - metadata: JobMetadata { name: "sign".try_into().unwrap(), ..Default::default() }, - params: bounded_vec![ - FieldType::Uint64, - FieldType::List(Box::new(FieldType::Uint8)) - ], - result: bounded_vec![FieldType::List(Box::new(FieldType::Uint8))], - }, - ], - registration_params: bounded_vec![], - request_params: bounded_vec![], - gadget: Default::default(), - } -} - -#[test] -fn update_mbsm() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - - assert_eq!(Pallet::::mbsm_latest_revision(), 0); - assert_eq!(Pallet::::mbsm_address(0).unwrap(), MBSM); - - // Add a new revision - let new_mbsm = { - let mut v = MBSM; - v.randomize(); - v - }; - - assert_ok!(Services::update_master_blueprint_service_manager( - RuntimeOrigin::root(), - new_mbsm - )); - - assert_eq!(Pallet::::mbsm_latest_revision(), 1); - assert_eq!(Pallet::::mbsm_address(1).unwrap(), new_mbsm); - // Old one should still be there - assert_eq!(Pallet::::mbsm_address(0).unwrap(), MBSM); - // Doesn't exist - assert!(Pallet::::mbsm_address(2).is_err()); - }); -} - -#[test] -fn update_mbsm_not_root() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - let alice = mock_pub_key(ALICE); - assert_err!( - Services::update_master_blueprint_service_manager(RuntimeOrigin::signed(alice), MBSM), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn create_service_blueprint() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - - let alice = mock_pub_key(ALICE); - - let blueprint = cggmp21_blueprint(); - - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint,)); - - let next_id = Services::next_blueprint_id(); - assert_eq!(next_id, 1); - assert_events(vec![RuntimeEvent::Services(crate::Event::BlueprintCreated { - owner: alice, - blueprint_id: next_id - 1, - })]); - - let (_, blueprint) = Services::blueprints(next_id - 1).unwrap(); - - // The MBSM should be set on the blueprint - assert_eq!(Pallet::::mbsm_address_of(&blueprint).unwrap(), MBSM); - // The master manager revision should pinned to a specific revision that is equal to the - // latest revision of the MBSM. - assert_eq!( - blueprint.master_manager_revision, - MasterBlueprintServiceManagerRevision::Specific(0) - ); - }); -} - -#[test] -fn register_on_blueprint() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - - let blueprint = cggmp21_blueprint(); - - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); - - let bob = mock_pub_key(BOB); - let bob_ecdsa_key = test_ecdsa_key(); - - let registration_call = Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { - key: bob_ecdsa_key, - price_targets: price_targets(MachineKind::Large), - }, - Default::default(), - 0, - ); - assert_ok!(registration_call); - - assert_events(vec![RuntimeEvent::Services(crate::Event::Registered { - provider: bob.clone(), - blueprint_id: 0, - preferences: OperatorPreferences { - key: bob_ecdsa_key, - price_targets: price_targets(MachineKind::Large), - }, - registration_args: Default::default(), - })]); - - // The blueprint should be added to my blueprints in my profile. - let profile = OperatorsProfile::::get(bob.clone()).unwrap(); - assert!(profile.blueprints.contains(&0)); - - // if we try to register again, it should fail. - assert_err!( - Services::register( - RuntimeOrigin::signed(bob), - 0, - OperatorPreferences { key: bob_ecdsa_key, price_targets: Default::default() }, - Default::default(), - 0, - ), - crate::Error::::AlreadyRegistered - ); - - // if we try to register with a non active operator, should fail - assert_err!( - Services::register( - RuntimeOrigin::signed(mock_pub_key(10)), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - ), - crate::Error::::OperatorNotActive - ); - }); -} - -#[test] -fn pre_register_on_blueprint() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - - let blueprint = cggmp21_blueprint(); - - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); - - let bob = mock_pub_key(BOB); - - let pre_registration_call = Services::pre_register(RuntimeOrigin::signed(bob.clone()), 0); - assert_ok!(pre_registration_call); - - assert_events(vec![RuntimeEvent::Services(crate::Event::PreRegistration { - operator: bob.clone(), - blueprint_id: 0, - })]); - }); -} - -#[test] -fn update_price_targets() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - - let blueprint = cggmp21_blueprint(); - - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); - - let bob = mock_pub_key(BOB); - let bob_operator_ecdsa_key = test_ecdsa_key(); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { - key: bob_operator_ecdsa_key, - price_targets: price_targets(MachineKind::Small) - }, - Default::default(), - 0, - )); - - assert_eq!( - Operators::::get(0, &bob).unwrap(), - OperatorPreferences { - key: bob_operator_ecdsa_key, - price_targets: price_targets(MachineKind::Small) - } - ); - - assert_events(vec![RuntimeEvent::Services(crate::Event::Registered { - provider: bob.clone(), - blueprint_id: 0, - preferences: OperatorPreferences { - key: bob_operator_ecdsa_key, - price_targets: price_targets(MachineKind::Small), - }, - registration_args: Default::default(), - })]); - - // update price targets - assert_ok!(Services::update_price_targets( - RuntimeOrigin::signed(bob.clone()), - 0, - price_targets(MachineKind::Medium), - )); - - assert_eq!( - Operators::::get(0, &bob).unwrap().price_targets, - price_targets(MachineKind::Medium) - ); - - assert_events(vec![RuntimeEvent::Services(crate::Event::PriceTargetsUpdated { - operator: bob, - blueprint_id: 0, - price_targets: price_targets(MachineKind::Medium), - })]); - - // try to update price targets when not registered - let charlie = mock_pub_key(CHARLIE); - assert_err!( - Services::update_price_targets( - RuntimeOrigin::signed(charlie), - 0, - price_targets(MachineKind::Medium) - ), - crate::Error::::NotRegistered - ); - }); -} - -#[test] -fn unregister_from_blueprint() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - let blueprint = cggmp21_blueprint(); - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); - - let bob = mock_pub_key(BOB); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - assert_ok!(Services::unregister(RuntimeOrigin::signed(bob.clone()), 0)); - assert!(!Operators::::contains_key(0, &bob)); - - // The blueprint should be removed from my blueprints in my profile. - let profile = OperatorsProfile::::get(bob.clone()).unwrap(); - assert!(!profile.blueprints.contains(&0)); - - assert_events(vec![RuntimeEvent::Services(crate::Event::Unregistered { - operator: bob, - blueprint_id: 0, - })]); - - // try to deregister when not registered - let charlie = mock_pub_key(CHARLIE); - assert_err!( - Services::unregister(RuntimeOrigin::signed(charlie), 0), - crate::Error::::NotRegistered - ); - }); -} - -#[test] -fn request_service() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - let blueprint = cggmp21_blueprint(); - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); - let bob = mock_pub_key(BOB); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - let charlie = mock_pub_key(CHARLIE); - assert_ok!(Services::register( - RuntimeOrigin::signed(charlie.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - let dave = mock_pub_key(DAVE); - assert_ok!(Services::register( - RuntimeOrigin::signed(dave.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - - let eve = mock_pub_key(EVE); - assert_ok!(Services::request( - RuntimeOrigin::signed(eve.clone()), - None, - 0, - vec![alice.clone()], - vec![bob.clone(), charlie.clone(), dave.clone()], - Default::default(), - vec![USDC, WETH], - 100, - Asset::Custom(USDC), - 0, - )); - - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); - - // Bob approves the request - assert_ok!(Services::approve( - RuntimeOrigin::signed(bob.clone()), - 0, - Percent::from_percent(10) - )); - - assert_events(vec![RuntimeEvent::Services(crate::Event::ServiceRequestApproved { - operator: bob.clone(), - request_id: 0, - blueprint_id: 0, - approved: vec![bob.clone()], - pending_approvals: vec![charlie.clone(), dave.clone()], - })]); - // Charlie approves the request - assert_ok!(Services::approve( - RuntimeOrigin::signed(charlie.clone()), - 0, - Percent::from_percent(20) - )); - - assert_events(vec![RuntimeEvent::Services(crate::Event::ServiceRequestApproved { - operator: charlie.clone(), - request_id: 0, - blueprint_id: 0, - approved: vec![bob.clone(), charlie.clone()], - pending_approvals: vec![dave.clone()], - })]); - - // Dave approves the request - assert_ok!(Services::approve( - RuntimeOrigin::signed(dave.clone()), - 0, - Percent::from_percent(30) - )); - - assert_events(vec![ - RuntimeEvent::Services(crate::Event::ServiceRequestApproved { - operator: dave.clone(), - request_id: 0, - blueprint_id: 0, - approved: vec![bob.clone(), charlie.clone(), dave.clone()], - pending_approvals: vec![], - }), - RuntimeEvent::Services(crate::Event::ServiceInitiated { - owner: eve, - request_id: 0, - service_id: 0, - blueprint_id: 0, - assets: vec![USDC, WETH], - }), - ]); - - // The request is now fully approved - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 0); - - // Now the service should be initiated - assert!(Instances::::contains_key(0)); - - // The service should also be added to the services for each operator. - let profile = OperatorsProfile::::get(bob).unwrap(); - assert!(profile.services.contains(&0)); - let profile = OperatorsProfile::::get(charlie).unwrap(); - assert!(profile.services.contains(&0)); - let profile = OperatorsProfile::::get(dave).unwrap(); - assert!(profile.services.contains(&0)); - }); -} - -#[test] -fn request_service_with_no_assets() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - let blueprint = cggmp21_blueprint(); - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); - let bob = mock_pub_key(BOB); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - let eve = mock_pub_key(EVE); - assert_err!( - Services::request( - RuntimeOrigin::signed(eve.clone()), - None, - 0, - vec![alice.clone()], - vec![bob.clone()], - Default::default(), - vec![], // no assets - 100, - Asset::Custom(USDC), - 0, - ), - Error::::NoAssetsProvided - ); - }); -} - -#[test] -fn request_service_with_payment_asset() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - let blueprint = cggmp21_blueprint(); - - assert_ok!(Services::create_blueprint( - RuntimeOrigin::signed(alice.clone()), - blueprint.clone() - )); - let bob = mock_pub_key(BOB); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - - let payment = 5 * 10u128.pow(6); // 5 USDC - let charlie = mock_pub_key(CHARLIE); - assert_ok!(Services::request( - RuntimeOrigin::signed(charlie.clone()), - None, - 0, - vec![], - vec![bob.clone()], - Default::default(), - vec![TNT, USDC, WETH], - 100, - Asset::Custom(USDC), - payment, - )); - - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); - - // The Pallet account now has 5 USDC - assert_eq!(Assets::balance(USDC, Services::account_id()), payment); - - // Bob approves the request - assert_ok!(Services::approve( - RuntimeOrigin::signed(bob.clone()), - 0, - Percent::from_percent(10) - )); - - // The request is now fully approved - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 0); - - // The Payment should be now transferred to the MBSM. - let mbsm_address = Pallet::::mbsm_address_of(&blueprint).unwrap(); - let mbsm_account_id = address_to_account_id(mbsm_address); - assert_eq!(Assets::balance(USDC, mbsm_account_id), payment); - // Pallet account should have 0 USDC - assert_eq!(Assets::balance(USDC, Services::account_id()), 0); - - // Now the service should be initiated - assert!(Instances::::contains_key(0)); - }); -} - -#[test] -fn request_service_with_payment_token() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - let blueprint = cggmp21_blueprint(); - - assert_ok!(Services::create_blueprint( - RuntimeOrigin::signed(alice.clone()), - blueprint.clone() - )); - let bob = mock_pub_key(BOB); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - - let payment = 5 * 10u128.pow(6); // 5 USDC - let charlie = mock_pub_key(CHARLIE); - assert_ok!(Services::request( - RuntimeOrigin::signed(address_to_account_id(mock_address(CHARLIE))), - Some(account_id_to_address(charlie.clone())), - 0, - vec![], - vec![bob.clone()], - Default::default(), - vec![TNT, USDC, WETH], - 100, - Asset::Erc20(USDC_ERC20), - payment, - )); - - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); - - // The Pallet address now has 5 USDC - assert_ok!( - Services::query_erc20_balance_of(USDC_ERC20, Services::address()).map(|(b, _)| b), - U256::from(payment) - ); - - // Bob approves the request - assert_ok!(Services::approve( - RuntimeOrigin::signed(bob.clone()), - 0, - Percent::from_percent(10) - )); - - // The request is now fully approved - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 0); - - // The Payment should be now transferred to the MBSM. - let mbsm_address = Pallet::::mbsm_address_of(&blueprint).unwrap(); - assert_ok!( - Services::query_erc20_balance_of(USDC_ERC20, mbsm_address).map(|(b, _)| b), - U256::from(payment) - ); - // Pallet account should have 0 USDC - assert_ok!( - Services::query_erc20_balance_of(USDC_ERC20, Services::address()).map(|(b, _)| b), - U256::from(0) - ); - - // Now the service should be initiated - assert!(Instances::::contains_key(0)); - }); -} - -#[test] -fn reject_service_with_payment_token() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - let blueprint = cggmp21_blueprint(); - - assert_ok!(Services::create_blueprint( - RuntimeOrigin::signed(alice.clone()), - blueprint.clone() - )); - let bob = mock_pub_key(BOB); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - - let payment = 5 * 10u128.pow(6); // 5 USDC - let charlie_address = mock_address(CHARLIE); - let charlie_evm_account_id = address_to_account_id(charlie_address); - let before_balance = Services::query_erc20_balance_of(USDC_ERC20, charlie_address) - .map(|(b, _)| b) - .unwrap_or_default(); - assert_ok!(Services::request( - RuntimeOrigin::signed(charlie_evm_account_id), - Some(charlie_address), - 0, - vec![], - vec![bob.clone()], - Default::default(), - vec![TNT, USDC, WETH], - 100, - Asset::Erc20(USDC_ERC20), - payment, - )); - - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); - - // The Pallet address now has 5 USDC - assert_ok!( - Services::query_erc20_balance_of(USDC_ERC20, Services::address()).map(|(b, _)| b), - U256::from(payment) - ); - // Charlie Balance should be decreased by 5 USDC - assert_ok!( - Services::query_erc20_balance_of(USDC_ERC20, charlie_address).map(|(b, _)| b), - before_balance - U256::from(payment) - ); - - // Bob rejects the request - assert_ok!(Services::reject(RuntimeOrigin::signed(bob.clone()), 0)); - - // The Payment should be now refunded to the requester. - // Pallet account should have 0 USDC - assert_ok!( - Services::query_erc20_balance_of(USDC_ERC20, Services::address()).map(|(b, _)| b), - U256::from(0) - ); - // Charlie Balance should be back to the original - assert_ok!( - Services::query_erc20_balance_of(USDC_ERC20, charlie_address).map(|(b, _)| b), - before_balance - ); - }); -} - -#[test] -fn reject_service_with_payment_asset() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - let blueprint = cggmp21_blueprint(); - - assert_ok!(Services::create_blueprint( - RuntimeOrigin::signed(alice.clone()), - blueprint.clone() - )); - let bob = mock_pub_key(BOB); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - - let payment = 5 * 10u128.pow(6); // 5 USDC - let charlie = mock_pub_key(CHARLIE); - let before_balance = Assets::balance(USDC, charlie.clone()); - assert_ok!(Services::request( - RuntimeOrigin::signed(charlie.clone()), - None, - 0, - vec![], - vec![bob.clone()], - Default::default(), - vec![TNT, USDC, WETH], - 100, - Asset::Custom(USDC), - payment, - )); - - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); - - // The Pallet account now has 5 USDC - assert_eq!(Assets::balance(USDC, Services::account_id()), payment); - // Charlie Balance should be decreased by 5 USDC - assert_eq!(Assets::balance(USDC, charlie.clone()), before_balance - payment); - - // Bob rejects the request - assert_ok!(Services::reject(RuntimeOrigin::signed(bob.clone()), 0)); - - // The Payment should be now refunded to the requester. - // Pallet account should have 0 USDC - assert_eq!(Assets::balance(USDC, Services::account_id()), 0); - // Charlie Balance should be back to the original - assert_eq!(Assets::balance(USDC, charlie), before_balance); - }); -} - -#[test] -fn job_calls() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - let blueprint = cggmp21_blueprint(); - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); - let bob = mock_pub_key(BOB); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - let charlie = mock_pub_key(CHARLIE); - assert_ok!(Services::register( - RuntimeOrigin::signed(charlie.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - let dave = mock_pub_key(DAVE); - assert_ok!(Services::register( - RuntimeOrigin::signed(dave.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - - let eve = mock_pub_key(EVE); - assert_ok!(Services::request( - RuntimeOrigin::signed(eve.clone()), - None, - 0, - vec![alice.clone()], - vec![bob.clone(), charlie.clone(), dave.clone()], - Default::default(), - vec![WETH], - 100, - Asset::Custom(USDC), - 0, - )); - - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); - assert_ok!(Services::approve( - RuntimeOrigin::signed(bob.clone()), - 0, - Percent::from_percent(10) - )); - - assert_ok!(Services::approve( - RuntimeOrigin::signed(charlie.clone()), - 0, - Percent::from_percent(10) - )); - - assert_ok!(Services::approve( - RuntimeOrigin::signed(dave.clone()), - 0, - Percent::from_percent(10) - )); - assert!(Instances::::contains_key(0)); - assert_events(vec![RuntimeEvent::Services(crate::Event::ServiceInitiated { - owner: eve.clone(), - request_id: 0, - service_id: 0, - blueprint_id: 0, - assets: vec![WETH], - })]); - - // now we can call the jobs - let job_call_id = 0; - assert_ok!(Services::call( - RuntimeOrigin::signed(eve.clone()), - 0, - 0, - bounded_vec![Field::Uint8(2)], - )); - - assert!(JobCalls::::contains_key(0, job_call_id)); - assert_events(vec![RuntimeEvent::Services(crate::Event::JobCalled { - caller: eve, - service_id: 0, - job: 0, - call_id: job_call_id, - args: vec![Field::Uint8(2)], - })]); - }); -} - -#[test] -fn job_result() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - let alice = mock_pub_key(ALICE); - let blueprint = cggmp21_blueprint(); - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); - let bob = mock_pub_key(BOB); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - let charlie = mock_pub_key(CHARLIE); - assert_ok!(Services::register( - RuntimeOrigin::signed(charlie.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - let dave = mock_pub_key(DAVE); - assert_ok!(Services::register( - RuntimeOrigin::signed(dave.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - - let eve = mock_pub_key(EVE); - assert_ok!(Services::request( - RuntimeOrigin::signed(eve.clone()), - None, - 0, - vec![alice.clone()], - vec![bob.clone(), charlie.clone(), dave.clone()], - Default::default(), - vec![WETH], - 100, - Asset::Custom(USDC), - 0, - )); - - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); - assert_ok!(Services::approve( - RuntimeOrigin::signed(bob.clone()), - 0, - Percent::from_percent(10) - )); - - assert_ok!(Services::approve( - RuntimeOrigin::signed(charlie.clone()), - 0, - Percent::from_percent(10) - )); - - assert_ok!(Services::approve( - RuntimeOrigin::signed(dave.clone()), - 0, - Percent::from_percent(10) - )); - assert!(Instances::::contains_key(0)); - assert_events(vec![RuntimeEvent::Services(crate::Event::ServiceInitiated { - owner: eve.clone(), - request_id: 0, - service_id: 0, - blueprint_id: 0, - assets: vec![WETH], - })]); - - // now we can call the jobs - let keygen_job_call_id = 0; - - assert_ok!(Services::call( - RuntimeOrigin::signed(eve.clone()), - 0, - 0, - bounded_vec![Field::Uint8(2)] - )); - - assert!(JobCalls::::contains_key(0, keygen_job_call_id)); - // now we can set the job result - let key_type = KeyTypeId(*b"mdkg"); - let dkg = sp_io::crypto::ecdsa_generate(key_type, None); - assert_ok!(Services::submit_result( - RuntimeOrigin::signed(bob.clone()), - 0, - keygen_job_call_id, - bounded_vec![Field::from(BoundedVec::try_from(dkg.to_raw_vec()).unwrap())], - )); - - // submit signing job - let _signing_job_call_id = 1; - let data_hash = sp_core::keccak_256(&[1; 32]); - - assert_ok!(Services::call( - RuntimeOrigin::signed(eve.clone()), - 0, - SIGN_JOB_ID, - bounded_vec![ - Field::Uint64(keygen_job_call_id), - Field::from(BoundedVec::try_from(data_hash.to_vec()).unwrap()) - ], - )); - - // now we can set the job result - let signature = sp_io::crypto::ecdsa_sign_prehashed(key_type, &dkg, &data_hash).unwrap(); - let mut signature_bytes = signature.to_raw_vec(); - // fix the v value (it should be 27 or 28). - signature_bytes[64] += 27u8; - // For some reason, the signature is not being verified. - // in EVM, ecrecover is used to verify the signature, but it returns - // 0x000000000000000000000000000000000000000 as the address of the signer. - // even though the signature is correct, and we have the precomiles in the runtime. - // - // assert_ok!(Services::submit_result( - // RuntimeOrigin::signed(bob.clone()), - // 0, - // signing_job_call_id, - // bounded_vec![Field::Bytes(signature_bytes.try_into().unwrap())], - // )); - }); -} - -struct Deployment { - blueprint_id: u64, - service_id: u64, - bob_exposed_restake_percentage: Percent, -} - -/// A Helper function that creates a blueprint and service instance -fn deploy() -> Deployment { - let alice = mock_pub_key(ALICE); - let blueprint = cggmp21_blueprint(); - let blueprint_id = Services::next_blueprint_id(); - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); - - let bob = mock_pub_key(BOB); - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - blueprint_id, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - - let eve = mock_pub_key(EVE); - let service_id = Services::next_instance_id(); - assert_ok!(Services::request( - RuntimeOrigin::signed(eve.clone()), - None, - blueprint_id, - vec![alice.clone()], - vec![bob.clone()], - Default::default(), - vec![WETH], - 100, - Asset::Custom(USDC), - 0, - )); - - assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); - - let bob_exposed_restake_percentage = Percent::from_percent(10); - assert_ok!(Services::approve( - RuntimeOrigin::signed(bob.clone()), - service_id, - bob_exposed_restake_percentage, - )); - - assert!(Instances::::contains_key(service_id)); - - Deployment { blueprint_id, service_id, bob_exposed_restake_percentage } -} - -#[test] -fn unapplied_slash() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - let Deployment { blueprint_id, service_id, bob_exposed_restake_percentage } = deploy(); - let eve = mock_pub_key(EVE); - let bob = mock_pub_key(BOB); - // now we can call the jobs - let job_call_id = Services::next_job_call_id(); - assert_ok!(Services::call( - RuntimeOrigin::signed(eve.clone()), - service_id, - KEYGEN_JOB_ID, - bounded_vec![Field::Uint8(1)], - )); - // sumbit an invalid result - let mut dkg = vec![0u8; 33]; - dkg[32] = 1; - assert_ok!(Services::submit_result( - RuntimeOrigin::signed(bob.clone()), - 0, - job_call_id, - bounded_vec![Field::from(BoundedVec::try_from(dkg).unwrap())], - )); - - let slash_percent = Percent::from_percent(50); - let service = Services::services(service_id).unwrap(); - let slashing_origin = - Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); - - // Slash the operator for the invalid result - assert_ok!(Services::slash( - RuntimeOrigin::signed(slashing_origin.clone()), - bob.clone(), - service_id, - slash_percent - )); - - assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 1); - - let bob_slash = ::OperatorDelegationManager::get_operator_stake(&bob); - let expected_slash_amount = - (slash_percent * bob_exposed_restake_percentage).mul_floor(bob_slash); - - assert_events(vec![RuntimeEvent::Services(crate::Event::UnappliedSlash { - era: 0, - index: 0, - operator: bob.clone(), - blueprint_id, - service_id, - amount: expected_slash_amount, - })]); - }); -} - -#[test] -fn unapplied_slash_with_invalid_origin() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - let Deployment { service_id, .. } = deploy(); - let eve = mock_pub_key(EVE); - let bob = mock_pub_key(BOB); - let slash_percent = Percent::from_percent(50); - // Try to slash with an invalid origin - assert_err!( - Services::slash( - RuntimeOrigin::signed(eve.clone()), - bob.clone(), - service_id, - slash_percent - ), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn slash_account_not_an_operator() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - let Deployment { service_id, .. } = deploy(); - let karen = mock_pub_key(23); - - let service = Services::services(service_id).unwrap(); - let slashing_origin = - Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); - let slash_percent = Percent::from_percent(50); - // Try to slash an operator that is not active in this service - assert_err!( - Services::slash( - RuntimeOrigin::signed(slashing_origin.clone()), - karen.clone(), - service_id, - slash_percent - ), - Error::::OffenderNotOperator - ); - }); -} - -#[test] -fn dispute() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - let Deployment { blueprint_id, service_id, bob_exposed_restake_percentage } = deploy(); - let bob = mock_pub_key(BOB); - let slash_percent = Percent::from_percent(50); - let service = Services::services(service_id).unwrap(); - let slashing_origin = - Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); - - // Slash the operator for the invalid result - assert_ok!(Services::slash( - RuntimeOrigin::signed(slashing_origin.clone()), - bob.clone(), - service_id, - slash_percent - )); - - assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 1); - - let era = 0; - let slash_index = 0; - - // Dispute the slash - let dispute_origin = - Services::query_dispute_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); - - assert_ok!(Services::dispute( - RuntimeOrigin::signed(dispute_origin.clone()), - era, - slash_index - )); - - assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 0); - - let bob_slash = ::OperatorDelegationManager::get_operator_stake(&bob); - let expected_slash_amount = - (slash_percent * bob_exposed_restake_percentage).mul_floor(bob_slash); - - assert_events(vec![RuntimeEvent::Services(crate::Event::SlashDiscarded { - era: 0, - index: 0, - operator: bob.clone(), - blueprint_id, - service_id, - amount: expected_slash_amount, - })]); - }); -} - -#[test] -fn dispute_with_unauthorized_origin() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - let Deployment { service_id, .. } = deploy(); - let eve = mock_pub_key(EVE); - let bob = mock_pub_key(BOB); - let slash_percent = Percent::from_percent(50); - let service = Services::services(service_id).unwrap(); - let slashing_origin = - Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); - - // Slash the operator for the invalid result - assert_ok!(Services::slash( - RuntimeOrigin::signed(slashing_origin.clone()), - bob.clone(), - service_id, - slash_percent - )); - - assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 1); - - let era = 0; - let slash_index = 0; - - // Try to dispute with an invalid origin - assert_err!( - Services::dispute(RuntimeOrigin::signed(eve.clone()), era, slash_index), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn dispute_an_already_applied_slash() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - System::set_block_number(1); - let Deployment { service_id, .. } = deploy(); - let eve = mock_pub_key(EVE); - let bob = mock_pub_key(BOB); - let slash_percent = Percent::from_percent(50); - let service = Services::services(service_id).unwrap(); - let slashing_origin = - Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); - - // Slash the operator for the invalid result - assert_ok!(Services::slash( - RuntimeOrigin::signed(slashing_origin.clone()), - bob.clone(), - service_id, - slash_percent - )); - - assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 1); - - let era = 0; - let slash_index = 0; - // Simulate a slash happening - UnappliedSlashes::::remove(era, slash_index); - - // Try to dispute an already applied slash - assert_err!( - Services::dispute(RuntimeOrigin::signed(eve.clone()), era, slash_index), - Error::::UnappliedSlashNotFound - ); - }); -} - -#[test] -fn hooks() { - new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - - let alice = mock_pub_key(ALICE); - let bob = mock_pub_key(BOB); - let charlie = mock_pub_key(CHARLIE); - let blueprint = ServiceBlueprint { - metadata: ServiceMetadata { - name: "Hooks Tests".try_into().unwrap(), - ..Default::default() - }, - manager: BlueprintServiceManager::Evm(HOOKS_TEST), - master_manager_revision: MasterBlueprintServiceManagerRevision::Latest, - jobs: bounded_vec![JobDefinition { - metadata: JobMetadata { name: "foo".try_into().unwrap(), ..Default::default() }, - params: bounded_vec![], - result: bounded_vec![], - },], - registration_params: bounded_vec![], - request_params: bounded_vec![], - gadget: Default::default(), - }; - - // OnBlueprintCreated hook should be called - assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); - assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnBlueprintCreated()")]); - - // OnRegister hook should be called - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnRegister()")]); - - // OnUnregister hook should be called - assert_ok!(Services::unregister(RuntimeOrigin::signed(bob.clone()), 0)); - assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnUnregister()")]); - - // Register again to continue testing - assert_ok!(Services::register( - RuntimeOrigin::signed(bob.clone()), - 0, - OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, - Default::default(), - 0, - )); - - // OnUpdatePriceTargets hook should be called - assert_ok!(Services::update_price_targets( - RuntimeOrigin::signed(bob.clone()), - 0, - price_targets(MachineKind::Medium), - )); - assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnUpdatePriceTargets()")]); - - // OnRequest hook should be called - assert_ok!(Services::request( - RuntimeOrigin::signed(charlie.clone()), - None, - 0, - vec![alice.clone()], - vec![bob.clone()], - Default::default(), - vec![USDC, WETH], - 100, - Asset::Custom(USDC), - 0, - )); - assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnRequest()")]); - - // OnReject hook should be called - assert_ok!(Services::reject(RuntimeOrigin::signed(bob.clone()), 0)); - assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnReject()")]); - - // OnApprove hook should be called - // OnServiceInitialized is also called - assert_ok!(Services::approve( - RuntimeOrigin::signed(bob.clone()), - 0, - Percent::from_percent(10) - )); - assert_evm_logs(&[ - evm_log!(HOOKS_TEST, b"OnApprove()"), - evm_log!(HOOKS_TEST, b"OnServiceInitialized()"), - ]); - - // OnJobCall hook should be called - assert_ok!(Services::call(RuntimeOrigin::signed(charlie.clone()), 0, 0, bounded_vec![],)); - assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnJobCall()")]); - - // OnJobResult hook should be called - assert_ok!(Services::submit_result( - RuntimeOrigin::signed(bob.clone()), - 0, - 0, - bounded_vec![], - )); - assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnJobResult()")]); - // OnServiceTermination hook should be called - assert_ok!(Services::terminate(RuntimeOrigin::signed(charlie.clone()), 0)); - assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnServiceTermination()")]); - }); -} diff --git a/pallets/services/src/tests/asset_security.rs b/pallets/services/src/tests/asset_security.rs new file mode 100644 index 000000000..2edc0bdaa --- /dev/null +++ b/pallets/services/src/tests/asset_security.rs @@ -0,0 +1,417 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use frame_support::{assert_err, assert_ok}; + +#[test] +fn test_security_requirements_validation() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + let bob = mock_pub_key(BOB); + let eve = mock_pub_key(EVE); + // Register operator + assert_ok!(join_and_register(bob.clone(), 0, test_ecdsa_key(), Default::default(), 1000)); + // Test Case 1: Invalid min exposure (0%) + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[0, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + ), + Error::::InvalidSecurityRequirements + ); + // Test Case 2: Invalid max exposure (0%) + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 0])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + ), + Error::::InvalidSecurityRequirements + ); + // Test Case 3: Min exposure > Max exposure + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[30, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + ), + Error::::InvalidSecurityRequirements + ); + // Test Case 4: Max exposure > 100% + // NOTE: this one passes because the max exposure is capped at 100% anyway + // This enforcement is done in the [`Percent`] type + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 101])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + // Test Case 5: Valid security requirements + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + }); +} + +#[test] +fn test_security_commitment_validation() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + let bob = mock_pub_key(BOB); + let eve = mock_pub_key(EVE); + // Register operator + assert_ok!(join_and_register(bob.clone(), 0, test_ecdsa_key(), Default::default(), 1000)); + // Create service request + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20]),], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + // Test Case 1: Commitment below minimum exposure + assert_err!( + Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 5)], + ), + Error::::InvalidSecurityCommitments + ); + // Test Case 2: Commitment above maximum exposure + assert_err!( + Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 25)], + ), + Error::::InvalidSecurityCommitments + ); + // Test Case 3: Missing required asset commitment (native asset) + assert_err!( + Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 15)], + ), + Error::::InvalidSecurityCommitments + ); + // Test Case 4: Wrong asset provided + assert_err!( + Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(USDC, 15)], + ), + Error::::InvalidSecurityCommitments + ); + // Test Case 4: Valid commitment + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 15), get_security_commitment(TNT, 15)], + )); + }); +} + +#[test] +fn test_exposure_calculations() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + let bob = mock_pub_key(BOB); + let charlie = mock_pub_key(CHARLIE); + let dave = mock_pub_key(DAVE); + let eve = mock_pub_key(EVE); + + // Register operators + for operator in [bob.clone(), charlie.clone(), dave.clone()] { + assert_ok!(join_and_register(operator, 0, test_ecdsa_key(), Default::default(), 1000)); + } + + // Create service with multiple assets and exposure requirements + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone(), dave.clone()], + Default::default(), + vec![ + get_security_requirement(WETH, &[10, 30]), + get_security_requirement(USDC, &[15, 25]), + ], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 3 }, + )); + + // Test different exposure combinations + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![ + get_security_commitment(WETH, 20), + get_security_commitment(USDC, 20), + get_security_commitment(TNT, 20) + ], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(charlie.clone()), + 0, + vec![ + get_security_commitment(WETH, 25), + get_security_commitment(USDC, 15), + get_security_commitment(TNT, 10), + ], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(dave.clone()), + 0, + vec![ + get_security_commitment(WETH, 15), + get_security_commitment(USDC, 20), + get_security_commitment(TNT, 10), + ], + )); + + let service = Instances::::get(0).unwrap(); + let operator_security_commitments = service.operator_security_commitments; + + // Verify service is initiated with correct exposures + assert!(Instances::::contains_key(0)); + let events = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect::>(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::ServiceInitiated { + owner: eve.clone(), + request_id: 0, + service_id: 0, + blueprint_id: 0, + operator_security_commitments, + }))); + }); +} + +#[test] +fn test_exposure_limits() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + let bob = mock_pub_key(BOB); + let charlie = mock_pub_key(CHARLIE); + let dave = mock_pub_key(DAVE); + let eve = mock_pub_key(EVE); + + // Register operators + for operator in [bob.clone(), charlie.clone(), dave.clone()] { + assert_ok!(join_and_register(operator, 0, test_ecdsa_key(), Default::default(), 1000)); + } + + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone(), dave.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[40, 60])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 3 }, + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 50), get_security_commitment(TNT, 50)], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(charlie.clone()), + 0, + vec![get_security_commitment(WETH, 50), get_security_commitment(TNT, 50)], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(dave.clone()), + 0, + vec![get_security_commitment(WETH, 50), get_security_commitment(TNT, 50)], + )); + + // Create second service that shares the same security (overlapping exposures) + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[40, 60])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 2 }, + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 1, + vec![get_security_commitment(WETH, 50), get_security_commitment(TNT, 50)], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(charlie.clone()), + 1, + vec![get_security_commitment(WETH, 50), get_security_commitment(TNT, 50)], + )); + + // Create third service with different asset (USDC) + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[40, 60])], + 100, + Asset::Custom(WETH), + 0, + MembershipModel::Fixed { min_operators: 2 }, + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 2, + vec![get_security_commitment(USDC, 50), get_security_commitment(TNT, 50)], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(charlie.clone()), + 2, + vec![get_security_commitment(USDC, 50), get_security_commitment(TNT, 50)], + )); + + // Verify all services are active + let service0 = Instances::::get(0).unwrap(); + let service1 = Instances::::get(1).unwrap(); + let service2 = Instances::::get(2).unwrap(); + + // Verify events for service initiation and approvals + let events = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect::>(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::ServiceInitiated { + owner: eve.clone(), + request_id: 0, + service_id: 0, + blueprint_id: 0, + operator_security_commitments: service0.operator_security_commitments.clone(), + }))); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::ServiceInitiated { + owner: eve.clone(), + request_id: 1, + service_id: 1, + blueprint_id: 0, + operator_security_commitments: service1.operator_security_commitments.clone(), + }))); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::ServiceInitiated { + owner: eve.clone(), + request_id: 2, + service_id: 2, + blueprint_id: 0, + operator_security_commitments: service2.operator_security_commitments.clone(), + }))); + }); +} diff --git a/pallets/services/src/tests/blueprint.rs b/pallets/services/src/tests/blueprint.rs new file mode 100644 index 000000000..74090ffc5 --- /dev/null +++ b/pallets/services/src/tests/blueprint.rs @@ -0,0 +1,91 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use frame_support::{assert_err, assert_ok}; + +#[test] +fn update_mbsm() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + assert_eq!(Pallet::::mbsm_latest_revision(), 0); + assert_eq!(Pallet::::mbsm_address(0).unwrap(), MBSM); + + // Add a new revision + let new_mbsm = { + let mut v = MBSM; + v.randomize(); + v + }; + + assert_ok!(Services::update_master_blueprint_service_manager( + RuntimeOrigin::root(), + new_mbsm + )); + + assert_eq!(Pallet::::mbsm_latest_revision(), 1); + assert_eq!(Pallet::::mbsm_address(1).unwrap(), new_mbsm); + // Old one should still be there + assert_eq!(Pallet::::mbsm_address(0).unwrap(), MBSM); + // Doesn't exist + assert!(Pallet::::mbsm_address(2).is_err()); + }); +} + +#[test] +fn update_mbsm_not_root() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let alice = mock_pub_key(ALICE); + assert_err!( + Services::update_master_blueprint_service_manager(RuntimeOrigin::signed(alice), MBSM), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn create_service_blueprint() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint,)); + + let next_id = Services::next_blueprint_id(); + assert_eq!(next_id, 1); + System::assert_has_event(RuntimeEvent::Services(crate::Event::BlueprintCreated { + owner: alice, + blueprint_id: next_id - 1, + })); + + let (_, blueprint) = Services::blueprints(next_id - 1).unwrap(); + + // The MBSM should be set on the blueprint + assert_eq!(Pallet::::mbsm_address_of(&blueprint).unwrap(), MBSM); + // The master manager revision should pinned to a specific revision that is equal to the + // latest revision of the MBSM. + assert_eq!( + blueprint.master_manager_revision, + MasterBlueprintServiceManagerRevision::Specific(0) + ); + }); +} diff --git a/pallets/services/src/tests/hooks.rs b/pallets/services/src/tests/hooks.rs new file mode 100644 index 000000000..061061498 --- /dev/null +++ b/pallets/services/src/tests/hooks.rs @@ -0,0 +1,152 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use frame_support::assert_ok; + +#[test] +fn hooks() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + let alice = mock_pub_key(ALICE); + let bob = mock_pub_key(BOB); + let charlie = mock_pub_key(CHARLIE); + let blueprint = ServiceBlueprint { + metadata: ServiceMetadata { + name: "Hooks Tests".try_into().unwrap(), + ..Default::default() + }, + manager: BlueprintServiceManager::Evm(HOOKS_TEST), + master_manager_revision: MasterBlueprintServiceManagerRevision::Latest, + jobs: bounded_vec![JobDefinition { + metadata: JobMetadata { name: "foo".try_into().unwrap(), ..Default::default() }, + params: bounded_vec![], + result: bounded_vec![], + },], + registration_params: bounded_vec![], + request_params: bounded_vec![], + gadget: Default::default(), + supported_membership_models: bounded_vec![ + MembershipModelType::Fixed, + MembershipModelType::Dynamic, + ], + }; + + // OnBlueprintCreated hook should be called + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnBlueprintCreated()")]); + + // OnRegister hook should be called + assert_ok!(join_and_register(bob.clone(), 0, test_ecdsa_key(), Default::default(), 1000)); + assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnRegister()")]); + + // OnUnregister hook should be called + assert_ok!(Services::unregister(RuntimeOrigin::signed(bob.clone()), 0)); + assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnUnregister()")]); + + // Register again to continue testing + assert_ok!(Services::register( + RuntimeOrigin::signed(bob.clone()), + 0, + OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, + Default::default(), + 0, + )); + + // OnUpdatePriceTargets hook should be called + assert_ok!(Services::update_price_targets( + RuntimeOrigin::signed(bob.clone()), + 0, + price_targets(MachineKind::Medium), + )); + assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnUpdatePriceTargets()")]); + + // OnRequest hook should be called + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![ + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[10, 20]) + ], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnRequest()")]); + + // OnReject hook should be called + assert_ok!(Services::reject(RuntimeOrigin::signed(bob.clone()), 0)); + assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnReject()")]); + + // Create another request to test remaining hooks + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![ + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[10, 20]) + ], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // OnApprove hook should be called + // OnServiceInitialized is also called + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 1, + vec![ + get_security_commitment(USDC, 10), + get_security_commitment(WETH, 10), + get_security_commitment(TNT, 10) + ], + )); + assert_evm_logs(&[ + evm_log!(HOOKS_TEST, b"OnApprove()"), + evm_log!(HOOKS_TEST, b"OnServiceInitialized()"), + ]); + + // OnJobCall hook should be called + assert_ok!(Services::call(RuntimeOrigin::signed(charlie.clone()), 0, 0, bounded_vec![],)); + assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnJobCall()")]); + + // OnJobResult hook should be called + assert_ok!(Services::submit_result( + RuntimeOrigin::signed(bob.clone()), + 0, + 0, + bounded_vec![], + )); + assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnJobResult()")]); + + // OnServiceTermination hook should be called + assert_ok!(Services::terminate(RuntimeOrigin::signed(charlie.clone()), 0)); + assert_evm_logs(&[evm_log!(HOOKS_TEST, b"OnServiceTermination()")]); + }); +} diff --git a/pallets/services/src/tests/jobs.rs b/pallets/services/src/tests/jobs.rs new file mode 100644 index 000000000..4e8d72977 --- /dev/null +++ b/pallets/services/src/tests/jobs.rs @@ -0,0 +1,541 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use frame_support::{assert_err, assert_ok}; +use sp_core::{offchain::KeyTypeId, ByteArray}; + +#[test] +fn job_calls() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register multiple operators + let bob = mock_pub_key(BOB); + assert_ok!(join_and_register(bob.clone(), 0, test_ecdsa_key(), Default::default(), 1000)); + + let charlie = mock_pub_key(CHARLIE); + assert_ok!(join_and_register( + charlie.clone(), + 0, + test_ecdsa_key(), + Default::default(), + 1000 + )); + + let dave = mock_pub_key(DAVE); + assert_ok!(join_and_register(dave.clone(), 0, test_ecdsa_key(), Default::default(), 1000)); + + let eve = mock_pub_key(EVE); + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone(), dave.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 3 }, + )); + + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); + + // All operators approve with security commitments + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(charlie.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(dave.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + + let service = Instances::::get(0).unwrap(); + let operator_security_commitments = service.operator_security_commitments; + + let events = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect::>(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::ServiceInitiated { + owner: eve.clone(), + request_id: 0, + service_id: 0, + blueprint_id: 0, + operator_security_commitments, + }))); + + // now we can call the jobs + let job_call_id = 0; + assert_ok!(Services::call( + RuntimeOrigin::signed(eve.clone()), + 0, + 0, + bounded_vec![Field::Uint8(2)], + )); + + assert!(JobCalls::::contains_key(0, job_call_id)); + let events = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect::>(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::JobCalled { + caller: eve, + service_id: 0, + job: 0, + call_id: job_call_id, + args: vec![Field::Uint8(2)], + }))); + }); +} + +#[test] +fn job_result() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register multiple operators + let bob = mock_pub_key(BOB); + assert_ok!(join_and_register(bob.clone(), 0, test_ecdsa_key(), Default::default(), 1000)); + + let charlie = mock_pub_key(CHARLIE); + assert_ok!(join_and_register( + charlie.clone(), + 0, + test_ecdsa_key(), + Default::default(), + 1000 + )); + + let dave = mock_pub_key(DAVE); + assert_ok!(join_and_register(dave.clone(), 0, test_ecdsa_key(), Default::default(), 1000)); + + let eve = mock_pub_key(EVE); + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone(), dave.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 3 }, + )); + + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); + + // All operators approve with security commitments + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(charlie.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(dave.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + + let service = Instances::::get(0).unwrap(); + let operator_security_commitments = service.operator_security_commitments; + + let events = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect::>(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::ServiceInitiated { + owner: eve.clone(), + request_id: 0, + service_id: 0, + blueprint_id: 0, + operator_security_commitments, + }))); + + // now we can call the jobs + let keygen_job_call_id = 0; + + assert_ok!(Services::call( + RuntimeOrigin::signed(eve.clone()), + 0, + 0, + bounded_vec![Field::Uint8(2)] + )); + + assert!(JobCalls::::contains_key(0, keygen_job_call_id)); + + // now we can set the job result + let key_type = KeyTypeId(*b"mdkg"); + let dkg = sp_io::crypto::ecdsa_generate(key_type, None); + assert_ok!(Services::submit_result( + RuntimeOrigin::signed(bob.clone()), + 0, + keygen_job_call_id, + bounded_vec![Field::from(BoundedVec::try_from(dkg.to_raw_vec()).unwrap())], + )); + + // submit signing job + + let data_hash = sp_core::keccak_256(&[1; 32]); + + assert_ok!(Services::call( + RuntimeOrigin::signed(eve.clone()), + 0, + SIGN_JOB_ID, + bounded_vec![ + Field::Uint64(keygen_job_call_id), + Field::from(BoundedVec::try_from(data_hash.to_vec()).unwrap()) + ], + )); + + // now we can set the job result + let signature = sp_io::crypto::ecdsa_sign_prehashed(key_type, &dkg, &data_hash).unwrap(); + let mut signature_bytes = signature.to_raw_vec(); + // fix the v value (it should be 27 or 28). + signature_bytes[64] += 27u8; + + // For some reason, the signature is not being verified. + // in EVM, ecrecover is used to verify the signature, but it returns + // 0x000000000000000000000000000000000000000 as the address of the signer. + // even though the signature is correct, and we have the precomiles in the runtime. + // + // let signing_job_call_id = 1; + // assert_ok!(Services::submit_result( + // RuntimeOrigin::signed(bob.clone()), + // 0, + // signing_job_call_id, + // bounded_vec![Field::Bytes(signature_bytes.try_into().unwrap())], + // )); + }); +} + +#[test] +fn test_concurrent_job_execution() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operators + let bob = mock_pub_key(BOB); + let charlie = mock_pub_key(CHARLIE); + let dave = mock_pub_key(DAVE); + let eve = mock_pub_key(EVE); + + for operator in [bob.clone(), charlie.clone(), dave.clone()] { + assert_ok!(join_and_register( + operator.clone(), + 0, + test_ecdsa_key(), + Default::default(), + 1000 + )); + } + + // Create and approve service + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone(), dave.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 3 }, + )); + + for operator in [bob.clone(), charlie.clone(), dave.clone()] { + assert_ok!(Services::approve( + RuntimeOrigin::signed(operator), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + } + + // Submit multiple concurrent job calls + assert_ok!(Services::call( + RuntimeOrigin::signed(eve.clone()), + 0, + 0, + bounded_vec![Field::Uint8(1)], + )); + + assert_ok!(Services::call( + RuntimeOrigin::signed(eve.clone()), + 0, + 0, + bounded_vec![Field::Uint8(2)], + )); + + // Verify both jobs are tracked + assert!(JobCalls::::contains_key(0, 0)); + assert!(JobCalls::::contains_key(0, 1)); + + // Submit results for both jobs + let key_type = KeyTypeId(*b"mdkg"); + let dkg = sp_io::crypto::ecdsa_generate(key_type, None); + + assert_ok!(Services::submit_result( + RuntimeOrigin::signed(bob.clone()), + 0, + 0, + bounded_vec![Field::from(BoundedVec::try_from(dkg.to_raw_vec()).unwrap())], + )); + + assert_ok!(Services::submit_result( + RuntimeOrigin::signed(bob.clone()), + 0, + 1, + bounded_vec![Field::from(BoundedVec::try_from(dkg.to_raw_vec()).unwrap())], + )); + }); +} + +#[test] +fn test_result_submission_non_operators() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operators + let bob = mock_pub_key(BOB); + let charlie = mock_pub_key(CHARLIE); + let dave = mock_pub_key(DAVE); + let eve = mock_pub_key(EVE); + + for operator in [bob.clone(), charlie.clone()] { + assert_ok!(join_and_register( + operator.clone(), + 0, + test_ecdsa_key(), + Default::default(), + 1000 + )); + } + + // Create and approve service + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 2 }, + )); + + for operator in [bob.clone(), charlie.clone()] { + assert_ok!(Services::approve( + RuntimeOrigin::signed(operator), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + } + + // Submit job call + assert_ok!(Services::call( + RuntimeOrigin::signed(eve.clone()), + 0, + 0, + bounded_vec![Field::Uint8(1)], + )); + + // Non-operator tries to submit result + let key_type = KeyTypeId(*b"mdkg"); + let dkg = sp_io::crypto::ecdsa_generate(key_type, None); + + assert_err!( + Services::submit_result( + RuntimeOrigin::signed(dave.clone()), + 0, + 0, + bounded_vec![Field::from(BoundedVec::try_from(dkg.to_raw_vec()).unwrap())], + ), + Error::::NotRegistered + ); + }); +} + +#[test] +fn test_invalid_result_formats() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operators + let bob = mock_pub_key(BOB); + let eve = mock_pub_key(EVE); + + assert_ok!(join_and_register(bob.clone(), 0, test_ecdsa_key(), Default::default(), 1000)); + + // Create and approve service + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + + // Submit job call + assert_ok!(Services::call( + RuntimeOrigin::signed(eve.clone()), + 0, + 0, + bounded_vec![Field::Uint8(1)], + )); + + // Try to submit result with wrong field type + assert_err!( + Services::submit_result( + RuntimeOrigin::signed(bob.clone()), + 0, + 0, + bounded_vec![Field::String("invalid".try_into().unwrap())], + ), + Error::::TypeCheck(TypeCheckError::ArgumentTypeMismatch { + index: 0, + expected: FieldType::List(Box::new(FieldType::String)), + actual: FieldType::String, + }), + ); + }); +} + +#[test] +fn test_result_submission_after_termination() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operators + let bob = mock_pub_key(BOB); + let eve = mock_pub_key(EVE); + + assert_ok!(join_and_register(bob.clone(), 0, test_ecdsa_key(), Default::default(), 1000)); + + // Create and approve service + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + + // Submit job call + assert_ok!(Services::call( + RuntimeOrigin::signed(eve.clone()), + 0, + 0, + bounded_vec![Field::Uint8(1)], + )); + + // Terminate service + assert_ok!(Services::terminate(RuntimeOrigin::signed(eve.clone()), 0)); + + // Try to submit result after termination + let key_type = KeyTypeId(*b"mdkg"); + let dkg = sp_io::crypto::ecdsa_generate(key_type, None); + + assert_err!( + Services::submit_result( + RuntimeOrigin::signed(bob.clone()), + 0, + 0, + bounded_vec![Field::from(BoundedVec::try_from(dkg.to_raw_vec()).unwrap())], + ), + Error::::ServiceNotFound + ); + }); +} diff --git a/pallets/services/src/tests/mod.rs b/pallets/services/src/tests/mod.rs new file mode 100644 index 000000000..0b4bde9ed --- /dev/null +++ b/pallets/services/src/tests/mod.rs @@ -0,0 +1,241 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use std::collections::BTreeMap; + +pub use super::*; +pub use crate::mock::*; +use frame_support::assert_ok; +use sp_core::bounded_vec; +use sp_core::Pair; +use sp_runtime::Percent; +use tangle_primitives::services::*; + +mod asset_security; +mod blueprint; +mod hooks; +mod jobs; +mod native_slashing; +mod payments; +mod registration; +mod service; +mod slashing; + +pub const ALICE: u8 = 1; +pub const BOB: u8 = 2; +pub const CHARLIE: u8 = 3; +pub const DAVE: u8 = 4; +pub const EVE: u8 = 5; + +pub const KEYGEN_JOB_ID: u8 = 0; +pub const SIGN_JOB_ID: u8 = 1; + +pub fn mint_tokens( + asset_id: AssetId, + creator: ::AccountId, + recipient: ::AccountId, + amount: Balance, +) { + assert_ok!(Assets::mint(RuntimeOrigin::signed(creator.clone()), asset_id, recipient, amount)); +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum MachineKind { + Large, + Medium, + Small, +} + +/// All prices are specified in USD/hr (in u64, so 1e6 = 1$) +fn price_targets(kind: MachineKind) -> PriceTargets { + match kind { + MachineKind::Large => PriceTargets { + cpu: 2_000, + mem: 1_000, + storage_hdd: 100, + storage_ssd: 200, + storage_nvme: 300, + }, + MachineKind::Medium => PriceTargets { + cpu: 1_000, + mem: 500, + storage_hdd: 50, + storage_ssd: 100, + storage_nvme: 150, + }, + MachineKind::Small => { + PriceTargets { cpu: 500, mem: 250, storage_hdd: 25, storage_ssd: 50, storage_nvme: 75 } + }, + } +} + +// Common test utilities and setup +pub(crate) fn cggmp21_blueprint() -> ServiceBlueprint> { + #[allow(deprecated)] + ServiceBlueprint { + metadata: ServiceMetadata { name: "CGGMP21 TSS".try_into().unwrap(), ..Default::default() }, + manager: BlueprintServiceManager::Evm(CGGMP21_BLUEPRINT), + master_manager_revision: MasterBlueprintServiceManagerRevision::Latest, + jobs: bounded_vec![ + JobDefinition { + metadata: JobMetadata { name: "keygen".try_into().unwrap(), ..Default::default() }, + params: bounded_vec![FieldType::Uint8], + result: bounded_vec![FieldType::List(Box::new(FieldType::Uint8))], + }, + JobDefinition { + metadata: JobMetadata { name: "sign".try_into().unwrap(), ..Default::default() }, + params: bounded_vec![ + FieldType::Uint64, + FieldType::List(Box::new(FieldType::Uint8)) + ], + result: bounded_vec![FieldType::List(Box::new(FieldType::Uint8))], + }, + ], + registration_params: bounded_vec![], + request_params: bounded_vec![], + gadget: Default::default(), + supported_membership_models: bounded_vec![ + MembershipModelType::Fixed, + MembershipModelType::Dynamic, + ], + } +} + +pub(crate) fn test_ecdsa_key() -> [u8; 65] { + let (ecdsa_key, _) = sp_core::ecdsa::Pair::generate(); + let secret = k256::ecdsa::SigningKey::from_slice(&ecdsa_key.seed()) + .expect("Should be able to create a secret key from a seed"); + let verifying_key = k256::ecdsa::VerifyingKey::from(secret); + let public_key = verifying_key.to_encoded_point(false); + public_key.to_bytes().to_vec().try_into().unwrap() +} + +pub(crate) fn get_security_requirement( + a: AssetId, + p: &[u8; 2], +) -> AssetSecurityRequirement { + AssetSecurityRequirement { + asset: Asset::Custom(a), + min_exposure_percent: Percent::from_percent(p[0]), + max_exposure_percent: Percent::from_percent(p[1]), + } +} + +pub(crate) fn get_security_commitment(a: AssetId, p: u8) -> AssetSecurityCommitment { + AssetSecurityCommitment { asset: Asset::Custom(a), exposure_percent: Percent::from_percent(p) } +} + +struct Deployment { + blueprint_id: u64, + service_id: u64, + #[allow(dead_code)] + security_commitments: BTreeMap, AssetSecurityCommitment>, +} + +/// A Helper function that creates a blueprint and service instance +fn deploy() -> Deployment { + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + let blueprint_id = Services::next_blueprint_id(); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + let alice = mock_pub_key(ALICE); + let bob = mock_pub_key(BOB); + + assert_ok!(join_and_register( + bob.clone(), + blueprint_id, + test_ecdsa_key(), + Default::default(), + 1000 + )); + + let eve = mock_pub_key(EVE); + let service_id = Services::next_instance_id(); + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + blueprint_id, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); + + let security_commitments = + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)]; + let security_commitment_map = security_commitments + .iter() + .map(|c| (c.asset, c.clone())) + .collect::>(); + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + service_id, + security_commitments, + )); + + assert!(Instances::::contains_key(service_id)); + + Deployment { blueprint_id, service_id, security_commitments: security_commitment_map } +} + +pub fn join_and_register( + operator: AccountId, + blueprint_id: BlueprintId, + key: [u8; 65], + price_targets: PriceTargets, + stake_amount: Balance, +) -> DispatchResult { + // Join operators with stake + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + stake_amount + )); + + // Register for blueprint + assert_ok!(Services::register( + RuntimeOrigin::signed(operator.clone()), + blueprint_id, + OperatorPreferences { key, price_targets }, + Default::default(), + 0, + )); + + Ok(()) +} + +pub fn assert_events(mut expected: Vec) { + let mut actual: Vec = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect(); + expected.reverse(); + + for evt in expected { + let next = actual.pop().expect("event expected"); + assert_eq!(next, evt, "Events don't match"); + } + assert!(actual.is_empty(), "More events than expected"); +} diff --git a/pallets/services/src/tests/native_slashing.rs b/pallets/services/src/tests/native_slashing.rs new file mode 100644 index 000000000..ae95a4dfd --- /dev/null +++ b/pallets/services/src/tests/native_slashing.rs @@ -0,0 +1,257 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use frame_support::{assert_err, assert_ok}; +use sp_runtime::Percent; +use sp_staking::StakingAccount; + +#[test] +fn test_basic_native_restaking_slash() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, blueprint_id, .. } = deploy(); + + // Setup native restaking + let operator = mock_pub_key(BOB); + let delegator = mock_pub_key(CHARLIE); + let stake_amount = 10_000; + + // Delegate via native restaking + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + stake_amount / 2, // Delegate half the stake + vec![blueprint_id].into(), + )); + + // Verify initial state + let staking_ledger = Staking::ledger(StakingAccount::Stash(delegator.clone())).unwrap(); + assert_eq!(staking_ledger.active, stake_amount); + + // Create and apply slash + let slash_percent = Percent::from_percent(50); + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + operator.clone(), + service_id, + slash_percent + )); + + // Verify unapplied slash storage values + let unapplied_slash_index = Services::next_unapplied_slash_index() - 1; + let unapplied_slash = UnappliedSlashes::::get(0, unapplied_slash_index).unwrap(); + + assert_eq!(unapplied_slash.era, 0); + assert_eq!(unapplied_slash.operator, operator); + assert_eq!(unapplied_slash.service_id, service_id); + assert_eq!(unapplied_slash.blueprint_id, blueprint_id); + assert_eq!(unapplied_slash.slash_percent, slash_percent); + + // TODO: Verify final state after applying slashes + assert_ok!(MultiAssetDelegation::slash_operator(&unapplied_slash)); + }); +} + +#[test] +fn test_mixed_native_and_regular_delegation_slash() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, blueprint_id, .. } = deploy(); + + let operator = mock_pub_key(BOB); + let delegator = mock_pub_key(CHARLIE); + let native_stake = 10_000; + let regular_stake = 100_000; + + // Setup regular delegation + mint_tokens(USDC, mock_pub_key(ALICE), delegator.clone(), regular_stake * 10u128.pow(3)); + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(delegator.clone()), + Asset::Custom(USDC), + regular_stake, + None, + None, + )); + + // Delegate both native and regular stakes + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + native_stake / 2, + vec![blueprint_id].into(), + )); + + assert_ok!(MultiAssetDelegation::delegate( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + Asset::Custom(USDC), + regular_stake, + vec![blueprint_id].into(), + )); + + // Apply slash + let slash_percent = Percent::from_percent(50); + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + operator.clone(), + service_id, + slash_percent + )); + + // Verify the unapplied slash is stored correctly + let slashes: Vec<_> = UnappliedSlashes::::iter_prefix(0).collect(); + assert_eq!(slashes.len(), 1, "Should have one unapplied slash"); + + let (_, slash) = &slashes[0]; + assert_eq!(slash.service_id, service_id); + assert_eq!(slash.operator, operator); + assert_eq!(slash.blueprint_id, blueprint_id); + assert_eq!(slash.era, 0); + assert_eq!(slash.slash_percent, slash_percent); + + // TODO: Verify final state after applying slashes + assert_ok!(MultiAssetDelegation::slash_operator(slash)); + }); +} + +#[test] +fn test_native_restaking_slash_with_invalid_operator() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let invalid_operator = mock_pub_key(99); // Non-existent operator + + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + // Try to slash an invalid operator + assert_err!( + Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + invalid_operator.clone(), + service_id, + Percent::from_percent(50) + ), + Error::::OffenderNotOperator + ); + }); +} + +#[test] +fn test_native_restaking_slash_with_multiple_services() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + + // Deploy first service + let Deployment { service_id: service_id1, blueprint_id, .. } = deploy(); + + // Deploy second service + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + let bob = mock_pub_key(BOB); + assert_ok!(Services::register( + RuntimeOrigin::signed(bob.clone()), + 1, + OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, + Default::default(), + 0, + )); + + // Request second service + let eve = mock_pub_key(EVE); + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 1, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Approve second service + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 1, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + + let delegator = mock_pub_key(CHARLIE); + let stake_amount = 1000; + + // Delegate to both services + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(delegator.clone()), + bob.clone(), + stake_amount, + vec![blueprint_id, blueprint_id + 1].into(), + )); + + // Slash in first service + let service1 = Services::services(service_id1).unwrap(); + let slashing_origin1 = + Services::query_slashing_origin(&service1).map(|(o, _)| o.unwrap()).unwrap(); + + let first_slash_percent = Percent::from_percent(50); + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin1.clone()), + bob.clone(), + service_id1, + first_slash_percent + )); + + // Verify first slash was recorded + assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 1); + + // Slash in second service + let service2 = Services::services(1).unwrap(); + let slashing_origin2 = + Services::query_slashing_origin(&service2).map(|(o, _)| o.unwrap()).unwrap(); + + let second_slash_percent = Percent::from_percent(25); + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin2.clone()), + bob.clone(), + 1, + second_slash_percent + )); + + // Verify both slashes are recorded + assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 2); + // Verify slash data + let slashes: Vec<_> = UnappliedSlashes::::iter_prefix(0).collect(); + assert_eq!(slashes.len(), 2); + + // TODO: Verify final state after applying slashes + assert_ok!(MultiAssetDelegation::slash_operator(&slashes[0].1)); + assert_ok!(MultiAssetDelegation::slash_operator(&slashes[1].1)); + }); +} diff --git a/pallets/services/src/tests/payments.rs b/pallets/services/src/tests/payments.rs new file mode 100644 index 000000000..98bf4e2df --- /dev/null +++ b/pallets/services/src/tests/payments.rs @@ -0,0 +1,749 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use frame_support::{assert_err, assert_ok, traits::ConstU128}; +use sp_core::{H160, U256}; +use sp_runtime::TokenError; + +#[test] +fn test_payment_refunds_on_failure() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operator + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000,)); + + let payment = 5 * 10u128.pow(6); // 5 USDC + let charlie = mock_pub_key(CHARLIE); + let before_balance = Assets::balance(USDC, charlie.clone()); + + // Test Case 1: Refund on operator rejection + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Verify payment is held by pallet + assert_eq!(Assets::balance(USDC, Services::pallet_account()), payment); + assert_eq!(Assets::balance(USDC, charlie.clone()), before_balance - payment); + + // Bob rejects the request + assert_ok!(Services::reject(RuntimeOrigin::signed(bob.clone()), 0)); + + // Verify payment is refunded + assert_eq!(Assets::balance(USDC, Services::pallet_account()), 0); + assert_eq!(Assets::balance(USDC, charlie.clone()), before_balance); + + // Test Case 2: Refund on ERC20 payment + let charlie_address = mock_address(CHARLIE); + let charlie_evm_account_id = address_to_account_id(charlie_address); + let before_erc20_balance = Services::query_erc20_balance_of(USDC_ERC20, charlie_address) + .map(|(b, _)| b) + .unwrap_or_default(); + + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie_evm_account_id.clone()), + Some(charlie_address), + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Erc20(USDC_ERC20), + payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Verify ERC20 payment is held by pallet + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) + .map(|(b, _)| b), + U256::from(payment) + ); + + // Bob rejects the request + assert_ok!(Services::reject(RuntimeOrigin::signed(bob.clone()), 1)); + + // Verify ERC20 payment is refunded + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) + .map(|(b, _)| b), + U256::from(0) + ); + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, charlie_address).map(|(b, _)| b), + before_erc20_balance + ); + + // Test Case 3: Refund on native currency payment + let native_payment = 20000u128; // 0.00002 TNT + let initial_balance = native_payment * 100; + Balances::make_free_balance_be(&charlie, initial_balance); + let before_native_balance = Balances::free_balance(charlie.clone()); + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(0), + native_payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Verify native payment is held by pallet + assert_eq!(Balances::free_balance(Services::pallet_account()), native_payment); + assert_eq!(Balances::free_balance(charlie.clone()), before_native_balance - native_payment); + + // Bob rejects the request + assert_ok!(Services::reject(RuntimeOrigin::signed(bob.clone()), 2)); + + // Verify native payment is refunded + assert_eq!(Balances::free_balance(Services::pallet_account()), 0); + assert_eq!(Balances::free_balance(charlie.clone()), before_native_balance); + }); +} + +#[test] +fn test_payment_distribution_operators() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint( + RuntimeOrigin::signed(alice.clone()), + blueprint.clone() + )); + + // Register operators + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000,)); + + let charlie = mock_pub_key(CHARLIE); + let charlie_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register( + charlie.clone(), + 0, + charlie_ecdsa_key, + Default::default(), + 1000 + )); + + // Test Case 1: Custom Asset Payment (USDC) + let payment = 5 * 10u128.pow(6); // 5 USDC + let eve = mock_pub_key(EVE); + mint_tokens(USDC, alice.clone(), eve.clone(), 1000000000000000000u128); + let before_balance = Assets::balance(USDC, eve.clone()); + + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![], + vec![bob.clone(), charlie.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + payment, + MembershipModel::Fixed { min_operators: 2 }, + )); + + // Verify payment is held by pallet + assert_eq!(Assets::balance(USDC, Services::pallet_account()), payment); + assert_eq!(Assets::balance(USDC, eve.clone()), before_balance - payment); + + // Approve service request + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(USDC, 10), get_security_commitment(TNT, 20)], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(charlie.clone()), + 0, + vec![get_security_commitment(USDC, 15), get_security_commitment(TNT, 25)], + )); + + // Verify payment is transferred to MBSM + let mbsm_address = Services::mbsm_address_of(&blueprint).unwrap(); + let mbsm_account_id = PalletEVMAddressMapping::into_account_id(mbsm_address); + assert_eq!(Assets::balance(USDC, mbsm_account_id.clone()), payment); + assert_eq!(Assets::balance(USDC, Services::pallet_account()), 0); + + // Test Case 2: ERC20 Token Payment + let charlie_address = mock_address(CHARLIE); + let charlie_evm_account_id = PalletEVMAddressMapping::into_account_id(charlie_address); + + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie_evm_account_id.clone()), + Some(charlie_address), + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Erc20(USDC_ERC20), + payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Verify ERC20 payment is held by pallet + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) + .map(|(b, _)| b), + U256::from(payment) + ); + + // Bob approves + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 1, + vec![get_security_commitment(USDC, 10), get_security_commitment(TNT, 20)], + )); + + // Verify ERC20 payment is transferred to MBSM + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, mbsm_address).map(|(b, _)| b), + U256::from(payment) + ); + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) + .map(|(b, _)| b), + U256::from(0) + ); + + // Test Case 3: Native Currency Payment + let native_payment = 1000000000000000000u128; // 1 TNT + let existential_deposit = as sp_core::Get>::get(); + // Ensure enough balance for payment + existential deposit + let required_balance = native_payment * 10; + + // Setup accounts with sufficient balances + Balances::make_free_balance_be(&eve, required_balance); + let pallet_account = Services::pallet_account(); + Balances::make_free_balance_be(&pallet_account, required_balance); + + let mbsm_address = Services::mbsm_address_of(&blueprint).unwrap(); + let mbsm_account_id = PalletEVMAddressMapping::into_account_id(mbsm_address); + Balances::make_free_balance_be(&mbsm_account_id, required_balance); + + // Verify initial balances + assert_eq!( + Balances::free_balance(eve.clone()), + required_balance, + "Eve's balance not set correctly" + ); + let initial_pallet_balance = Balances::free_balance(pallet_account.clone()); + let initial_mbsm_balance = Balances::free_balance(mbsm_account_id.clone()); + assert!( + initial_pallet_balance >= existential_deposit, + "Pallet account needs existential deposit" + ); + assert!( + initial_mbsm_balance >= existential_deposit, + "MBSM account needs existential deposit" + ); + + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(0), + native_payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Bob approves + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 2, + vec![get_security_commitment(USDC, 10), get_security_commitment(TNT, 20)], + )); + + // Verify native payment is transferred to MBSM after approval + assert_eq!( + Balances::free_balance(mbsm_account_id), + initial_mbsm_balance + native_payment * 2, + "MBSM account should have payment after approval" + ); + assert_eq!( + Balances::free_balance(pallet_account), + initial_pallet_balance - native_payment, + "Pallet account should transfer payment after approval" + ); + assert_eq!( + Balances::free_balance(eve.clone()), + native_payment * 9, + "Eve should retain rest of the balance" + ); + }); +} + +#[test] +fn test_payment_multiple_asset_types() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint( + RuntimeOrigin::signed(alice.clone()), + blueprint.clone() + )); + + // Register operator + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000,)); + + // Test Case 1: Multiple asset security requirements + let eve = mock_pub_key(EVE); + let payment = 5 * 10u128.pow(6); // 5 USDC + mint_tokens(USDC, alice.clone(), eve.clone(), payment * 10u128.pow(6)); + let before_balance = Assets::balance(USDC, eve.clone()); + + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![ + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[15, 25]), + ], + 100, + Asset::Custom(USDC), + payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Verify payment is held by pallet + assert_eq!(Assets::balance(USDC, Services::pallet_account()), payment); + assert_eq!(Assets::balance(USDC, eve.clone()), before_balance - payment); + + // Bob approves with security commitments for all assets + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![ + get_security_commitment(USDC, 10), + get_security_commitment(WETH, 15), + get_security_commitment(TNT, 10), + ], + )); + + // Verify payment is transferred to MBSM + let mbsm_address = Services::mbsm_address_of(&blueprint).unwrap(); + let mbsm_account_id = address_to_account_id(mbsm_address); + assert_eq!(Assets::balance(USDC, mbsm_account_id.clone()), payment); + assert_eq!(Assets::balance(USDC, Services::pallet_account()), 0); + + // Test Case 2: Multiple asset types with ERC20 payment + let charlie_address = mock_address(CHARLIE); + let charlie_evm_account_id = address_to_account_id(charlie_address); + + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie_evm_account_id.clone()), + Some(charlie_address), + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![ + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[15, 25]), + ], + 100, + Asset::Erc20(USDC_ERC20), + payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Verify ERC20 payment is held by pallet + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) + .map(|(b, _)| b), + U256::from(payment) + ); + + // Bob approves with security commitments for all assets + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 1, + vec![ + get_security_commitment(USDC, 10), + get_security_commitment(WETH, 15), + get_security_commitment(TNT, 15), + ], + )); + + // Verify ERC20 payment is transferred to MBSM + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, mbsm_address).map(|(b, _)| b), + U256::from(payment) + ); + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) + .map(|(b, _)| b), + U256::from(0) + ); + + // Test Case 3: Multiple asset types with native currency payment + let native_payment = 1000000000000000000u128; // 1 TNT + let existential_deposit = as sp_core::Get>::get(); + // Ensure enough balance for payment + existential deposit + let required_balance = native_payment * 10; + + // Setup accounts with sufficient balances + Balances::make_free_balance_be(&eve, required_balance); + let pallet_account = Services::pallet_account(); + Balances::make_free_balance_be(&pallet_account, required_balance); + + let mbsm_address = Services::mbsm_address_of(&blueprint).unwrap(); + let mbsm_account_id = PalletEVMAddressMapping::into_account_id(mbsm_address); + Balances::make_free_balance_be(&mbsm_account_id, required_balance); + + // Verify initial balances + assert_eq!( + Balances::free_balance(eve.clone()), + required_balance, + "Eve's balance not set correctly" + ); + let initial_pallet_balance = Balances::free_balance(pallet_account.clone()); + let initial_mbsm_balance = Balances::free_balance(mbsm_account_id.clone()); + assert!( + initial_pallet_balance >= existential_deposit, + "Pallet account needs existential deposit" + ); + assert!( + initial_mbsm_balance >= existential_deposit, + "MBSM account needs existential deposit" + ); + + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![ + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[15, 25]), + ], + 100, + Asset::Custom(0), + native_payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Bob approves with security commitments for all assets + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 2, + vec![ + get_security_commitment(USDC, 10), + get_security_commitment(WETH, 15), + get_security_commitment(TNT, 15), + ], + )); + + // Verify native payment is transferred to MBSM after approval + assert_eq!( + Balances::free_balance(mbsm_account_id), + initial_mbsm_balance + native_payment * 2, + "MBSM account should have payment after approval" + ); + assert_eq!( + Balances::free_balance(pallet_account), + initial_pallet_balance - native_payment, + "Pallet account should transfer payment after approval" + ); + assert_eq!( + Balances::free_balance(eve.clone()), + native_payment * 9, + "Eve should retain rest of the balance" + ); + }); +} + +#[test] +fn test_payment_zero_amount() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operator + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000,)); + + let charlie = mock_pub_key(CHARLIE); + + // Test Case 1: Zero amount for Custom Asset + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Test Case 2: Zero amount for ERC20 Token + let charlie_address = mock_address(CHARLIE); + let charlie_evm_account_id = address_to_account_id(charlie_address); + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie_evm_account_id.clone()), + Some(charlie_address), + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Erc20(USDC_ERC20), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Test Case 3: Zero amount for Native Currency + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(0), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + }); +} + +#[test] +fn test_payment_maximum_amount() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operator + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000,)); + + let charlie = mock_pub_key(CHARLIE); + + // Test Case 1: Maximum amount for Custom Asset (more than balance) + let max_custom_amount = Assets::balance(USDC, charlie.clone()) + 1; + assert_err!( + Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + max_custom_amount, + MembershipModel::Fixed { min_operators: 1 }, + ), + TokenError::FundsUnavailable, + ); + + // Test Case 2: Maximum amount for ERC20 Token (more than balance) + let charlie_address = mock_address(CHARLIE); + let charlie_evm_account_id = address_to_account_id(charlie_address); + let max_erc20_amount = Services::query_erc20_balance_of(USDC_ERC20, charlie_address) + .map(|(b, _)| b) + .unwrap_or_default() + .as_u128() + + 1; + assert_err!( + Services::request( + RuntimeOrigin::signed(charlie_evm_account_id.clone()), + Some(charlie_address), + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Erc20(USDC_ERC20), + max_erc20_amount, + MembershipModel::Fixed { min_operators: 1 }, + ), + Error::::ERC20TransferFailed + ); + + // Test Case 3: Maximum amount for Native Currency (more than balance) + let max_native_amount = Balances::free_balance(charlie.clone()) + 1; + assert_err!( + Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(0), + max_native_amount, + MembershipModel::Fixed { min_operators: 1 }, + ), + TokenError::FundsUnavailable, + ); + }); +} + +#[test] +fn test_payment_invalid_asset_types() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operator + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000,)); + + let charlie = mock_pub_key(CHARLIE); + let payment = 5 * 10u128.pow(6); // 5 USDC + + // Test Case 1: Non-existent Custom Asset + let non_existent_asset_id = 999999; + assert_err!( + Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(non_existent_asset_id), + payment, + MembershipModel::Fixed { min_operators: 1 }, + ), + TokenError::UnknownAsset, + ); + + // Test Case 2: Non-existent ERC20 Token + let charlie_address = mock_address(CHARLIE); + let charlie_evm_account_id = address_to_account_id(charlie_address); + let non_existent_erc20 = H160::from_low_u64_be(999999); + assert_err!( + Services::request( + RuntimeOrigin::signed(charlie_evm_account_id.clone()), + Some(charlie_address), + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Erc20(non_existent_erc20), + payment, + MembershipModel::Fixed { min_operators: 1 }, + ), + Error::::ERC20TransferFailed + ); + + // Test Case 3: Invalid ERC20 Token (not a contract) + let invalid_erc20 = H160::from_low_u64_be(1); // Random address that's not a contract + assert_err!( + Services::request( + RuntimeOrigin::signed(charlie_evm_account_id.clone()), + Some(charlie_address), + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Erc20(invalid_erc20), + payment, + MembershipModel::Fixed { min_operators: 1 }, + ), + Error::::ERC20TransferFailed + ); + }); +} diff --git a/pallets/services/src/tests/registration.rs b/pallets/services/src/tests/registration.rs new file mode 100644 index 000000000..22a1976f7 --- /dev/null +++ b/pallets/services/src/tests/registration.rs @@ -0,0 +1,451 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use frame_support::{assert_err, assert_ok}; + +#[test] +fn register_on_blueprint() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + + assert_ok!(join_and_register( + bob.clone(), + 0, + bob_ecdsa_key, + price_targets(MachineKind::Large), + 1000, + )); + + let events = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect::>(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::Registered { + provider: bob.clone(), + blueprint_id: 0, + preferences: OperatorPreferences { + key: bob_ecdsa_key, + price_targets: price_targets(MachineKind::Large), + }, + registration_args: Default::default(), + }))); + + // The blueprint should be added to my blueprints in my profile. + let profile = OperatorsProfile::::get(bob.clone()).unwrap(); + assert!(profile.blueprints.contains(&0)); + + // if we try to register again, it should fail. + assert_err!( + Services::register( + RuntimeOrigin::signed(bob), + 0, + OperatorPreferences { key: bob_ecdsa_key, price_targets: Default::default() }, + Default::default(), + 0, + ), + crate::Error::::AlreadyRegistered + ); + + // if we try to register with a non active operator, should fail + assert_err!( + Services::register( + RuntimeOrigin::signed(mock_pub_key(100)), + 0, + OperatorPreferences { key: test_ecdsa_key(), price_targets: Default::default() }, + Default::default(), + 0, + ), + crate::Error::::OperatorNotActive + ); + }); +} + +#[test] +fn pre_register_on_blueprint() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + let bob = mock_pub_key(BOB); + let pre_registration_call = Services::pre_register(RuntimeOrigin::signed(bob.clone()), 0); + assert_ok!(pre_registration_call); + + let events = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect::>(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::PreRegistration { + operator: bob.clone(), + blueprint_id: 0, + }))); + }); +} + +#[test] +fn update_price_targets() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + let bob = mock_pub_key(BOB); + let bob_operator_ecdsa_key = test_ecdsa_key(); + + // Join operators and register + assert_ok!(join_and_register( + bob.clone(), + 0, + bob_operator_ecdsa_key, + price_targets(MachineKind::Small), + 1000, + )); + + assert_eq!( + Operators::::get(0, &bob).unwrap(), + OperatorPreferences { + key: bob_operator_ecdsa_key, + price_targets: price_targets(MachineKind::Small) + } + ); + + System::reset_events(); // Clear all previous events + + // update price targets + assert_ok!(Services::update_price_targets( + RuntimeOrigin::signed(bob.clone()), + 0, + price_targets(MachineKind::Medium), + )); + + assert_eq!( + Operators::::get(0, &bob).unwrap().price_targets, + price_targets(MachineKind::Medium) + ); + + assert_events(vec![RuntimeEvent::Services(crate::Event::PriceTargetsUpdated { + operator: bob, + blueprint_id: 0, + price_targets: price_targets(MachineKind::Medium), + })]); + + // try to update price targets when not registered + let charlie = mock_pub_key(CHARLIE); + assert_err!( + Services::update_price_targets( + RuntimeOrigin::signed(charlie), + 0, + price_targets(MachineKind::Medium) + ), + crate::Error::::NotRegistered + ); + }); +} + +#[test] +fn unregister_from_blueprint() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + let bob = mock_pub_key(BOB); + assert_ok!(join_and_register(bob.clone(), 0, test_ecdsa_key(), Default::default(), 1000,)); + assert_ok!(Services::unregister(RuntimeOrigin::signed(bob.clone()), 0)); + assert!(!Operators::::contains_key(0, &bob)); + + // The blueprint should be removed from my blueprints in my profile. + let profile = OperatorsProfile::::get(bob.clone()).unwrap(); + assert!(!profile.blueprints.contains(&0)); + + let events = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect::>(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::Unregistered { + operator: bob, + blueprint_id: 0, + }))); + + // try to deregister when not registered + let charlie = mock_pub_key(CHARLIE); + assert_err!( + Services::unregister(RuntimeOrigin::signed(charlie), 0), + crate::Error::::NotRegistered + ); + }); +} + +#[test] +fn test_registration_max_blueprints() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + + // Join as operator first + assert_ok!(MultiAssetDelegation::join_operators(RuntimeOrigin::signed(bob.clone()), 1000,)); + + // Create maximum number of blueprints + for i in 0..MaxBlueprintsPerOperator::get() { + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register for each blueprint + assert_ok!(Services::register( + RuntimeOrigin::signed(bob.clone()), + i.into(), + OperatorPreferences { + key: bob_ecdsa_key, + price_targets: price_targets(MachineKind::Large), + }, + Default::default(), + 0, + )); + } + + // Create one more blueprint + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Try to register for one more blueprint - should fail + assert_err!( + Services::register( + RuntimeOrigin::signed(bob.clone()), + MaxBlueprintsPerOperator::get().into(), + OperatorPreferences { + key: bob_ecdsa_key, + price_targets: price_targets(MachineKind::Large), + }, + Default::default(), + 0, + ), + Error::::MaxBlueprintsPerOperatorExceeded + ); + }); +} + +#[test] +fn test_registration_invalid_preferences() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + let bob = mock_pub_key(BOB); + + // Test with invalid ECDSA key (zero key) + let invalid_key = [0u8; 65]; + assert_err!( + Services::register( + RuntimeOrigin::signed(bob.clone()), + 0, + OperatorPreferences { + key: invalid_key, + price_targets: price_targets(MachineKind::Large), + }, + Default::default(), + 0, + ), + Error::::InvalidKey + ); + + // TODO: Decide how we want to validate price targets + // // Test with invalid price targets (all zeros) + // assert_err!( + // Services::register( + // RuntimeOrigin::signed(bob.clone()), + // 0, + // OperatorPreferences { + // key: test_ecdsa_key(), + // price_targets: PriceTargets { + // cpu: 0, + // mem: 0, + // storage_hdd: 0, + // storage_ssd: 0, + // storage_nvme: 0, + // }, + // }, + // Default::default(), + // 0, + // ), + // Error::::InvalidPriceTargets + // ); + }); +} + +#[test] +fn test_registration_duplicate_keys() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + let bob = mock_pub_key(BOB); + let charlie = mock_pub_key(CHARLIE); + let ecdsa_key = test_ecdsa_key(); + + // First registration should succeed + assert_ok!(join_and_register( + bob.clone(), + 0, + ecdsa_key, + price_targets(MachineKind::Large), + 1000, + )); + + // Join operators first for Charlie + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(charlie.clone()), + 1000 + )); + + // Second registration with same key should fail with DuplicateKey error + assert_err!( + Services::register( + RuntimeOrigin::signed(charlie.clone()), + 0, + OperatorPreferences { + key: ecdsa_key, + price_targets: price_targets(MachineKind::Large), + }, + Default::default(), + 0, + ), + Error::::DuplicateKey + ); + }); +} + +#[test] +fn test_registration_during_active_services() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + let bob = mock_pub_key(BOB); + let charlie = mock_pub_key(CHARLIE); + let eve = mock_pub_key(EVE); + + // Join operators with stake for Bob + assert_ok!(MultiAssetDelegation::join_operators(RuntimeOrigin::signed(bob.clone()), 1000)); + + // Register Bob as an operator + assert_ok!(Services::register( + RuntimeOrigin::signed(bob.clone()), + 0, + OperatorPreferences { + key: test_ecdsa_key(), + price_targets: price_targets(MachineKind::Large), + }, + Default::default(), + 0, + )); + + // Create a service request + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(WETH, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Verify service request exists but service instance doesn't yet + assert!(ServiceRequests::::contains_key(0)); + assert!(!Instances::::contains_key(0)); + assert!(!UserServices::::get(eve.clone()).contains(&0)); + + // Approve the service request + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); + + // Verify service is active and in instances storage + // Check service instance exists + assert!(Instances::::contains_key(0)); + // Verify service details + let service = Instances::::get(0).unwrap(); + assert_eq!(service.owner, eve); + let service_operators = service + .operator_security_commitments + .iter() + .map(|r| r.0.clone()) + .collect::>(); + assert_eq!(service_operators.len(), 1); + assert!(service_operators.contains(&bob)); + // Verify user services mapping + assert!(UserServices::::get(eve).contains(&0)); + + // Try to unregister while service is active - should fail + assert_err!( + Services::unregister(RuntimeOrigin::signed(bob.clone()), 0), + Error::::NotAllowedToUnregister + ); + + // Try to register another operator for the same blueprint + assert_ok!(join_and_register( + charlie.clone(), + 0, + test_ecdsa_key(), + price_targets(MachineKind::Large), + 1000, + )); + + // Verify Charlie was registered successfully despite active service + let profile = OperatorsProfile::::get(charlie.clone()).unwrap(); + assert!(profile.blueprints.contains(&0)); + }); +} diff --git a/pallets/services/src/tests/service.rs b/pallets/services/src/tests/service.rs new file mode 100644 index 000000000..807ead604 --- /dev/null +++ b/pallets/services/src/tests/service.rs @@ -0,0 +1,962 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use frame_support::{assert_err, assert_ok}; +use sp_core::U256; + +#[test] +fn request_service() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register multiple operators + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + + let charlie = mock_pub_key(CHARLIE); + let charlie_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register( + charlie.clone(), + 0, + charlie_ecdsa_key, + Default::default(), + 1000, + )); + + let dave = mock_pub_key(DAVE); + let dave_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(dave.clone(), 0, dave_ecdsa_key, Default::default(), 1000,)); + + let eve = mock_pub_key(EVE); + + // Native asset exposure too low should fail + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone(), dave.clone()], + Default::default(), + vec![ + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[15, 25]), + get_security_requirement(TNT, &[5, 10]), + ], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + ), + Error::::NativeAssetExposureTooLow, + ); + + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone(), dave.clone()], + Default::default(), + vec![ + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[10, 20]) + ], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 3 }, + )); + + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); + + // Bob approves the request with security commitments + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![ + get_security_commitment(USDC, 10), + get_security_commitment(WETH, 10), + get_security_commitment(TNT, 10) + ], + )); + + let events: Vec = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::ServiceRequestApproved { + operator: bob.clone(), + request_id: 0, + blueprint_id: 0, + approved: vec![bob.clone()], + pending_approvals: vec![charlie.clone(), dave.clone()], + }))); + + // Charlie approves the request with security commitments + assert_ok!(Services::approve( + RuntimeOrigin::signed(charlie.clone()), + 0, + vec![ + get_security_commitment(USDC, 15), + get_security_commitment(WETH, 15), + get_security_commitment(TNT, 15), + ], + )); + + let events: Vec = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::ServiceRequestApproved { + operator: charlie.clone(), + request_id: 0, + blueprint_id: 0, + approved: vec![bob.clone(), charlie.clone()], + pending_approvals: vec![dave.clone()], + }))); + + // Dave should not be able to approve the request with an invalid security commitment + // because the security commitments are misordered. They must be in the same order as the + // security requirements. + assert_err!( + Services::approve( + RuntimeOrigin::signed(dave.clone()), + 0, + vec![ + get_security_commitment(TNT, 20), + get_security_commitment(USDC, 20), + get_security_commitment(WETH, 20), + ], + ), + Error::::InvalidSecurityCommitments, + ); + + // Dave approves the request with security commitments + assert_ok!(Services::approve( + RuntimeOrigin::signed(dave.clone()), + 0, + vec![ + get_security_commitment(USDC, 20), + get_security_commitment(WETH, 20), + get_security_commitment(TNT, 20), + ], + )); + + let service = Services::services(0).unwrap(); + let operator_security_commitments = service.operator_security_commitments; + + let events: Vec = System::events() + .into_iter() + .map(|e| e.event) + .filter(|e| matches!(e, RuntimeEvent::Services(_))) + .collect(); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::ServiceRequestApproved { + operator: dave.clone(), + request_id: 0, + blueprint_id: 0, + approved: vec![bob.clone(), charlie.clone(), dave.clone()], + pending_approvals: vec![], + }))); + + assert!(events.contains(&RuntimeEvent::Services(crate::Event::ServiceInitiated { + owner: eve, + request_id: 0, + service_id: 0, + blueprint_id: 0, + operator_security_commitments, + }))); + + // The request is now fully approved + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 0); + + // Now the service should be initiated + assert!(Instances::::contains_key(0)); + + // The service should also be added to the services for each operator. + let profile = OperatorsProfile::::get(bob).unwrap(); + assert!(profile.services.contains(&0)); + let profile = OperatorsProfile::::get(charlie).unwrap(); + assert!(profile.services.contains(&0)); + let profile = OperatorsProfile::::get(dave).unwrap(); + assert!(profile.services.contains(&0)); + }); +} + +#[test] +fn request_service_with_no_assets() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + let eve = mock_pub_key(EVE); + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![], // no assets + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + ), + Error::::NoAssetsProvided + ); + }); +} + +#[test] +fn request_service_with_payment_asset() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + + assert_ok!(Services::create_blueprint( + RuntimeOrigin::signed(alice.clone()), + blueprint.clone() + )); + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + + let payment = 5 * 10u128.pow(6); // 5 USDC + let charlie = mock_pub_key(CHARLIE); + let before_balance = Assets::balance(USDC, charlie.clone()); + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![ + get_security_requirement(TNT, &[10, 20]), + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[10, 20]) + ], + 100, + Asset::Custom(USDC), + payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); + + // The Pallet account now has 5 USDC + assert_eq!(Assets::balance(USDC, Services::pallet_account()), payment); + // Charlie Balance should be decreased by 5 USDC + assert_eq!(Assets::balance(USDC, charlie.clone()), before_balance - payment); + + // Bob approves the request with security commitments + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![ + get_security_commitment(TNT, 10), + get_security_commitment(USDC, 10), + get_security_commitment(WETH, 10) + ], + )); + + // The request is now fully approved + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 0); + + // The Payment should be now transferred to the MBSM. + let mbsm_address = Pallet::::mbsm_address_of(&blueprint).unwrap(); + let mbsm_account_id = address_to_account_id(mbsm_address); + assert_eq!(Assets::balance(USDC, mbsm_account_id), payment); + // Pallet account should have 0 USDC + assert_eq!(Assets::balance(USDC, Services::pallet_account()), 0); + + // Now the service should be initiated + assert!(Instances::::contains_key(0)); + }); +} + +#[test] +fn request_service_with_payment_erc20_token() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + + assert_ok!(Services::create_blueprint( + RuntimeOrigin::signed(alice.clone()), + blueprint.clone() + )); + let bob = mock_pub_key(BOB); + assert_ok!(join_and_register(bob.clone(), 0, test_ecdsa_key(), Default::default(), 1000)); + + let payment = 5 * 10u128.pow(6); // 5 USDC + let charlie = mock_pub_key(CHARLIE); + assert_ok!(Services::request( + RuntimeOrigin::signed(address_to_account_id(mock_address(CHARLIE))), + Some(account_id_to_address(charlie.clone())), + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![ + get_security_requirement(TNT, &[10, 20]), + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[10, 20]) + ], + 100, + Asset::Erc20(USDC_ERC20), + payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); + + // The Pallet address now has 5 USDC + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) + .map(|(b, _)| b), + U256::from(payment) + ); + + // Bob approves the request with security commitments + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![ + get_security_commitment(TNT, 10), + get_security_commitment(USDC, 10), + get_security_commitment(WETH, 10) + ], + )); + + // The request is now fully approved + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 0); + + // The Payment should be now transferred to the MBSM. + let mbsm_address = Pallet::::mbsm_address_of(&blueprint).unwrap(); + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, mbsm_address).map(|(b, _)| b), + U256::from(payment) + ); + // Pallet account should have 0 USDC + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) + .map(|(b, _)| b), + U256::from(0) + ); + + // Now the service should be initiated + assert!(Instances::::contains_key(0)); + }); +} + +#[test] +fn reject_service_with_payment_token() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + + assert_ok!(Services::create_blueprint( + RuntimeOrigin::signed(alice.clone()), + blueprint.clone() + )); + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + + let payment = 5 * 10u128.pow(6); // 5 USDC + let charlie_address = mock_address(CHARLIE); + let charlie_evm_account_id = address_to_account_id(charlie_address); + let before_balance = Services::query_erc20_balance_of(USDC_ERC20, charlie_address) + .map(|(b, _)| b) + .unwrap_or_default(); + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie_evm_account_id), + Some(charlie_address), + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![ + get_security_requirement(TNT, &[10, 20]), + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[10, 20]) + ], + 100, + Asset::Erc20(USDC_ERC20), + payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); + + // The Pallet address now has 5 USDC + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) + .map(|(b, _)| b), + U256::from(payment) + ); + // Charlie Balance should be decreased by 5 USDC + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, charlie_address).map(|(b, _)| b), + before_balance - U256::from(payment) + ); + + // Bob rejects the request + assert_ok!(Services::reject(RuntimeOrigin::signed(bob.clone()), 0)); + + // The Payment should be now refunded to the requester. + // Pallet account should have 0 USDC + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) + .map(|(b, _)| b), + U256::from(0) + ); + // Charlie Balance should be back to the original + assert_ok!( + Services::query_erc20_balance_of(USDC_ERC20, charlie_address).map(|(b, _)| b), + before_balance + ); + }); +} + +#[test] +fn reject_service_with_payment_asset() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + + assert_ok!(Services::create_blueprint( + RuntimeOrigin::signed(alice.clone()), + blueprint.clone() + )); + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + + let payment = 5 * 10u128.pow(6); // 5 USDC + let charlie = mock_pub_key(CHARLIE); + let before_balance = Assets::balance(USDC, charlie.clone()); + assert_ok!(Services::request( + RuntimeOrigin::signed(charlie.clone()), + None, + 0, + vec![], + vec![bob.clone()], + Default::default(), + vec![ + get_security_requirement(TNT, &[10, 20]), + get_security_requirement(USDC, &[10, 20]), + get_security_requirement(WETH, &[10, 20]) + ], + 100, + Asset::Custom(USDC), + payment, + MembershipModel::Fixed { min_operators: 1 }, + )); + + assert_eq!(ServiceRequests::::iter_keys().collect::>().len(), 1); + + // The Pallet account now has 5 USDC + assert_eq!(Assets::balance(USDC, Services::pallet_account()), payment); + // Charlie Balance should be decreased by 5 USDC + assert_eq!(Assets::balance(USDC, charlie.clone()), before_balance - payment); + + // Bob rejects the request + assert_ok!(Services::reject(RuntimeOrigin::signed(bob.clone()), 0)); + + // The Payment should be now refunded to the requester. + // Pallet account should have 0 USDC + assert_eq!(Assets::balance(USDC, Services::pallet_account()), 0); + // Charlie Balance should be back to the original + assert_eq!(Assets::balance(USDC, charlie), before_balance); + }); +} + +#[test] +fn test_service_creation_dynamic_max_operators() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register maximum number of operators (using mock accounts) + let max_operators = 10; + let mut operators = Vec::new(); + + // Create 11 operators with sequential keys + for i in 1..=10 { + let operator = mock_pub_key_from_fixed_bytes([i as u8; 32]); + // Give operator sufficient balance to join + Balances::make_free_balance_be(&operator, 10_000_000); + assert_ok!(join_and_register( + operator.clone(), + 0, + test_ecdsa_key(), + Default::default(), + 1000, + )); + operators.push(operator); + } + + let eve = mock_pub_key(EVE); + + // Try to create service with exactly 10 operators - should succeed + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + operators.clone(), + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Dynamic { min_operators: 1, max_operators: Some(max_operators) }, + )); + + // Try to create service with 11 operators - should fail + let extra_operator = mock_pub_key_from_fixed_bytes([11u8; 32]); + // Give extra operator sufficient balance to join + Balances::make_free_balance_be(&extra_operator, 10_000_000); + assert_ok!(join_and_register( + extra_operator.clone(), + 0, + test_ecdsa_key(), + Default::default(), + 1000, + )); + operators.push(extra_operator); + + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + operators, + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Dynamic { min_operators: 1, max_operators: Some(max_operators) }, + ), + Error::::TooManyOperators + ); + }); +} + +#[test] +fn test_service_creation_fixed_min_operators() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register some operators + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + + let charlie = mock_pub_key(CHARLIE); + let charlie_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register( + charlie.clone(), + 0, + charlie_ecdsa_key, + Default::default(), + 1000, + )); + + let eve = mock_pub_key(EVE); + + // Try to create service with zero operators - should fail + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 0 }, + ), + Error::::TooFewOperators + ); + + // Try to create service with fewer operators than min_operators - should fail + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 2 }, + ), + Error::::TooFewOperators + ); + + // Try to create service with exactly min_operators - should succeed + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 2 }, + )); + }); +} + +#[test] +fn test_service_creation_invalid_operators() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register one valid operator + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + + // Create an unregistered operator + let unregistered = mock_pub_key(CHARLIE); + let eve = mock_pub_key(EVE); + + // Try to create service with an unregistered operator - should fail + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), unregistered.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 2 }, + ), + Error::::OperatorNotActive + ); + }); +} + +#[test] +fn test_service_creation_duplicate_operators() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operators + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + + let charlie = mock_pub_key(CHARLIE); + let charlie_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register( + charlie.clone(), + 0, + charlie_ecdsa_key, + Default::default(), + 1000, + )); + + let eve = mock_pub_key(EVE); + + // Try to create service with duplicate operators - should fail + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 2 }, + ), + Error::::DuplicateOperator + ); + }); +} + +#[test] +fn test_service_creation_inactive_operators() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operators + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + + let charlie = mock_pub_key(CHARLIE); + let charlie_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register( + charlie.clone(), + 0, + charlie_ecdsa_key, + Default::default(), + 1000, + )); + + // Deactivate one operator + assert_ok!(MultiAssetDelegation::go_offline(RuntimeOrigin::signed(charlie.clone()))); + + let eve = mock_pub_key(EVE); + + // Try to create service with an inactive operator - should fail + assert_err!( + Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 2 }, + ), + Error::::OperatorNotActive + ); + + // Service creation with only active operators should succeed + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + }); +} + +#[test] +fn test_termination_with_partial_approvals() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operators + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + + let charlie = mock_pub_key(CHARLIE); + let charlie_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register( + charlie.clone(), + 0, + charlie_ecdsa_key, + Default::default(), + 1000, + )); + + let dave = mock_pub_key(DAVE); + let dave_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(dave.clone(), 0, dave_ecdsa_key, Default::default(), 1000,)); + + // Create service request + let eve = mock_pub_key(EVE); + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone(), charlie.clone(), dave.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 3 }, + )); + + // Only two operators approve + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(USDC, 10), get_security_commitment(TNT, 10)], + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(charlie.clone()), + 0, + vec![get_security_commitment(USDC, 15), get_security_commitment(TNT, 15)], + )); + + // Attempt to terminate service with partial approvals - should fail + assert_err!( + Services::terminate(RuntimeOrigin::signed(eve.clone()), 0), + Error::::ServiceNotFound + ); + + // Complete the approvals + assert_ok!(Services::approve( + RuntimeOrigin::signed(dave.clone()), + 0, + vec![get_security_commitment(USDC, 20), get_security_commitment(TNT, 20)], + )); + + // Now termination should succeed + assert_ok!(Services::terminate(RuntimeOrigin::signed(eve.clone()), 0)); + + // Verify service is terminated + assert!(!Instances::::contains_key(0)); + }); +} + +#[test] +fn test_operator_offline_during_active_service() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); + + // Create blueprint + let alice = mock_pub_key(ALICE); + let blueprint = cggmp21_blueprint(); + assert_ok!(Services::create_blueprint(RuntimeOrigin::signed(alice.clone()), blueprint)); + + // Register operator + let bob = mock_pub_key(BOB); + let bob_ecdsa_key = test_ecdsa_key(); + assert_ok!(join_and_register(bob.clone(), 0, bob_ecdsa_key, Default::default(), 1000)); + + // Create service + let eve = mock_pub_key(EVE); + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + 0, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + + // Approve service request + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(USDC, 10), get_security_commitment(TNT, 10)], + )); + + // Verify service is active + assert!(Instances::::contains_key(0)); + + // Attempt to go offline while service is active - should fail + assert_err!( + MultiAssetDelegation::go_offline(RuntimeOrigin::signed(bob.clone())), + pallet_multi_asset_delegation::Error::::CannotGoOfflineWithActiveServices + ); + + // Terminate the service + assert_ok!(Services::terminate(RuntimeOrigin::signed(eve.clone()), 0)); + + // Now operator should be able to go offline + assert_ok!(MultiAssetDelegation::go_offline(RuntimeOrigin::signed(bob.clone()))); + }); +} diff --git a/pallets/services/src/tests/slashing.rs b/pallets/services/src/tests/slashing.rs new file mode 100644 index 000000000..b4a7e558d --- /dev/null +++ b/pallets/services/src/tests/slashing.rs @@ -0,0 +1,579 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use frame_support::{assert_err, assert_ok}; +use sp_runtime::Percent; + +#[test] +fn test_zero_percentage_slash() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let operator = mock_pub_key(BOB); + + // Try to slash zero stake + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + assert_err!( + Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + operator.clone(), + service_id, + Percent::from_percent(0) + ), + Error::::InvalidSlashPercentage + ); + }); +} + +#[test] +fn unapplied_slash() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { blueprint_id, service_id, .. } = deploy(); + let eve = mock_pub_key(EVE); + let bob = mock_pub_key(BOB); + + // Set up a job call that will result in an invalid submission + let job_call_id = Services::next_job_call_id(); + assert_ok!(Services::call( + RuntimeOrigin::signed(eve.clone()), + service_id, + KEYGEN_JOB_ID, + bounded_vec![Field::Uint8(1)], + )); + + // Submit an invalid result that should trigger slashing + let mut dkg = vec![0u8; 33]; + dkg[32] = 1; + assert_ok!(Services::submit_result( + RuntimeOrigin::signed(bob.clone()), + 0, + job_call_id, + bounded_vec![Field::from(BoundedVec::try_from(dkg).unwrap())], + )); + + let slash_percent = Percent::from_percent(50); + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + // Slash the operator for the invalid result + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + bob.clone(), + service_id, + slash_percent + )); + + // Verify the slash was recorded but not yet applied + assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 1); + + // Verify the correct event was emitted + System::assert_has_event(RuntimeEvent::Services(crate::Event::UnappliedSlash { + era: 0, + index: 0, + operator: bob.clone(), + blueprint_id, + service_id, + slash_percent, + })); + }); +} + +#[test] +fn slash_account_not_an_operator() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let karen = mock_pub_key(23); + + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + let slash_percent = Percent::from_percent(50); + + // Try to slash an operator that is not active in this service + assert_err!( + Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + karen.clone(), + service_id, + slash_percent + ), + Error::::OffenderNotOperator + ); + }); +} + +#[test] +fn dispute_and_verify_event() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { blueprint_id, service_id, .. } = deploy(); + let bob = mock_pub_key(BOB); + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + // Create a slash + let slash_percent = Percent::from_percent(50); + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + bob.clone(), + service_id, + slash_percent + )); + + // Get the unapplied slash + let (era, index) = UnappliedSlashes::::iter_keys().next().unwrap(); + + // Dispute the slash + let dispute_origin = + Services::query_dispute_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + assert_ok!(Services::dispute(RuntimeOrigin::signed(dispute_origin.clone()), era, index)); + + // Verify the slash was removed + assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 0); + + // Verify the correct event was emitted + System::assert_has_event(RuntimeEvent::Services(crate::Event::SlashDiscarded { + era, + index, + operator: bob.clone(), + blueprint_id, + service_id, + slash_percent, + })); + }); +} + +#[test] +fn dispute_with_unauthorized_origin() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let eve = mock_pub_key(EVE); + let bob = mock_pub_key(BOB); + let slash_percent = Percent::from_percent(50); + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + // Create a slash + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + bob.clone(), + service_id, + slash_percent + )); + + assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 1); + + let era = 0; + let slash_index = 0; + + // Try to dispute with an invalid origin + assert_err!( + Services::dispute(RuntimeOrigin::signed(eve.clone()), era, slash_index), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn dispute_an_already_applied_slash() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let eve = mock_pub_key(EVE); + let bob = mock_pub_key(BOB); + let slash_percent = Percent::from_percent(50); + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + // Create a slash + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + bob.clone(), + service_id, + slash_percent + )); + + assert_eq!(UnappliedSlashes::::iter_keys().collect::>().len(), 1); + + let era = 0; + let slash_index = 0; + + // Simulate a slash being applied by removing it + UnappliedSlashes::::remove(era, slash_index); + + // Try to dispute an already applied slash + assert_err!( + Services::dispute(RuntimeOrigin::signed(eve.clone()), era, slash_index), + Error::::UnappliedSlashNotFound + ); + }); +} + +#[test] +fn test_slash_with_multiple_asset_types() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let operator = mock_pub_key(BOB); + let delegator = mock_pub_key(CHARLIE); + + // Setup native stake + let native_stake = 10_000; + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + native_stake, + Default::default(), + )); + + // Setup USDC stake + let usdc_stake = 100_000; + mint_tokens(USDC, mock_pub_key(ALICE), delegator.clone(), usdc_stake * 10u128.pow(6)); + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(delegator.clone()), + Asset::Custom(USDC), + usdc_stake, + None, + None, + )); + assert_ok!(MultiAssetDelegation::delegate( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + Asset::Custom(USDC), + usdc_stake, + Default::default(), + )); + + // Setup WETH stake + let weth_stake = 100_000; + mint_tokens(WETH, mock_pub_key(BOB), delegator.clone(), weth_stake * 10u128.pow(18)); + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(delegator.clone()), + Asset::Custom(WETH), + weth_stake, + None, + None, + )); + assert_ok!(MultiAssetDelegation::delegate( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + Asset::Custom(WETH), + weth_stake, + Default::default(), + )); + + // Apply slash + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + let slash_percent = Percent::from_percent(50); + + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + operator.clone(), + service_id, + slash_percent + )); + + // Get the unapplied slash and verify amounts + let unapplied_slash = UnappliedSlashes::::get(0, 0).unwrap(); + + // TODO: Verify slash is applied correctly + assert_ok!(MultiAssetDelegation::slash_operator(&unapplied_slash)); + }); +} + +#[test] +fn test_slash_with_no_blueprint_selection() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let operator = mock_pub_key(BOB); + let delegator = mock_pub_key(CHARLIE); + + // Initial stake amounts + let native_stake = 10_000; + + // Delegate assets but don't select any blueprints (uses default empty selection) + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + native_stake, + Default::default(), // Default blueprint selection is empty + )); + + // Get service and slashing origin + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + let slash_percent = Percent::from_percent(50); + + // Execute slash + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + operator.clone(), + service_id, + slash_percent + )); + + // Verify the unapplied slash record + let unapplied_slash = UnappliedSlashes::::get(0, 0).unwrap(); + + // TODO: Ensure that the slash is applied correctly + assert_ok!(MultiAssetDelegation::slash_operator(&unapplied_slash)); + }); +} + +#[test] +fn test_slash_with_native_delegation() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, blueprint_id, .. } = deploy(); + let operator = mock_pub_key(BOB); + let delegator1 = mock_pub_key(CHARLIE); + + // Initial setup + let initial_stake = 10_000; + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(delegator1.clone()), + operator.clone(), + initial_stake, + vec![blueprint_id].into(), + )); + + // Verify initial delegation storage + let delegator1_metadata = MultiAssetDelegation::delegators(delegator1.clone()).unwrap(); + let initial_delegation = delegator1_metadata + .delegations + .iter() + .find(|d| d.operator == operator) + .map(|d| d.amount) + .unwrap_or(0); + assert_eq!(initial_delegation, initial_stake); + + assert_eq!(delegator1_metadata.total_nomination_delegations(), initial_stake); + assert_eq!(delegator1_metadata.total_non_nomination_delegations(), 0); + + // Start a slash + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + let slash_percent = Percent::from_percent(50); + + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + operator.clone(), + service_id, + slash_percent + )); + + // Verify slash amount matches security commitment + let unapplied_slashes: Vec<_> = UnappliedSlashes::::iter_prefix(0).collect(); + assert_eq!(unapplied_slashes.len(), 1, "Should be exactly one unapplied slash"); + + // TODO: Apply the slash + }); +} + +#[test] +fn test_slash_with_partial_amounts() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let operator = mock_pub_key(BOB); + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + // Test various partial slash percentages + let slash_percentages = vec![ + Percent::from_percent(33), // 33% + Percent::from_percent(17), // 17% + Percent::from_percent(7), // 7% + ]; + + for slash_percent in slash_percentages { + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + operator.clone(), + service_id, + slash_percent + )); + + // Apply the slash + let slash = UnappliedSlashes::::get(0, 0).unwrap(); + assert_ok!(MultiAssetDelegation::slash_operator(&slash)); + } + }); +} + +#[test] +fn test_slash_with_invalid_operator() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let invalid_operator = mock_pub_key(99); // Non-existent operator + let delegator = mock_pub_key(CHARLIE); + + // Attempt to slash an unregistered operator + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + let slash_percent = Percent::from_percent(50); + + // Should fail because operator is not registered + assert_err!( + Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + invalid_operator.clone(), + service_id, + slash_percent + ), + Error::::OffenderNotOperator + ); + + // Try to slash with an account that is registered but not as an operator + assert_err!( + Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + delegator.clone(), + service_id, + slash_percent + ), + Error::::OffenderNotOperator + ); + }); +} + +#[test] +fn test_slash_with_multiple_services() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { blueprint_id, service_id, .. } = deploy(); + + // Create second service + let alice = mock_pub_key(ALICE); + let bob = mock_pub_key(BOB); + let eve = mock_pub_key(EVE); + let service2_id = Services::next_instance_id(); + assert_ok!(Services::request( + RuntimeOrigin::signed(eve.clone()), + None, + blueprint_id, + vec![alice.clone()], + vec![bob.clone()], + Default::default(), + vec![get_security_requirement(USDC, &[10, 20])], + 100, + Asset::Custom(USDC), + 0, + MembershipModel::Fixed { min_operators: 1 }, + )); + + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + service2_id, + vec![get_security_commitment(USDC, 10), get_security_commitment(TNT, 10)], + )); + + // Create slashes for both services + let service1 = Services::services(service_id).unwrap(); + let slashing_origin1 = + Services::query_slashing_origin(&service1).map(|(o, _)| o.unwrap()).unwrap(); + + let service2 = Services::services(service2_id).unwrap(); + let slashing_origin2 = + Services::query_slashing_origin(&service2).map(|(o, _)| o.unwrap()).unwrap(); + + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin1.clone()), + bob.clone(), + service_id, + Percent::from_percent(50) + )); + + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin2.clone()), + bob.clone(), + service2_id, + Percent::from_percent(25) + )); + + // Apply slashes + let slashes: Vec<_> = UnappliedSlashes::::iter_prefix(0).collect(); + for (_, slash) in slashes { + assert_ok!(MultiAssetDelegation::slash_operator(&slash)); + } + }); +} + +#[test] +fn test_slash_with_rewards_distribution() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let operator = mock_pub_key(BOB); + let service = Services::services(service_id).unwrap(); + let slashing_origin = + Services::query_slashing_origin(&service).map(|(o, _)| o.unwrap()).unwrap(); + + // Create and apply slash + assert_ok!(Services::slash( + RuntimeOrigin::signed(slashing_origin.clone()), + operator.clone(), + service_id, + Percent::from_percent(50) + )); + + let slash = UnappliedSlashes::::get(0, 0).unwrap(); + assert_ok!(MultiAssetDelegation::slash_operator(&slash)); + }); +} + +#[test] +fn test_slash_with_unauthorized_origin() { + new_test_ext(vec![ALICE, BOB, CHARLIE, DAVE, EVE]).execute_with(|| { + System::set_block_number(1); + let Deployment { service_id, .. } = deploy(); + let bob = mock_pub_key(BOB); + let eve = mock_pub_key(EVE); + let slash_percent = Percent::from_percent(50); + + // Try to slash with unauthorized origin (EVE) + assert_err!( + Services::slash( + RuntimeOrigin::signed(eve.clone()), + bob.clone(), + service_id, + slash_percent + ), + DispatchError::BadOrigin + ); + }); +} diff --git a/pallets/services/src/types.rs b/pallets/services/src/types.rs index 0b9cc2a12..754419a58 100644 --- a/pallets/services/src/types.rs +++ b/pallets/services/src/types.rs @@ -15,8 +15,6 @@ // along with Tangle. If not, see . use super::*; -use parity_scale_codec::HasCompact; -use sp_std::prelude::*; use tangle_primitives::services::Constraints; pub type BalanceOf = @@ -42,22 +40,3 @@ pub type MaxAssetsPerServiceOf = as Constraints>::MaxAsset #[codec(mel_bound(skip_type_params(T)))] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct ConstraintsOf(sp_std::marker::PhantomData); - -/// A pending slash record. The value of the slash has been computed but not applied yet, -/// rather deferred for several eras. -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct UnappliedSlash { - /// The Service Instance Id on which the slash is applied. - pub service_id: u64, - /// The account ID of the offending operator. - pub operator: AccountId, - /// The operator's own slash. - pub own: Balance, - /// All other slashed restakers and amounts. - pub others: Vec<(AccountId, Balance)>, - /// Reporters of the offence; bounty payout recipients. - pub reporters: Vec, - /// The amount of payout. - pub payout: Balance, -} diff --git a/precompiles/multi-asset-delegation/Cargo.toml b/precompiles/multi-asset-delegation/Cargo.toml index 3bccbd709..0a2fda4ea 100644 --- a/precompiles/multi-asset-delegation/Cargo.toml +++ b/precompiles/multi-asset-delegation/Cargo.toml @@ -57,9 +57,12 @@ pallet-evm-precompile-ed25519 = { workspace = true, optional = true } pallet-evm-precompile-modexp = { workspace = true, optional = true } pallet-evm-precompile-sha3fips = { workspace = true, optional = true } pallet-evm-precompile-simple = { workspace = true, optional = true } +pallet-evm-precompile-staking = { workspace = true, optional = true } pallet-session = { workspace = true, optional = true } pallet-staking = { workspace = true, optional = true } +pallet-staking-reward-curve = { workspace = true, optional = true } sp-staking = { workspace = true, optional = true } +sp-keyring = { workspace = true, optional = true } frame-election-provider-support = { workspace = true, optional = true } ethabi = { workspace = true, optional = true } @@ -76,11 +79,14 @@ serde_json = { workspace = true } smallvec = { workspace = true } sp-keystore = { workspace = true } - precompile-utils = { workspace = true, features = ["std", "testing"] } # Substrate +frame-election-provider-support = { workspace = true, features = ["std"] } pallet-balances = { workspace = true, features = ["std"] } +pallet-session = { workspace = true, features = ["std"] } +pallet-staking = { workspace = true, features = ["std"] } +pallet-staking-reward-curve = { workspace = true } pallet-assets = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true, features = ["std"] } scale-info = { workspace = true, features = ["derive", "std"] } @@ -109,13 +115,11 @@ pallet-evm-precompile-ed25519 = { workspace = true } pallet-evm-precompile-modexp = { workspace = true } pallet-evm-precompile-sha3fips = { workspace = true } pallet-evm-precompile-simple = { workspace = true } - -pallet-session = { workspace = true } -pallet-staking = { workspace = true } +pallet-evm-precompile-staking = { workspace = true } sp-staking = { workspace = true } -frame-election-provider-support = { workspace = true } +sp-keyring = { workspace = true } -ethabi = { workspace = true } +ethabi = { workspace = true, features = ["serde"] } [features] default = ["std"] @@ -155,7 +159,9 @@ fuzzing = [ "pallet-evm-precompile-simple", "pallet-session", "pallet-staking", + "pallet-staking-reward-curve", "sp-staking", + "sp-keyring", "frame-election-provider-support", ] std = [ @@ -202,6 +208,7 @@ std = [ "pallet-evm-precompile-bn128/std", "pallet-evm-precompile-curve25519/std", "pallet-evm-precompile-ed25519/std", + "pallet-evm-precompile-staking/std", "precompile-utils/std", "serde/std", "pallet-session/std", @@ -209,4 +216,6 @@ std = [ "sp-staking/std", "frame-election-provider-support/std", "evm-erc20-utils/std", + "sp-keyring/std", + "ethabi/std", ] diff --git a/precompiles/multi-asset-delegation/MultiAssetDelegation.sol b/precompiles/multi-asset-delegation/MultiAssetDelegation.sol index 6b83ee281..69f9ea9e0 100644 --- a/precompiles/multi-asset-delegation/MultiAssetDelegation.sol +++ b/precompiles/multi-asset-delegation/MultiAssetDelegation.sol @@ -89,4 +89,35 @@ interface MultiAssetDelegation { /// @return The delegated balance of the delegator. /// @custom:selector aabd20df function delegatedBalanceOf(address who, uint256 assetId, address tokenAddress) external view returns (uint256); + + /// @dev Delegate nominated stake (native restaking) to an operator. + /// @param operator The address of the operator. + /// @param amount The amount to delegate. + /// @param blueprintSelection The blueprint selection. + /// @custom:selector accc8f88 + function delegateNomination(bytes32 operator, uint256 amount, uint64[] memory blueprintSelection) external; + + /// @dev Schedule an unstake of nominations as a delegator. + /// @param operator The address of the operator. + /// @param amount The amount to unstake. + /// @param blueprintSelection The blueprint selection. + /// @custom:selector fdd20008 + function scheduleDelegatorNominationUnstake(bytes32 operator, uint256 amount, uint64[] memory blueprintSelection) + external; + + /// @dev Execute the scheduled nomination unstake as a delegator. + /// @param operator The address of the operator. + /// @custom:selector cf2ce918 + function executeDelegatorNominationUnstake(bytes32 operator) external; + + /// @dev Cancel the scheduled nomination unstake as a delegator. + /// @param operator The address of the operator. + /// @custom:selector a9332214 + function cancelDelegatorNominationUnstake(bytes32 operator) external; + + /// @dev Get the delegated nomination balance of the delegator. + /// @param who The address of the delegator. + /// @return The delegated nomination balance of the delegator. + /// @custom:selector d1909653 + function delegatedNominationBalance(address who) external view returns (uint256); } diff --git a/precompiles/multi-asset-delegation/fuzzer/call.rs b/precompiles/multi-asset-delegation/fuzzer/call.rs index 7be9919e3..fe50823d2 100644 --- a/precompiles/multi-asset-delegation/fuzzer/call.rs +++ b/precompiles/multi-asset-delegation/fuzzer/call.rs @@ -62,20 +62,20 @@ fn random_ed_multiple(rng: &mut R) -> Balance { } fn random_asset(rng: &mut R) -> Asset { - let asset_id = rng.gen_range(1..u128::MAX); + let asset = rng.gen_range(1..u128::MAX); let is_evm = rng.gen_bool(0.5); if is_evm { let evm_address = rng.gen::<[u8; 20]>().into(); Asset::Erc20(evm_address) } else { - Asset::Custom(asset_id) + Asset::Custom(asset) } } fn fund_account(rng: &mut R, address: &Address) { let target_amount = random_ed_multiple(rng); let signer = >::into_account_id(address.0); - if let Some(top_up) = target_amount.checked_sub(Balances::free_balance(signer)) { + if let Some(top_up) = target_amount.checked_sub(Balances::free_balance(signer.clone())) { let _ = Balances::deposit_creating(&signer, top_up); } assert!(Balances::free_balance(signer) >= target_amount); @@ -99,11 +99,19 @@ fn random_calls(mut rng: &mut R) -> impl IntoIterator (id.into(), Default::default()), - Asset::Erc20(token) => (0.into(), token.into()), + Asset::Custom(id) => (id, Default::default()), + Asset::Erc20(token) => (0u128, token.into()), }; let amount = random_ed_multiple(&mut rng).into(); - vec![(PCall::deposit { asset_id, amount, token_address, lock_multiplier: 0 }, who)] + vec![( + PCall::deposit { + asset_id: asset_id.into(), + amount, + token_address, + lock_multiplier: 0, + }, + who, + )] }, _ if op == PCall::schedule_withdraw_selectors()[0] => { // Schedule withdraw @@ -273,7 +281,7 @@ fn do_sanity_checks(call: PCall, origin: Address, outcome: PrecompileOutput) { (0, erc20_token) if erc20_token != [0; 20] => { (Asset::Erc20(erc20_token.into()), amount) }, - (other_asset_id, _) => (Asset::Custom(other_asset_id.into()), amount), + (other_asset, _) => (Asset::Custom(other_asset.into()), amount), }; match deposit_asset { Asset::Custom(id) => { @@ -304,7 +312,7 @@ fn do_sanity_checks(call: PCall, origin: Address, outcome: PrecompileOutput) { (0, erc20_token) if erc20_token != [0; 20] => { (Asset::Erc20(erc20_token.into()), amount) }, - (other_asset_id, _) => (Asset::Custom(other_asset_id.into()), amount), + (other_asset, _) => (Asset::Custom(other_asset.into()), amount), }; let round = MultiAssetDelegation::current_round(); assert!( @@ -312,7 +320,7 @@ fn do_sanity_checks(call: PCall, origin: Address, outcome: PrecompileOutput) { .unwrap_or_default() .get_withdraw_requests() .contains(&WithdrawRequest { - asset_id: deposit_asset, + asset: deposit_asset, amount: amount.as_u64(), requested_round: round }), @@ -335,14 +343,14 @@ fn do_sanity_checks(call: PCall, origin: Address, outcome: PrecompileOutput) { (0, erc20_token) if erc20_token != [0; 20] => { (Asset::Erc20(erc20_token.into()), amount) }, - (other_asset_id, _) => (Asset::Custom(other_asset_id.into()), amount), + (other_asset, _) => (Asset::Custom(other_asset.into()), amount), }; assert!( !MultiAssetDelegation::delegators(caller) .unwrap_or_default() .get_withdraw_requests() .contains(&WithdrawRequest { - asset_id: deposit_asset, + asset: deposit_asset, amount: amount.as_u64(), requested_round: round }), @@ -354,18 +362,18 @@ fn do_sanity_checks(call: PCall, origin: Address, outcome: PrecompileOutput) { (0, erc20_token) if erc20_token != [0; 20] => { (Asset::Erc20(erc20_token.into()), amount) }, - (other_asset_id, _) => (Asset::Custom(other_asset_id.into()), amount), + (other_asset, _) => (Asset::Custom(other_asset.into()), amount), }; let operator_account = AccountId::from(operator.0); - let delegator = MultiAssetDelegation::delegators(caller).unwrap_or_default(); + let delegator = MultiAssetDelegation::delegators(caller.clone()).unwrap_or_default(); let operator_info = - MultiAssetDelegation::operator_info(operator_account).unwrap_or_default(); + MultiAssetDelegation::operator_info(operator_account.clone()).unwrap_or_default(); assert!( delegator .calculate_delegation_by_operator(operator_account) .iter() .find_map(|x| { - if x.asset_id == deposit_asset { + if x.asset == deposit_asset { Some(x.amount) } else { None @@ -379,7 +387,7 @@ fn do_sanity_checks(call: PCall, origin: Address, outcome: PrecompileOutput) { .delegations .iter() .find_map(|x| { - if x.delegator == caller && x.asset_id == deposit_asset { + if x.delegator == caller && x.asset == deposit_asset { Some(x.amount) } else { None diff --git a/precompiles/multi-asset-delegation/src/lib.rs b/precompiles/multi-asset-delegation/src/lib.rs index 1d898556a..b0b9e5017 100644 --- a/precompiles/multi-asset-delegation/src/lib.rs +++ b/precompiles/multi-asset-delegation/src/lib.rs @@ -38,6 +38,8 @@ pub mod mock; #[cfg(any(test, feature = "fuzzing"))] pub mod mock_evm; #[cfg(test)] +mod native_restaking_tests; +#[cfg(test)] mod tests; use tangle_primitives::types::rewards::LockMultiplier; @@ -133,7 +135,7 @@ where pallet_multi_asset_delegation::Pallet::::ready_withdraw_requests(&who) .map_err(|_| revert("Failed to get ready withdraw requests"))?; - let erc20_transfers = snapshot.filter_map(|request| match request.asset_id { + let erc20_transfers = snapshot.filter_map(|request| match request.asset { Asset::Erc20(token) => Some((token, request.amount)), _ => None, }); @@ -204,7 +206,7 @@ where handle, Some(who).into(), pallet_multi_asset_delegation::Call::::deposit { - asset_id: deposit_asset, + asset: deposit_asset, amount: amount .try_into() .map_err(|_| RevertReason::value_is_too_large("amount"))?, @@ -239,7 +241,7 @@ where handle, Some(who).into(), pallet_multi_asset_delegation::Call::::schedule_withdraw { - asset_id: deposit_asset, + asset: deposit_asset, amount: amount .try_into() .map_err(|_| RevertReason::value_is_too_large("amount"))?, @@ -272,7 +274,7 @@ where handle, Some(who).into(), pallet_multi_asset_delegation::Call::::cancel_withdraw { - asset_id: deposit_asset, + asset: deposit_asset, amount: amount .try_into() .map_err(|_| RevertReason::value_is_too_large("amount"))?, @@ -309,7 +311,7 @@ where Some(who).into(), pallet_multi_asset_delegation::Call::::delegate { operator, - asset_id: deposit_asset, + asset: deposit_asset, amount: amount .try_into() .map_err(|_| RevertReason::value_is_too_large("amount"))?, @@ -350,7 +352,7 @@ where Some(who).into(), pallet_multi_asset_delegation::Call::::schedule_delegator_unstake { operator, - asset_id: deposit_asset, + asset: deposit_asset, amount: amount .try_into() .map_err(|_| RevertReason::value_is_too_large("amount"))?, @@ -397,7 +399,7 @@ where Some(who).into(), pallet_multi_asset_delegation::Call::::cancel_delegator_unstake { operator, - asset_id: deposit_asset, + asset: deposit_asset, amount: amount .try_into() .map_err(|_| RevertReason::value_is_too_large("amount"))?, @@ -406,4 +408,133 @@ where Ok(()) } + + #[precompile::public("delegateNomination(bytes32,uint256,uint64[])")] + fn delegate_nomination( + handle: &mut impl PrecompileHandle, + operator: H256, + amount: U256, + blueprint_selection: Vec, + ) -> EvmResult { + // Record both read and write costs since we'll be modifying state + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; + + let caller = handle.context().caller; + let who = Runtime::AddressMapping::into_account_id(caller); + let operator = Runtime::AccountId::from(WrappedAccountId32(operator.0)); + + // Validate amount before dispatching + let amount: BalanceOf = + amount.try_into().map_err(|_| RevertReason::value_is_too_large("amount"))?; + + // Convert blueprint selection + let blueprint_selection = DelegatorBlueprintSelection::Fixed( + blueprint_selection + .try_into() + .map_err(|_| RevertReason::custom("Too many blueprint ids for fixed selection"))?, + ); + + // Dispatch the call + RuntimeHelper::::try_dispatch( + handle, + Some(who).into(), + pallet_multi_asset_delegation::Call::::delegate_nomination { + operator, + amount, + blueprint_selection, + }, + )?; + + Ok(()) + } + + #[precompile::public("scheduleDelegatorNominationUnstake(bytes32,uint256,uint64[])")] + fn schedule_delegator_nomination_unstake( + handle: &mut impl PrecompileHandle, + operator: H256, + amount: U256, + blueprint_selection: Vec, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let caller = handle.context().caller; + let who = Runtime::AddressMapping::into_account_id(caller); + let operator = Runtime::AccountId::from(WrappedAccountId32(operator.0)); + + RuntimeHelper::::try_dispatch( + handle, + Some(who).into(), + pallet_multi_asset_delegation::Call::::schedule_nomination_unstake { + operator, + amount: amount + .try_into() + .map_err(|_| RevertReason::value_is_too_large("amount"))?, + blueprint_selection: DelegatorBlueprintSelection::Fixed( + blueprint_selection.try_into().map_err(|_| { + RevertReason::custom("Too many blueprint ids for fixed selection") + })?, + ), + }, + )?; + + Ok(()) + } + + #[precompile::public("executeDelegatorNominationUnstake(bytes32)")] + fn execute_delegator_nomination_unstake( + handle: &mut impl PrecompileHandle, + operator: H256, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let caller = handle.context().caller; + let who = Runtime::AddressMapping::into_account_id(caller); + let operator = Runtime::AccountId::from(WrappedAccountId32(operator.0)); + + RuntimeHelper::::try_dispatch( + handle, + Some(who).into(), + pallet_multi_asset_delegation::Call::::execute_nomination_unstake { operator }, + )?; + + Ok(()) + } + + #[precompile::public("cancelDelegatorNominationUnstake(bytes32)")] + fn cancel_delegator_nomination_unstake( + handle: &mut impl PrecompileHandle, + operator: H256, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let caller = handle.context().caller; + let who = Runtime::AddressMapping::into_account_id(caller); + let operator = Runtime::AccountId::from(WrappedAccountId32(operator.0)); + + RuntimeHelper::::try_dispatch( + handle, + Some(who).into(), + pallet_multi_asset_delegation::Call::::cancel_nomination_unstake { operator }, + )?; + + Ok(()) + } + + #[precompile::public("delegatedNominationBalance(address)")] + fn delegated_nomination_balance( + handle: &mut impl PrecompileHandle, + who: Address, + ) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let who = Runtime::AddressMapping::into_account_id(who.0); + let Some(delegator) = pallet_multi_asset_delegation::Pallet::::delegators(&who) + else { + return Ok(U256::zero()); + }; + let balance = delegator.total_nomination_delegations(); + + Ok(balance.into()) + } } diff --git a/precompiles/multi-asset-delegation/src/mock.rs b/precompiles/multi-asset-delegation/src/mock.rs index 2f68c7c19..9b361183e 100644 --- a/precompiles/multi-asset-delegation/src/mock.rs +++ b/precompiles/multi-asset-delegation/src/mock.rs @@ -21,33 +21,35 @@ use super::*; use crate::mock_evm::*; use core::ops::Mul; use ethabi::Uint; +use frame_election_provider_support::bounds::{ElectionBounds, ElectionBoundsBuilder}; +use frame_election_provider_support::onchain; +use frame_election_provider_support::SequentialPhragmen; use frame_support::{ construct_runtime, derive_impl, parameter_types, - traits::{AsEnsureOriginWithArg, ConstU64}, + traits::{AsEnsureOriginWithArg, ConstU64, OneSessionHandler}, weights::Weight, PalletId, }; use pallet_evm::GasWeightMapping; +use pallet_session::historical as pallet_session_historical; +use pallet_staking::ConvertCurve; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use serde_json::json; -use sp_core::{ - self, - sr25519::{Public as sr25519Public, Signature}, - ConstU32, H160, -}; +use sp_core::{self, sr25519::Public as sr25519Public, ConstU32, H160}; +use sp_keyring::AccountKeyring; use sp_keystore::{testing::MemoryKeystore, KeystoreExt, KeystorePtr}; +use sp_runtime::curve::PiecewiseLinear; +use sp_runtime::testing::UintAuthorityId; use sp_runtime::DispatchError; -use sp_runtime::{ - traits::{IdentifyAccount, Verify}, - AccountId32, BuildStorage, -}; +use sp_runtime::{AccountId32, BuildStorage, Perbill}; +use sp_staking::{EraIndex, SessionIndex}; use tangle_primitives::services::EvmRunner; use tangle_primitives::services::{EvmAddressMapping, EvmGasWeightMapping}; use tangle_primitives::traits::{RewardsManager, ServiceManager}; -pub type AccountId = <::Signer as IdentifyAccount>::AccountId; +pub type AccountId = AccountId32; pub type Balance = u64; pub type BlockNumber = u64; @@ -63,6 +65,7 @@ const PRECOMPILE_ADDRESS_BYTES: [u8; 32] = [ PartialEq, Ord, PartialOrd, + Copy, Clone, Encode, Decode, @@ -174,6 +177,9 @@ construct_runtime!( Balances: pallet_balances, Evm: pallet_evm, Ethereum: pallet_ethereum, + Session: pallet_session, + Staking: pallet_staking, + Historical: pallet_session_historical, Timestamp: pallet_timestamp, Assets: pallet_assets, MultiAssetDelegation: pallet_multi_asset_delegation, @@ -271,6 +277,10 @@ impl ServiceManager for MockServiceManager { // we don't care Default::default() } + + fn has_active_services(_operator: &AccountId) -> bool { + false + } } pub struct PalletEVMGasWeightMapping; @@ -308,17 +318,24 @@ parameter_types! { pub const MinOperatorBondAmount: u64 = 10_000; pub const BondDuration: u32 = 10; pub PID: PalletId = PalletId(*b"PotStake"); - pub SlashedAmountRecipient : AccountId = TestAccount::Alex.into(); + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxDelegatorBlueprints : u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxOperatorBlueprints : u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxWithdrawRequests: u32 = 5; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxUnstakeRequests: u32 = 5; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxDelegations: u32 = 50; + + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub const SlashRecipient: AccountId = AccountId32::new([9u8; 32]); } pub struct MockRewardsManager; @@ -364,7 +381,10 @@ impl pallet_multi_asset_delegation::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type MinOperatorBondAmount = MinOperatorBondAmount; + type SlashRecipient = SlashRecipient; type BondDuration = BondDuration; + type CurrencyToVote = (); + type StakingInterface = Staking; type ServiceManager = MockServiceManager; type LeaveOperatorsDelay = ConstU32<10>; type EvmRunner = MockedEvmRunner; @@ -382,12 +402,152 @@ impl pallet_multi_asset_delegation::Config for Runtime { type MaxWithdrawRequests = MaxWithdrawRequests; type MaxUnstakeRequests = MaxUnstakeRequests; type MaxDelegations = MaxDelegations; - type SlashedAmountRecipient = SlashedAmountRecipient; type PalletId = PID; type RewardsManager = MockRewardsManager; type WeightInfo = (); } +parameter_types! { + pub static MaxNominations: u32 = 16; + pub static HistoryDepth: u32 = 80; + pub static MaxUnlockingChunks: u32 = 32; + pub static RewardOnUnbalanceWasCalled: bool = false; + pub static MaxWinners: u32 = 100; +} + +impl pallet_session::historical::Config for Runtime { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; +} + +pallet_staking_reward_curve::build! { + const REWARD_CURVE: PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} + +parameter_types! { + pub const BondingDuration: EraIndex = 3; + pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; +} + +pub struct MockSessionHandler; +impl OneSessionHandler for MockSessionHandler { + type Key = UintAuthorityId; + + fn on_genesis_session<'a, I>(_: I) + where + I: Iterator, + AccountId: 'a, + I: 'a, + { + } + + fn on_new_session<'a, I>(_: bool, _: I, _: I) + where + I: Iterator, + AccountId: 'a, + I: 'a, + { + } + + fn on_disabled(_validator_index: u32) {} +} + +impl sp_runtime::BoundToRuntimeAppPublic for MockSessionHandler { + type Public = UintAuthorityId; +} + +sp_runtime::impl_opaque_keys! { + pub struct MockSessionKeys { + pub dummy: MockSessionHandler, + } +} + +parameter_types! { + pub static SessionsPerEra: SessionIndex = 3; + pub static SlashDeferDuration: EraIndex = 0; + pub static Period: BlockNumber = 5; + pub static Offset: BlockNumber = 0; +} + +impl pallet_session::Config for Runtime { + type SessionManager = Staking; + type Keys = MockSessionKeys; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionHandler = (MockSessionHandler,); + type RuntimeEvent = RuntimeEvent; + type ValidatorId = AccountId; + type ValidatorIdOf = pallet_staking::StashOf; + type WeightInfo = (); +} + +parameter_types! { + pub ElectionBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default() + .voters_count(5_000.into()).targets_count(1_250.into()).build(); + pub ElectionBoundsMultiPhase: ElectionBounds = ElectionBoundsBuilder::default() + .voters_count(10_000.into()).targets_count(1_500.into()).build(); +} + +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Runtime; + type Solver = SequentialPhragmen; + type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = ConstU32<100>; + type Bounds = ElectionBoundsOnChain; +} + +/// Upper limit on the number of NPOS nominations. +const MAX_QUOTA_NOMINATIONS: u32 = 16; + +pub struct MockReward {} +impl frame_support::traits::OnUnbalanced> + for MockReward +{ + fn on_unbalanced(_: pallet_balances::PositiveImbalance) { + RewardOnUnbalanceWasCalled::set(true); + } +} + +impl pallet_staking::Config for Runtime { + type Currency = Balances; + type CurrencyBalance = ::Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = (); + type RewardRemainder = (); + type RuntimeEvent = RuntimeEvent; + type Slash = (); + type Reward = MockReward; + type SessionsPerEra = SessionsPerEra; + type SlashDeferDuration = SlashDeferDuration; + type AdminOrigin = frame_system::EnsureRoot; + type BondingDuration = (); + type SessionInterface = Self; + type EraPayout = ConvertCurve; + type MaxExposurePageSize = ConstU32<64>; + type MaxControllersInDeprecationBatch = ConstU32<100>; + type NextNewSession = Session; + type ElectionProvider = onchain::OnChainExecution; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type EventListeners = (); + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type NominationsQuota = pallet_staking::FixedNominationsQuota; + type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; +} + /// Build test externalities, prepopulated with data for testing democracy precompiles #[derive(Default)] pub struct ExtBuilder { @@ -408,6 +568,12 @@ pub const USDC_ERC20: H160 = H160([0x23; 20]); impl ExtBuilder { /// Build the test externalities for use in tests pub fn build(self) -> sp_io::TestExternalities { + // We use default for brevity, but you can configure as desired if needed. + let authorities: Vec = vec![ + AccountKeyring::Alice.into(), + AccountKeyring::Bob.into(), + AccountKeyring::Charlie.into(), + ]; let mut t = frame_system::GenesisConfig::::default() .build_storage() .expect("Frame system builds valid default genesis config"); @@ -416,13 +582,22 @@ impl ExtBuilder { balances: self .balances .iter() + .chain( + [ + (AccountKeyring::Alice.into(), 1_000_000), + (AccountKeyring::Bob.into(), 1_000_000), + (AccountKeyring::Charlie.into(), 1_000_000), + (AccountKeyring::Dave.into(), 1_000_000), + (AccountKeyring::Eve.into(), 1_000_000), + (MultiAssetDelegation::pallet_account(), 100), + ] + .iter(), + ) .chain( [ (TestAccount::Alex.into(), 1_000_000), (TestAccount::Bobo.into(), 1_000_000), (TestAccount::Charlie.into(), 1_000_000), - (MultiAssetDelegation::pallet_account(), 100), /* give pallet some ED so - * it can receive tokens */ ] .iter(), ) @@ -456,7 +631,7 @@ impl ExtBuilder { for a in &accounts { evm_accounts.insert( - a.clone().into(), + (*a).into(), fp_evm::GenesisAccount { code: vec![], storage: Default::default(), @@ -471,6 +646,13 @@ impl ExtBuilder { evm_config.assimilate_storage(&mut t).unwrap(); + let staking_config = pallet_staking::GenesisConfig:: { + validator_count: 3, + invulnerables: authorities.clone(), + ..Default::default() + }; + + staking_config.assimilate_storage(&mut t).unwrap(); // assets_config.assimilate_storage(&mut t).unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.register_extension(KeystoreExt(Arc::new(MemoryKeystore::new()) as KeystorePtr)); diff --git a/precompiles/multi-asset-delegation/src/mock_evm.rs b/precompiles/multi-asset-delegation/src/mock_evm.rs index 85598f555..2d8e1ec78 100644 --- a/precompiles/multi-asset-delegation/src/mock_evm.rs +++ b/precompiles/multi-asset-delegation/src/mock_evm.rs @@ -27,6 +27,7 @@ use frame_support::{ }; use pallet_ethereum::{EthereumBlockHashMapping, IntermediateStateRoot, PostLogContent, RawOrigin}; use pallet_evm::{EnsureAddressNever, EnsureAddressOrigin, OnChargeEVMTransaction}; +use pallet_evm_precompile_staking::{StakingPrecompile, StakingPrecompileCall}; use precompile_utils::precompile_set::{ AddressU64, PrecompileAt, PrecompileSetBuilder, SubcallWithMaxNesting, }; @@ -40,10 +41,14 @@ use tangle_primitives::services::EvmRunner; pub type Precompiles = PrecompileSetBuilder< R, - (PrecompileAt, MultiAssetDelegationPrecompile, (SubcallWithMaxNesting<2>,)>,), + ( + PrecompileAt, MultiAssetDelegationPrecompile, (SubcallWithMaxNesting<2>,)>, + PrecompileAt, StakingPrecompile, (SubcallWithMaxNesting<2>,)>, + ), >; pub type PCall = MultiAssetDelegationPrecompileCall; +pub type SCall = StakingPrecompileCall; parameter_types! { pub const MinimumPeriod: u64 = 6000 / 2; diff --git a/precompiles/multi-asset-delegation/src/native_restaking_tests.rs b/precompiles/multi-asset-delegation/src/native_restaking_tests.rs new file mode 100644 index 000000000..f62beca4b --- /dev/null +++ b/precompiles/multi-asset-delegation/src/native_restaking_tests.rs @@ -0,0 +1,287 @@ +use crate::{mock::*, mock_evm::*}; +use frame_support::{assert_ok, traits::Currency}; +use pallet_multi_asset_delegation::{CurrentRound, Delegators}; +use precompile_utils::testing::*; +use sp_core::{H160, H256, U256}; +use sp_keyring::AccountKeyring; + +#[test] +fn test_delegate_nomination_through_precompile() { + ExtBuilder::default().build().execute_with(|| { + let delegator = TestAccount::Alex; + let operator: AccountId = AccountKeyring::Alice.into(); + let validator = Staking::invulnerables()[0].clone(); + let amount = 100_000; + let delegate_amount = amount / 2; + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Bond and nominate through staking precompile + Balances::make_free_balance_be(&delegator.into(), amount); + + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(2), // Staking precompile address + SCall::bond { + value: U256::from(amount), + payee: H256([0; 32]), // Stash + }, + ) + .execute_returns(()); + + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(2), + SCall::nominate { targets: vec![H256::from(validator.as_ref())] }, + ) + .execute_returns(()); + + // Delegate nomination through multi-asset delegation precompile + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(1), + PCall::delegate_nomination { + operator: H256::from(operator.as_ref()), + amount: U256::from(delegate_amount), + blueprint_selection: Default::default(), + }, + ) + .execute_returns(()); + + // Verify delegation + let delegator_account_id: AccountId = delegator.into(); + let delegator = MultiAssetDelegation::delegators(delegator_account_id).unwrap(); + assert_eq!(delegator.total_nomination_delegations(), delegate_amount); + }); +} + +#[test] +fn test_delegate_nomination_invalid_operator() { + ExtBuilder::default().build().execute_with(|| { + let delegator = TestAccount::Alex; + let invalid_operator: AccountId = AccountKeyring::Bob.into(); + let validator = Staking::invulnerables()[0].clone(); + let amount = 100_000; + let delegate_amount = amount / 2; + + // Bond and nominate through staking precompile + Balances::make_free_balance_be(&delegator.into(), amount); + + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(2), + SCall::bond { + value: U256::from(amount), + payee: H256([0; 32]), + }, + ) + .execute_returns(()); + + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(2), + SCall::nominate { + targets: vec![H256::from(validator.as_ref())], + }, + ) + .execute_returns(()); + + // Try to delegate to invalid operator + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(1), + PCall::delegate_nomination { + operator: H256::from(invalid_operator.as_ref()), + amount: U256::from(delegate_amount), + blueprint_selection: Default::default(), + }, + ) + .execute_reverts(|output| output == b"Dispatched call failed with error: Module(ModuleError { index: 9, error: [3, 0, 0, 0], message: Some(\"NotAnOperator\") })"); + }); +} + +#[test] +fn test_delegate_nomination_insufficient_balance() { + ExtBuilder::default().build().execute_with(|| { + let delegator = TestAccount::Alex; + let operator: AccountId = AccountKeyring::Alice.into(); + let validator = Staking::invulnerables()[0].clone(); + let amount = 100_000; + let delegate_amount = amount * 2; // More than bonded + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Bond and nominate through staking precompile + Balances::make_free_balance_be(&delegator.into(), amount); + + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(2), + SCall::bond { + value: U256::from(amount), + payee: H256([0; 32]), + }, + ) + .execute_returns(()); + + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(2), + SCall::nominate { + targets: vec![H256::from(validator.as_ref())], + }, + ) + .execute_returns(()); + + // Try to delegate more than bonded + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(1), + PCall::delegate_nomination { + operator: H256::from(operator.as_ref()), + amount: U256::from(delegate_amount), + blueprint_selection: Default::default(), + }, + ) + .execute_reverts(|output| output == b"Dispatched call failed with error: Module(ModuleError { index: 9, error: [15, 0, 0, 0], message: Some(\"InsufficientBalance\") })"); + }); +} + +#[test] +fn test_delegate_nomination_unstake_lifecycle() { + ExtBuilder::default().build().execute_with(|| { + let delegator = TestAccount::Alex; + let operator: AccountId = AccountKeyring::Alice.into(); + let validator = Staking::invulnerables()[0].clone(); + let amount = 100_000; + let delegate_amount = amount / 2; + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Bond and nominate through staking precompile + Balances::make_free_balance_be(&delegator.into(), amount); + + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(2), + SCall::bond { value: U256::from(amount), payee: H256([0; 32]) }, + ) + .execute_returns(()); + + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(2), + SCall::nominate { targets: vec![H256::from(validator.as_ref())] }, + ) + .execute_returns(()); + + // Delegate nomination + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(1), + PCall::delegate_nomination { + operator: H256::from(operator.as_ref()), + amount: U256::from(delegate_amount), + blueprint_selection: Default::default(), + }, + ) + .execute_returns(()); + + // Schedule unstake + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(1), + PCall::schedule_delegator_nomination_unstake { + operator: H256::from(operator.as_ref()), + amount: U256::from(delegate_amount), + blueprint_selection: Default::default(), + }, + ) + .execute_returns(()); + + // Verify unstake request exists + let delegator_account: AccountId = delegator.into(); + let metadata = Delegators::::get(&delegator_account).unwrap(); + assert_eq!(metadata.delegator_unstake_requests.len(), 1); + + // Cancel unstake + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(1), + PCall::cancel_delegator_nomination_unstake { + operator: H256::from(operator.as_ref()), + }, + ) + .execute_returns(()); + + // Verify unstake request was cancelled + let metadata = Delegators::::get(&delegator_account).unwrap(); + assert_eq!(metadata.delegator_unstake_requests.len(), 0); + + // Schedule unstake again + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(1), + PCall::schedule_delegator_nomination_unstake { + operator: H256::from(operator.as_ref()), + amount: U256::from(delegate_amount), + blueprint_selection: Default::default(), + }, + ) + .execute_returns(()); + + // Advance rounds to make unstake executable + CurrentRound::::put(100); + + // Execute unstake + PrecompilesValue::get() + .prepare_test( + delegator, + H160::from_low_u64_be(1), + PCall::execute_delegator_nomination_unstake { + operator: H256::from(operator.as_ref()), + }, + ) + .execute_returns(()); + + // Verify unstake was executed + let metadata = Delegators::::get(&delegator_account).unwrap(); + assert_eq!(metadata.delegator_unstake_requests.len(), 0); + assert_eq!(metadata.total_nomination_delegations(), 0); + }); +} + +#[test] +fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() { + check_precompile_implements_solidity_interfaces( + &["MultiAssetDelegation.sol"], + PCall::supports_selector, + ) +} diff --git a/precompiles/multi-asset-delegation/src/tests.rs b/precompiles/multi-asset-delegation/src/tests.rs index d228be74f..b9741503b 100644 --- a/precompiles/multi-asset-delegation/src/tests.rs +++ b/precompiles/multi-asset-delegation/src/tests.rs @@ -3,17 +3,14 @@ use frame_support::{assert_ok, traits::Currency}; use pallet_multi_asset_delegation::{CurrentRound, Delegators, Operators}; use precompile_utils::prelude::*; use precompile_utils::testing::*; -use sp_core::H160; +use sp_core::{H160, H256}; +use sp_runtime::AccountId32; use tangle_primitives::services::Asset; // Helper function for creating and minting tokens -pub fn create_and_mint_tokens( - asset_id: u128, - recipient: ::AccountId, - amount: Balance, -) { - assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, recipient, false, 1)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(recipient), asset_id, recipient, amount)); +pub fn create_and_mint_tokens(asset_id: u128, recipient: AccountId32, amount: Balance) { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, recipient.clone(), false, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(recipient.clone()), asset_id, recipient, amount)); } #[test] @@ -37,26 +34,32 @@ fn test_unimplemented_selector_reverts() { #[test] fn test_delegate_assets_invalid_operator() { ExtBuilder::default().build().execute_with(|| { - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); + let delegator_account: AccountId = TestAccount::Alex.into(); Balances::make_free_balance_be(&delegator_account, 500); - create_and_mint_tokens(1, delegator_account, 500); + create_and_mint_tokens(1, delegator_account.clone(), 500); - assert_ok!(MultiAssetDelegation::deposit(RuntimeOrigin::signed(delegator_account), Asset::Custom(1), 200, Some(TestAccount::Alex.into()), None)); + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(delegator_account.clone()), + Asset::Custom(1), + 200, + Some(TestAccount::Alex.into()), + None + )); PrecompilesValue::get() .prepare_test( TestAccount::Alex, H160::from_low_u64_be(1), PCall::delegate { - operator: sp_core::sr25519::Public::from(TestAccount::Eve).into(), + operator: H256::from(sp_runtime::AccountId32::from(TestAccount::Eve).as_ref()), asset_id: U256::from(1), amount: U256::from(100), blueprint_selection: Default::default(), token_address: Default::default(), }, ) - .execute_reverts(|output| output == b"Dispatched call failed with error: Module(ModuleError { index: 6, error: [2, 0, 0, 0], message: Some(\"NotAnOperator\") })"); + .execute_reverts(|output| output == b"Dispatched call failed with error: Module(ModuleError { index: 9, error: [3, 0, 0, 0], message: Some(\"NotAnOperator\") })"); assert_eq!(Balances::free_balance(delegator_account), 500); }); @@ -65,10 +68,10 @@ fn test_delegate_assets_invalid_operator() { #[test] fn test_deposit_assets() { ExtBuilder::default().build().execute_with(|| { - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); + let delegator_account: AccountId = TestAccount::Alex.into(); Balances::make_free_balance_be(&delegator_account, 500); - create_and_mint_tokens(1, delegator_account, 500); + create_and_mint_tokens(1, delegator_account.clone(), 500); PrecompilesValue::get() .prepare_test( TestAccount::Alex, @@ -82,7 +85,7 @@ fn test_deposit_assets() { ) .execute_returns(()); - assert_eq!(Assets::balance(1, delegator_account), 500 - 200); // should lose deposit + assert_eq!(Assets::balance(1, delegator_account.clone()), 500 - 200); // should lose deposit assert!(Delegators::::get(delegator_account).is_some()); }); @@ -91,10 +94,10 @@ fn test_deposit_assets() { #[test] fn test_deposit_assets_insufficient_balance() { ExtBuilder::default().build().execute_with(|| { - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); + let delegator_account: AccountId = TestAccount::Alex.into(); Balances::make_free_balance_be(&delegator_account, 500); - create_and_mint_tokens(1, delegator_account, 200); + create_and_mint_tokens(1, delegator_account.clone(), 200); PrecompilesValue::get() .prepare_test( TestAccount::Alex, @@ -110,16 +113,16 @@ fn test_deposit_assets_insufficient_balance() { output == b"Dispatched call failed with error: Arithmetic(Underflow)" }); - assert_eq!(Assets::balance(1, delegator_account), 200); // should not lose deposit + assert_eq!(Assets::balance(1, &delegator_account), 200); // should not lose deposit - assert!(Delegators::::get(delegator_account).is_none()); + assert!(Delegators::::get(&delegator_account).is_none()); }); } #[test] fn test_deposit_assets_erc20() { ExtBuilder::default().build().execute_with(|| { - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); + let delegator_account: AccountId = TestAccount::Alex.into(); Balances::make_free_balance_be(&delegator_account, 500); PrecompilesValue::get() @@ -156,7 +159,7 @@ fn test_deposit_assets_erc20() { #[test] fn test_deposit_assets_insufficient_balance_erc20() { ExtBuilder::default().build().execute_with(|| { - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); + let delegator_account: AccountId = TestAccount::Alex.into(); Balances::make_free_balance_be(&delegator_account, 500); PrecompilesValue::get() @@ -171,7 +174,7 @@ fn test_deposit_assets_insufficient_balance_erc20() { }, ) .with_subcall_handle(|_subcall| { - // Simulate a faild ERC20 transfer + // Simulate a failed ERC20 transfer let mut out = SubcallOutput::succeed(); out.output = ethabi::encode(&[ethabi::Token::Bool(false)]).to_vec(); out @@ -187,33 +190,33 @@ fn test_deposit_assets_insufficient_balance_erc20() { #[test] fn test_delegate_assets() { ExtBuilder::default().build().execute_with(|| { - let operator_account = sp_core::sr25519::Public::from(TestAccount::Bobo); - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); + let operator_account: AccountId = TestAccount::Bobo.into(); + let delegator_account: AccountId = TestAccount::Alex.into(); Balances::make_free_balance_be(&operator_account, 20_000); Balances::make_free_balance_be(&delegator_account, 500); assert_ok!(MultiAssetDelegation::join_operators( - RuntimeOrigin::signed(operator_account), + RuntimeOrigin::signed(operator_account.clone()), 10_000 )); - create_and_mint_tokens(1, delegator_account, 500); + create_and_mint_tokens(1, delegator_account.clone(), 500); assert_ok!(MultiAssetDelegation::deposit( - RuntimeOrigin::signed(delegator_account), + RuntimeOrigin::signed(delegator_account.clone()), Asset::Custom(1), 200, Some(TestAccount::Alex.into()), None )); - assert_eq!(Assets::balance(1, delegator_account), 500 - 200); // should lose deposit + assert_eq!(Assets::balance(1, &delegator_account), 500 - 200); // should lose deposit PrecompilesValue::get() .prepare_test( TestAccount::Alex, H160::from_low_u64_be(1), PCall::delegate { - operator: operator_account.into(), + operator: H256::from(operator_account.as_ref()), asset_id: U256::from(1), amount: U256::from(100), blueprint_selection: Default::default(), @@ -222,13 +225,13 @@ fn test_delegate_assets() { ) .execute_returns(()); - assert_eq!(Assets::balance(1, delegator_account), 500 - 200); // no change when delegating + assert_eq!(Assets::balance(1, &delegator_account), 500 - 200); // no change when delegating assert!(Operators::::get(operator_account) .unwrap() .delegations .iter() .any(|x| x.delegator == delegator_account - && x.asset_id == Asset::Custom(1) + && x.asset == Asset::Custom(1) && x.amount == 100)); }); } @@ -236,34 +239,34 @@ fn test_delegate_assets() { #[test] fn test_delegate_assets_insufficient_balance() { ExtBuilder::default().build().execute_with(|| { - let operator_account = sp_core::sr25519::Public::from(TestAccount::Bobo); - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Eve); + let operator_account: AccountId = TestAccount::Bobo.into(); + let delegator_account: AccountId = TestAccount::Eve.into(); Balances::make_free_balance_be(&operator_account, 20_000); Balances::make_free_balance_be(&delegator_account, 500); assert_ok!(MultiAssetDelegation::join_operators( - RuntimeOrigin::signed(operator_account), + RuntimeOrigin::signed(operator_account.clone()), 10_000 )); - create_and_mint_tokens(1, delegator_account, 500); + create_and_mint_tokens(1, delegator_account.clone(), 500); - assert_ok!(MultiAssetDelegation::deposit(RuntimeOrigin::signed(delegator_account), Asset::Custom(1), 200, None, None)); + assert_ok!(MultiAssetDelegation::deposit(RuntimeOrigin::signed(delegator_account.clone()), Asset::Custom(1), 200, None, None)); PrecompilesValue::get() .prepare_test( TestAccount::Eve, H160::from_low_u64_be(1), PCall::delegate { - operator: operator_account.into(), + operator: H256::from(operator_account.as_ref()), asset_id: U256::from(1), amount: U256::from(300), blueprint_selection: Default::default(), token_address: Default::default(), }, ) - .execute_reverts(|output| output == b"Dispatched call failed with error: Module(ModuleError { index: 6, error: [14, 0, 0, 0], message: Some(\"InsufficientBalance\") })"); + .execute_reverts(|output| output == b"Dispatched call failed with error: Module(ModuleError { index: 9, error: [15, 0, 0, 0], message: Some(\"InsufficientBalance\") })"); assert_eq!(Balances::free_balance(delegator_account), 500); }); @@ -272,16 +275,16 @@ fn test_delegate_assets_insufficient_balance() { #[test] fn test_unstake_assets_erc20() { ExtBuilder::default().build().execute_with(|| { - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); - let operator_account = sp_core::sr25519::Public::from(TestAccount::Bobo); + let delegator_account: AccountId = TestAccount::Alex.into(); + let operator_account: AccountId = TestAccount::Bobo.into(); Balances::make_free_balance_be(&operator_account, 20_000); assert_ok!(MultiAssetDelegation::join_operators( - RuntimeOrigin::signed(operator_account), + RuntimeOrigin::signed(operator_account.clone()), 10_000 )); - create_and_mint_tokens(1, delegator_account, 500); + create_and_mint_tokens(1, delegator_account.clone(), 500); Balances::make_free_balance_be(&delegator_account, 500); PrecompilesValue::get() @@ -311,14 +314,14 @@ fn test_unstake_assets_erc20() { }) .execute_returns(()); - assert!(Delegators::::get(delegator_account).is_some()); + assert!(Delegators::::get(delegator_account.clone()).is_some()); PrecompilesValue::get() .prepare_test( TestAccount::Alex, H160::from_low_u64_be(1), PCall::delegate { - operator: operator_account.into(), + operator: H256::from(operator_account.as_ref()), asset_id: U256::zero(), amount: U256::from(200), token_address: Address(USDC_ERC20), @@ -327,7 +330,7 @@ fn test_unstake_assets_erc20() { ) .execute_returns(()); - assert!(Delegators::::get(delegator_account).is_some()); + assert!(Delegators::::get(delegator_account.clone()).is_some()); // Unstake @@ -336,7 +339,7 @@ fn test_unstake_assets_erc20() { TestAccount::Alex, H160::from_low_u64_be(1), PCall::schedule_delegator_unstake { - operator: operator_account.into(), + operator: H256::from(operator_account.as_ref()), asset_id: U256::zero(), amount: U256::from(200), token_address: Address(USDC_ERC20), @@ -348,25 +351,25 @@ fn test_unstake_assets_erc20() { assert!(d .delegator_unstake_requests .iter() - .any(|x| x.amount == 200 && x.asset_id == Asset::Erc20(USDC_ERC20))); + .any(|x| x.amount == 200 && x.asset == Asset::Erc20(USDC_ERC20))); }); } #[test] fn test_schedule_withdraw() { ExtBuilder::default().build().execute_with(|| { - let operator_account = sp_core::sr25519::Public::from(TestAccount::Bobo); - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); + let operator_account: AccountId = TestAccount::Bobo.into(); + let delegator_account: AccountId = TestAccount::Alex.into(); Balances::make_free_balance_be(&operator_account, 20_000); Balances::make_free_balance_be(&delegator_account, 500); assert_ok!(MultiAssetDelegation::join_operators( - RuntimeOrigin::signed(operator_account), + RuntimeOrigin::signed(operator_account.clone()), 10_000 )); - create_and_mint_tokens(1, delegator_account, 500); + create_and_mint_tokens(1, delegator_account.clone(), 500); PrecompilesValue::get() .prepare_test( @@ -381,14 +384,14 @@ fn test_schedule_withdraw() { ) .execute_returns(()); - assert_eq!(Assets::balance(1, delegator_account), 500 - 200); // should lose deposit + assert_eq!(Assets::balance(1, delegator_account.clone()), 500 - 200); // should lose deposit PrecompilesValue::get() .prepare_test( TestAccount::Alex, H160::from_low_u64_be(1), PCall::delegate { - operator: operator_account.into(), + operator: H256::from(operator_account.as_ref()), asset_id: U256::from(1), amount: U256::from(100), blueprint_selection: Default::default(), @@ -397,7 +400,7 @@ fn test_schedule_withdraw() { ) .execute_returns(()); - assert!(Delegators::::get(delegator_account).is_some()); + assert!(Delegators::::get(delegator_account.clone()).is_some()); PrecompilesValue::get() .prepare_test( @@ -418,16 +421,16 @@ fn test_schedule_withdraw() { #[test] fn test_execute_withdraw() { ExtBuilder::default().build().execute_with(|| { - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); - let operator_account = sp_core::sr25519::Public::from(TestAccount::Bobo); + let delegator_account: AccountId = TestAccount::Alex.into(); + let operator_account: AccountId = TestAccount::Bobo.into(); Balances::make_free_balance_be(&operator_account, 20_000); assert_ok!(MultiAssetDelegation::join_operators( - RuntimeOrigin::signed(operator_account), + RuntimeOrigin::signed(operator_account.clone()), 10_000 )); - create_and_mint_tokens(1, delegator_account, 500); + create_and_mint_tokens(1, delegator_account.clone(), 500); PrecompilesValue::get() .prepare_test( @@ -441,14 +444,14 @@ fn test_execute_withdraw() { }, ) .execute_returns(()); - assert_eq!(Assets::balance(1, delegator_account), 500 - 200); // should lose deposit + assert_eq!(Assets::balance(1, delegator_account.clone()), 500 - 200); // should lose deposit PrecompilesValue::get() .prepare_test( TestAccount::Alex, H160::from_low_u64_be(1), PCall::delegate { - operator: operator_account.into(), + operator: H256::from(operator_account.as_ref()), asset_id: U256::from(1), amount: U256::from(100), blueprint_selection: Default::default(), @@ -457,7 +460,7 @@ fn test_execute_withdraw() { ) .execute_returns(()); - assert!(Delegators::::get(delegator_account).is_some()); + assert!(Delegators::::get(delegator_account.clone()).is_some()); PrecompilesValue::get() .prepare_test( @@ -471,7 +474,7 @@ fn test_execute_withdraw() { ) .execute_returns(()); - let metadata = MultiAssetDelegation::delegators(delegator_account).unwrap(); + let metadata = MultiAssetDelegation::delegators(&delegator_account).unwrap(); assert!(!metadata.withdraw_requests.is_empty()); >::put(5); @@ -487,17 +490,17 @@ fn test_execute_withdraw() { #[test] fn test_execute_withdraw_before_due() { ExtBuilder::default().build().execute_with(|| { - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); - let operator_account = sp_core::sr25519::Public::from(TestAccount::Bobo); + let delegator_account: AccountId = TestAccount::Alex.into(); + let operator_account: AccountId = TestAccount::Bobo.into(); Balances::make_free_balance_be(&delegator_account, 10_000); Balances::make_free_balance_be(&operator_account, 20_000); assert_ok!(MultiAssetDelegation::join_operators( - RuntimeOrigin::signed(operator_account), + RuntimeOrigin::signed(operator_account.clone()), 10_000 )); - create_and_mint_tokens(1, delegator_account, 500); + create_and_mint_tokens(1, delegator_account.clone(), 500); PrecompilesValue::get() .prepare_test( @@ -511,14 +514,14 @@ fn test_execute_withdraw_before_due() { }, ) .execute_returns(()); - assert_eq!(Assets::balance(1, delegator_account), 500 - 200); // should lose deposit + assert_eq!(Assets::balance(1, delegator_account.clone()), 500 - 200); // should lose deposit PrecompilesValue::get() .prepare_test( TestAccount::Alex, H160::from_low_u64_be(1), PCall::delegate { - operator: operator_account.into(), + operator: H256::from(operator_account.as_ref()), asset_id: U256::from(1), amount: U256::from(100), blueprint_selection: Default::default(), @@ -527,8 +530,8 @@ fn test_execute_withdraw_before_due() { ) .execute_returns(()); - assert!(Delegators::::get(delegator_account).is_some()); - assert_eq!(Assets::balance(1, delegator_account), 500 - 200); // delegate should not change balance + assert!(Delegators::::get(delegator_account.clone()).is_some()); + assert_eq!(Assets::balance(1, delegator_account.clone()), 500 - 200); // delegate should not change balance PrecompilesValue::get() .prepare_test( @@ -554,16 +557,16 @@ fn test_execute_withdraw_before_due() { #[test] fn test_cancel_withdraw() { ExtBuilder::default().build().execute_with(|| { - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); - let operator_account = sp_core::sr25519::Public::from(TestAccount::Bobo); + let delegator_account: AccountId = TestAccount::Alex.into(); + let operator_account: AccountId = TestAccount::Bobo.into(); Balances::make_free_balance_be(&operator_account, 20_000); assert_ok!(MultiAssetDelegation::join_operators( - RuntimeOrigin::signed(operator_account), + RuntimeOrigin::signed(operator_account.clone()), 10_000 )); - create_and_mint_tokens(1, delegator_account, 500); + create_and_mint_tokens(1, delegator_account.clone(), 500); PrecompilesValue::get() .prepare_test( @@ -577,14 +580,14 @@ fn test_cancel_withdraw() { }, ) .execute_returns(()); - assert_eq!(Assets::balance(1, delegator_account), 500 - 200); // should lose deposit + assert_eq!(Assets::balance(1, delegator_account.clone()), 500 - 200); // should lose deposit PrecompilesValue::get() .prepare_test( TestAccount::Alex, H160::from_low_u64_be(1), PCall::delegate { - operator: operator_account.into(), + operator: H256::from(operator_account.as_ref()), asset_id: U256::from(1), amount: U256::from(100), blueprint_selection: Default::default(), @@ -593,7 +596,7 @@ fn test_cancel_withdraw() { ) .execute_returns(()); - assert!(Delegators::::get(delegator_account).is_some()); + assert!(Delegators::::get(&delegator_account).is_some()); PrecompilesValue::get() .prepare_test( @@ -619,7 +622,7 @@ fn test_cancel_withdraw() { ) .execute_returns(()); - let metadata = MultiAssetDelegation::delegators(delegator_account).unwrap(); + let metadata = MultiAssetDelegation::delegators(&delegator_account).unwrap(); assert!(metadata.deposits.contains_key(&Asset::Custom(1))); assert!(metadata.withdraw_requests.is_empty()); @@ -630,16 +633,16 @@ fn test_cancel_withdraw() { #[test] fn balance_of_works() { ExtBuilder::default().build().execute_with(|| { - let delegator_account = sp_core::sr25519::Public::from(TestAccount::Alex); - let operator_account = sp_core::sr25519::Public::from(TestAccount::Bobo); + let delegator_account: AccountId = TestAccount::Alex.into(); + let operator_account: AccountId = TestAccount::Bobo.into(); Balances::make_free_balance_be(&operator_account, 20_000); assert_ok!(MultiAssetDelegation::join_operators( - RuntimeOrigin::signed(operator_account), + RuntimeOrigin::signed(operator_account.clone()), 10_000 )); - create_and_mint_tokens(1, delegator_account, 500); + create_and_mint_tokens(1, delegator_account.clone(), 500); Balances::make_free_balance_be(&delegator_account, 500); // Not a delegator yet. @@ -694,7 +697,7 @@ fn balance_of_works() { }) .execute_returns(()); - assert!(Delegators::::get(delegator_account).is_some()); + assert!(Delegators::::get(delegator_account.clone()).is_some()); // Deposit successful, now check balance @@ -728,7 +731,7 @@ fn balance_of_works() { TestAccount::Alex, H160::from_low_u64_be(1), PCall::delegate { - operator: operator_account.into(), + operator: H256::from(operator_account.as_ref()), asset_id: U256::zero(), amount: U256::from(100), token_address: Address(USDC_ERC20), @@ -737,7 +740,7 @@ fn balance_of_works() { ) .execute_returns(()); - assert!(Delegators::::get(delegator_account).is_some()); + assert!(Delegators::::get(delegator_account.clone()).is_some()); // Delegated balance should now be 100 // Deposit balance should be the same as before. PrecompilesValue::get() @@ -770,7 +773,7 @@ fn balance_of_works() { TestAccount::Alex, H160::from_low_u64_be(1), PCall::schedule_delegator_unstake { - operator: operator_account.into(), + operator: H256::from(operator_account.as_ref()), asset_id: U256::zero(), amount: U256::from(50), token_address: Address(USDC_ERC20), @@ -778,11 +781,11 @@ fn balance_of_works() { ) .execute_returns(()); - let d = Delegators::::get(delegator_account).unwrap(); + let d = Delegators::::get(delegator_account.clone()).unwrap(); assert!(d .delegator_unstake_requests .iter() - .any(|x| x.amount == 50 && x.asset_id == Asset::Erc20(USDC_ERC20))); + .any(|x| x.amount == 50 && x.asset == Asset::Erc20(USDC_ERC20))); // Now check balance again diff --git a/precompiles/rewards/src/mock.rs b/precompiles/rewards/src/mock.rs index f505db12c..d72ecd7ad 100644 --- a/precompiles/rewards/src/mock.rs +++ b/precompiles/rewards/src/mock.rs @@ -294,15 +294,19 @@ parameter_types! { pub const MinOperatorBondAmount: u64 = 10_000; pub const BondDuration: u32 = 10; pub PID: PalletId = PalletId(*b"PotStake"); - pub SlashedAmountRecipient : AccountId = TestAccount::Alex.into(); + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxDelegatorBlueprints : u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxOperatorBlueprints : u32 = 50; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxWithdrawRequests: u32 = 5; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxUnstakeRequests: u32 = 5; + #[derive(PartialEq, Eq, Clone, Copy, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub const MaxDelegations: u32 = 50; } @@ -330,7 +334,6 @@ impl pallet_multi_asset_delegation::Config for Runtime { type MaxWithdrawRequests = MaxWithdrawRequests; type MaxUnstakeRequests = MaxUnstakeRequests; type MaxDelegations = MaxDelegations; - type SlashedAmountRecipient = SlashedAmountRecipient; type PalletId = PID; type WeightInfo = (); } diff --git a/precompiles/services/Cargo.toml b/precompiles/services/Cargo.toml index 835643041..d7241d36a 100644 --- a/precompiles/services/Cargo.toml +++ b/precompiles/services/Cargo.toml @@ -74,6 +74,8 @@ pallet-evm-precompile-ed25519 = { workspace = true } pallet-evm-precompile-modexp = { workspace = true } pallet-evm-precompile-sha3fips = { workspace = true } pallet-evm-precompile-simple = { workspace = true } +pallet-evm-precompile-balances-erc20 = { workspace = true } +pallet-evm-precompileset-assets-erc20 = { workspace = true } pallet-session = { workspace = true } pallet-staking = { workspace = true } diff --git a/precompiles/services/Services.sol b/precompiles/services/Services.sol index 8950e545f..d0d257916 100644 --- a/precompiles/services/Services.sol +++ b/precompiles/services/Services.sol @@ -15,85 +15,61 @@ Services constant SERVICES_CONTRACT = Services(SERVICES_ADDRESS); interface Services { /// @dev Create a new blueprint. /// @param blueprint_data The blueprint data in SCALE-encoded format. + /// @custom:selector c45a6865 function createBlueprint(bytes calldata blueprint_data) external; - /// @dev Register as an operator for a specific blueprint. - /// @param blueprint_id The blueprint ID. - /// @param preferences The operator preferences in SCALE-encoded format. - /// @param registration_args The registration arguments in SCALE-encoded format. - function registerOperator(uint256 blueprint_id, bytes calldata preferences, bytes calldata registration_args) external payable; - - /// @dev Pre-register as an operator for a specific blueprint. - /// @param blueprint_id The blueprint ID. - function preRegister(uint256 blueprint_id) external; - - /// @dev Unregister as an operator from a blueprint. - /// @param blueprint_id The blueprint ID. - function unregisterOperator(uint256 blueprint_id) external; - - /// @dev Request a new service. - /// @param blueprint_id The blueprint ID. - /// @param assets The list of asset IDs. - /// @param permitted_callers The permitted callers in SCALE-encoded format. - /// @param service_providers The service providers in SCALE-encoded format. - /// @param request_args The request arguments in SCALE-encoded format. - /// @param ttl The time-to-live for the request. - /// @param payment_asset_id The payment asset ID. - /// @param payment_token_address The payment token address. - /// @param amount The payment amount. + /// @notice Request a service from a specific blueprint + /// @param blueprint_id The ID of the blueprint + /// @param security_requirements The security requirements for each asset + /// @param permitted_callers_data The permitted callers for the service encoded as bytes + /// @param service_providers_data The service providers encoded as bytes + /// @param request_args_data The request arguments encoded as bytes + /// @param ttl The time-to-live of the service. + /// @param payment_asset_id The ID of the asset to use for payment (0 for native asset) + /// @param payment_token_address The address of the token to use for payment (0x0 for using the value of payment_asset_id) + /// @param payment_amount The amount to pay for the service (use msg.value if payment_asset_id is 0) + /// @param min_operators The minimum number of operators required + /// @param max_operators The maximum number of operators allowed + /// @custom:selector 7f3c0ee4 function requestService( uint256 blueprint_id, - uint256[] calldata assets, - bytes calldata permitted_callers, - bytes calldata service_providers, - bytes calldata request_args, + bytes[] calldata security_requirements, + bytes calldata permitted_callers_data, + bytes calldata service_providers_data, + bytes calldata request_args_data, uint256 ttl, uint256 payment_asset_id, address payment_token_address, - uint256 amount + uint256 payment_amount, + uint32 min_operators, + uint32 max_operators ) external payable; /// @dev Terminate a service. /// @param service_id The service ID. + /// @custom:selector b997a71e function terminateService(uint256 service_id) external; - /// @dev Approve a request. - /// @param request_id The request ID. - /// @param restaking_percent The restaking percentage. - function approve(uint256 request_id, uint8 restaking_percent) external; - - /// @dev Reject a service request. - /// @param request_id The request ID. - function reject(uint256 request_id) external; - /// @dev Call a job in the service. /// @param service_id The service ID. /// @param job The job ID. /// @param args_data The job arguments in SCALE-encoded format. + /// @custom:selector fce65e13 function callJob(uint256 service_id, uint8 job, bytes calldata args_data) external; - /// @dev Submit the result for a job call. - /// @param service_id The service ID. - /// @param call_id The call ID. - /// @param result_data The result data in SCALE-encoded format. - function submitResult(uint256 service_id, uint256 call_id, bytes calldata result_data) external; - /// @dev Slash an operator for a service. /// @param offender The offender in SCALE-encoded format. /// @param service_id The service ID. - /// @param percent The slash percentage. + /// @param percent The slash percentage (0-100). + /// @custom:selector 64a798ac function slash(bytes calldata offender, uint256 service_id, uint8 percent) external; /// @dev Dispute an unapplied slash. /// @param era The era number. /// @param index The index of the slash. + /// @custom:selector fac9efa3 function dispute(uint32 era, uint32 index) external; - /// @dev Update price targets for a blueprint. - /// @param blueprint_id The blueprint ID. - /// @param price_targets The new price targets. - function updatePriceTargets(uint256 blueprint_id, uint256[] calldata price_targets) external; - /// @dev Custom errors for the Services precompile error InvalidPermittedCallers(); error InvalidOperatorsList(); diff --git a/precompiles/services/src/lib.rs b/precompiles/services/src/lib.rs index f18b77a80..635e28def 100644 --- a/precompiles/services/src/lib.rs +++ b/precompiles/services/src/lib.rs @@ -12,7 +12,7 @@ use sp_core::U256; use sp_runtime::{traits::Dispatchable, Percent}; use sp_std::{marker::PhantomData, vec::Vec}; use tangle_primitives::services::{ - Asset, Field, OperatorPreferences, PriceTargets, ServiceBlueprint, + Asset, AssetSecurityRequirement, Field, MembershipModel, ServiceBlueprint, }; #[cfg(test)] @@ -74,76 +74,15 @@ where Ok(()) } - /// Register as an operator for a specific blueprint. - #[precompile::public("registerOperator(uint256,bytes,bytes)")] - #[precompile::payable] - fn register_operator( - handle: &mut impl PrecompileHandle, - blueprint_id: U256, - preferences: UnboundedBytes, - registration_args: UnboundedBytes, - ) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - // msg.value - let value = handle.context().apparent_value; - - let blueprint_id: u64 = blueprint_id.as_u64(); - let preferences: Vec = preferences.into(); - let registration_args: Vec = registration_args.into(); - let preferences: OperatorPreferences = Decode::decode(&mut &preferences[..]) - .map_err(|_| revert("Invalid preferences data"))?; - - let registration_args: Vec> = - if registration_args.is_empty() { - Vec::new() - } else { - Decode::decode(&mut ®istration_args[..]) - .map_err(|_| revert("Invalid registration arguments"))? - }; - let value_bytes = { - let mut value_bytes = [0u8; core::mem::size_of::()]; - value.to_little_endian(&mut value_bytes); - value_bytes - }; - let value = BalanceOf::::decode(&mut &value_bytes[..]) - .map_err(|_| revert("Value is not a valid balance"))?; - let call = pallet_services::Call::::register { - blueprint_id, - preferences, - registration_args, - value, - }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - /// Unregister as an operator from a blueprint. - #[precompile::public("unregisterOperator(uint256)")] - fn unregister_operator(handle: &mut impl PrecompileHandle, blueprint_id: U256) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let blueprint_id: u64 = blueprint_id.as_u64(); - - let call = pallet_services::Call::::unregister { blueprint_id }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - /// Request a new service. #[precompile::public( - "requestService(uint256,uint256[],bytes,bytes,bytes,uint256,uint256,address,uint256)" + "requestService(uint256,bytes[],bytes,bytes,bytes,uint256,uint256,address,uint256,uint32,uint32)" )] #[precompile::payable] fn request_service( handle: &mut impl PrecompileHandle, blueprint_id: U256, - assets: Vec, + asset_security_requirements: Vec, permitted_callers_data: UnboundedBytes, service_providers_data: UnboundedBytes, request_args_data: UnboundedBytes, @@ -151,12 +90,16 @@ where payment_asset_id: U256, payment_token_address: Address, amount: U256, + min_operators: u32, + max_operators: u32, ) -> EvmResult { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; let msg_sender = handle.context().caller; let origin = Runtime::AddressMapping::into_account_id(msg_sender); let blueprint_id: u64 = blueprint_id.as_u64(); + let asset_security_requirements_data: Vec> = + asset_security_requirements.into_iter().map(|x| x.into()).collect(); let permitted_callers_data: Vec = permitted_callers_data.into(); let service_providers_data: Vec = service_providers_data.into(); let request_args_data: Vec = request_args_data.into(); @@ -173,8 +116,12 @@ where Decode::decode(&mut &request_args_data[..]) .map_err(|_| revert_custom_error(Self::INVALID_REQUEST_ARGUMENTS))?; - let assets: Vec = - assets.into_iter().map(|asset| asset.as_u32().into()).collect(); + let asset_security_requirements: Vec> = + asset_security_requirements_data + .into_iter() + .map(|req| Decode::decode(&mut &req[..])) + .collect::>() + .map_err(|_| revert_custom_error(Self::INVALID_REQUEST_ARGUMENTS))?; let value_bytes = { let value = handle.context().apparent_value; @@ -223,16 +170,25 @@ where }, }; + let membership_model = if max_operators == 0 { + MembershipModel::Fixed { min_operators } + } else if max_operators == u32::MAX { + MembershipModel::Dynamic { min_operators, max_operators: None } + } else { + MembershipModel::Dynamic { min_operators, max_operators: Some(max_operators) } + }; + let call = pallet_services::Call::::request { evm_origin: Some(msg_sender), blueprint_id, permitted_callers, operators, ttl, - assets, + asset_security_requirements, request_args, payment_asset, value: amount, + membership_model, }; RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; @@ -255,39 +211,6 @@ where Ok(()) } - /// Approve a request. - #[precompile::public("approve(uint256,uint8)")] - fn approve( - handle: &mut impl PrecompileHandle, - request_id: U256, - restaking_percent: u8, - ) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - let request_id: u64 = request_id.as_u64(); - let restaking_percent: Percent = Percent::from_percent(restaking_percent); - - let call = pallet_services::Call::::approve { request_id, restaking_percent }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - /// Reject a service request. - #[precompile::public("reject(uint256)")] - fn reject(handle: &mut impl PrecompileHandle, request_id: U256) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - let request_id: u64 = request_id.as_u64(); - - let call = pallet_services::Call::::reject { request_id }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - /// Call a job in the service. #[precompile::public("callJob(uint256,uint8,bytes)")] fn call_job( @@ -312,34 +235,6 @@ where Ok(()) } - /// Submit the result for a job call. - #[precompile::public("submitResult(uint256,uint256,bytes)")] - fn submit_result( - handle: &mut impl PrecompileHandle, - service_id: U256, - call_id: U256, - result_data: UnboundedBytes, - ) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - let service_id: u64 = service_id.as_u64(); - let call_id: u64 = call_id.as_u64(); - let result: Vec = result_data.into(); - - let decoded_result: Vec> = - Decode::decode(&mut &result[..]).map_err(|_| revert("Invalid job result data"))?; - - let call = pallet_services::Call::::submit_result { - service_id, - call_id, - result: decoded_result, - }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - /// Slash an operator (offender) for a service id with a given percent of their exposed stake /// for that service. /// @@ -362,8 +257,12 @@ where let offender: Runtime::AccountId = Decode::decode(&mut &offender_bytes[..]) .map_err(|_| revert("Invalid offender account id"))?; - // inside this call, we do check if the caller is authorized to slash the offender - let call = pallet_services::Call::::slash { offender, service_id, percent }; + let call = pallet_services::Call::::slash { + offender, + + service_id, + slash_percent: percent, + }; RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; Ok(()) @@ -384,52 +283,6 @@ where Ok(()) } - - /// Update price targets for a blueprint. - #[precompile::public("updatePriceTargets(uint256,uint256[])")] - fn update_price_targets( - handle: &mut impl PrecompileHandle, - blueprint_id: U256, - price_targets: Vec, - ) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let blueprint_id: u64 = blueprint_id.as_u64(); - - // Convert price targets into the correct struct - let price_targets = { - let mut targets = price_targets.into_iter(); - PriceTargets { - cpu: targets.next().map_or(0, |v| v.as_u64()), - mem: targets.next().map_or(0, |v| v.as_u64()), - storage_hdd: targets.next().map_or(0, |v| v.as_u64()), - storage_ssd: targets.next().map_or(0, |v| v.as_u64()), - storage_nvme: targets.next().map_or(0, |v| v.as_u64()), - } - }; - - let call = - pallet_services::Call::::update_price_targets { blueprint_id, price_targets }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } - - /// Pre-register as an operator for a specific blueprint. - #[precompile::public("preRegister(uint256)")] - fn pre_register(handle: &mut impl PrecompileHandle, blueprint_id: U256) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let origin = Runtime::AddressMapping::into_account_id(handle.context().caller); - - let blueprint_id: u64 = blueprint_id.as_u64(); - let call = pallet_services::Call::::pre_register { blueprint_id }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(()) - } } /// Revert with Custom Error Selector diff --git a/precompiles/services/src/mock.rs b/precompiles/services/src/mock.rs index 9fcdb89a9..2695cbf0e 100644 --- a/precompiles/services/src/mock.rs +++ b/precompiles/services/src/mock.rs @@ -26,6 +26,7 @@ use frame_support::{ pallet_prelude::{Hooks, Weight}, parameter_types, traits::{AsEnsureOriginWithArg, ConstU128, OneSessionHandler}, + PalletId, }; use frame_system::EnsureRoot; use mock_evm::MockedEvmRunner; @@ -215,7 +216,7 @@ impl pallet_staking::Config for Runtime { } parameter_types! { - pub const ServicesEVMAddress: H160 = H160([0x11; 20]); + pub const ServicesPalletId: PalletId = PalletId(*b"Services"); } pub struct PalletEVMGasWeightMapping; @@ -251,7 +252,7 @@ impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = u128; type AssetId = AssetId; - type AssetIdParameter = u32; + type AssetIdParameter = u128; type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; @@ -389,14 +390,12 @@ impl From for sp_core::sr25519::Public { } } -pub type AssetId = u32; +pub type AssetId = u128; pub struct MockDelegationManager; -impl tangle_primitives::traits::MultiAssetDelegationInfo +impl tangle_primitives::traits::MultiAssetDelegationInfo for MockDelegationManager { - type AssetId = AssetId; - fn get_current_round() -> tangle_primitives::types::RoundIndex { Default::default() } @@ -417,29 +416,19 @@ impl tangle_primitives::traits::MultiAssetDelegationInfo, - ) -> Balance { + fn get_total_delegation_by_asset(_operator: &AccountId, _asset_id: &Asset) -> Balance { Default::default() } fn get_delegators_for_operator( _operator: &AccountId, - ) -> Vec<(AccountId, Balance, Asset)> { + ) -> Vec<(AccountId, Balance, Asset)> { Default::default() } - fn slash_operator( - _operator: &AccountId, - _blueprint_id: tangle_primitives::BlueprintId, - _percentage: sp_runtime::Percent, - ) { - } - fn get_user_deposit_with_locks( _who: &AccountId, - _asset_id: Asset, + _asset: Asset, ) -> Option> { None } @@ -533,6 +522,17 @@ parameter_types! { #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub const SlashDeferDuration: u32 = 7; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + pub const MinimumNativeSecurityRequirement: Percent = Percent::from_percent(10); + + // Ripemd160(keccak256("ServicesPalletEvmAccount")) + pub const ServicesPalletEvmAccount: H160 = H160([ + 0x09, 0xdf, 0x6a, 0x94, 0x1e, 0xe0, 0x3b, 0x1e, + 0x63, 0x29, 0x04, 0xe3, 0x82, 0xe1, 0x08, 0x62, + 0xfa, 0x9c, 0xc0, 0xe3 + ]); } impl pallet_services::Config for Runtime { @@ -541,7 +541,8 @@ impl pallet_services::Config for Runtime { type Currency = Balances; type Fungibles = Assets; type AssetId = AssetId; - type PalletEVMAddress = ServicesEVMAddress; + type PalletEvmAccount = ServicesPalletEvmAccount; + type SlashManager = (); type EvmRunner = MockedEvmRunner; type EvmAddressMapping = PalletEVMAddressMapping; type EvmGasWeightMapping = PalletEVMGasWeightMapping; @@ -566,6 +567,7 @@ impl pallet_services::Config for Runtime { type MaxContainerImageTagLength = MaxContainerImageTagLength; type MaxAssetsPerService = MaxAssetsPerService; type MaxMasterBlueprintServiceManagerVersions = MaxMasterBlueprintServiceManagerRevisions; + type MinimumNativeSecurityRequirement = MinimumNativeSecurityRequirement; type Constraints = pallet_services::types::ConstraintsOf; type OperatorDelegationManager = MockDelegationManager; type SlashDeferDuration = SlashDeferDuration; @@ -603,6 +605,7 @@ pub const MBSM: H160 = H160([0x12; 20]); pub const CGGMP21_BLUEPRINT: H160 = H160([0x21; 20]); pub const USDC_ERC20: H160 = H160([0x23; 20]); +#[allow(dead_code)] pub const TNT: AssetId = 0; pub const USDC: AssetId = 1; pub const WETH: AssetId = 2; @@ -747,7 +750,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::TestE >::on_initialize(1); let call = ::EvmRunner::call( - Services::address(), + Services::pallet_evm_account(), USDC_ERC20, serde_json::from_value::(json!({ "name": "initialize", @@ -788,7 +791,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::TestE // Mint for a in authorities { let call = ::EvmRunner::call( - Services::address(), + Services::pallet_evm_account(), USDC_ERC20, serde_json::from_value::(json!({ "name": "mint", diff --git a/precompiles/services/src/mock_evm.rs b/precompiles/services/src/mock_evm.rs index 9d0878282..75995bc2a 100644 --- a/precompiles/services/src/mock_evm.rs +++ b/precompiles/services/src/mock_evm.rs @@ -15,7 +15,9 @@ // along with Tangle. If not, see . #![allow(clippy::all)] use crate::{ - mock::{AccountId, Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Timestamp}, + mock::{ + AccountId, AssetId, Balances, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Timestamp, + }, ServicesPrecompile, ServicesPrecompileCall, }; use fp_evm::FeeCalculator; @@ -27,7 +29,10 @@ use frame_support::{ }; use pallet_ethereum::{EthereumBlockHashMapping, IntermediateStateRoot, PostLogContent, RawOrigin}; use pallet_evm::{EnsureAddressNever, EnsureAddressOrigin, OnChargeEVMTransaction}; -use precompile_utils::precompile_set::{AddressU64, PrecompileAt, PrecompileSetBuilder}; +use precompile_utils::precompile_set::{ + AddressU64, CallableByContract, CallableByPrecompile, PrecompileAt, PrecompileSetBuilder, + PrecompileSetStartingWith, +}; use sp_core::{keccak_256, ConstU32, H160, H256, U256}; use sp_runtime::{ traits::{DispatchInfoOf, Dispatchable}, @@ -36,8 +41,60 @@ use sp_runtime::{ }; use tangle_primitives::services::EvmRunner; -pub type Precompiles = - PrecompileSetBuilder, ServicesPrecompile>,)>; +use pallet_evm_precompile_balances_erc20::{Erc20BalancesPrecompile, Erc20Metadata}; +use pallet_evm_precompileset_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; + +pub struct NativeErc20Metadata; + +/// ERC20 metadata for the native token. +impl Erc20Metadata for NativeErc20Metadata { + /// Returns the name of the token. + fn name() -> &'static str { + "Tangle Testnet Network Token" + } + + /// Returns the symbol of the token. + fn symbol() -> &'static str { + "tTNT" + } + + /// Returns the decimals places of the token. + fn decimals() -> u8 { + 18 + } + + /// Must return `true` only if it represents the main native currency of + /// the network. It must be the currency used in `pallet_evm`. + fn is_native_currency() -> bool { + true + } +} + +/// The asset precompile address prefix. Addresses that match against this prefix will be routed +/// to Erc20AssetsPrecompileSet being marked as foreign +pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; + +parameter_types! { + pub ForeignAssetPrefix: &'static [u8] = ASSET_PRECOMPILE_ADDRESS_PREFIX; +} + +pub type Precompiles = PrecompileSetBuilder< + R, + ( + PrecompileAt, ServicesPrecompile>, + PrecompileAt< + AddressU64<2050>, + Erc20BalancesPrecompile, + (CallableByContract, CallableByPrecompile), + >, + // Prefixed precompile sets (XC20) + PrecompileSetStartingWith< + ForeignAssetPrefix, + Erc20AssetsPrecompileSet, + CallableByContract, + >, + ), +>; pub type PCall = ServicesPrecompileCall; @@ -52,6 +109,28 @@ impl pallet_timestamp::Config for Runtime { type WeightInfo = (); } +const ASSET_ID_SIZE: usize = core::mem::size_of::(); + +impl AddressToAssetId for Runtime { + fn address_to_asset_id(address: H160) -> Option { + let mut data = [0u8; ASSET_ID_SIZE]; + let address_bytes: [u8; 20] = address.into(); + if ASSET_PRECOMPILE_ADDRESS_PREFIX.eq(&address_bytes[0..4]) { + data.copy_from_slice(&address_bytes[4..ASSET_ID_SIZE + 4]); + Some(AssetId::from_be_bytes(data)) + } else { + None + } + } + + fn asset_id_to_address(asset_id: AssetId) -> H160 { + let mut data = [0u8; 20]; + data[0..4].copy_from_slice(ASSET_PRECOMPILE_ADDRESS_PREFIX); + data[4..ASSET_ID_SIZE + 4].copy_from_slice(&asset_id.to_be_bytes()); + H160::from(data) + } +} + pub struct FixedGasPrice; impl FeeCalculator for FixedGasPrice { fn min_gas_price() -> (U256, Weight) { @@ -149,7 +228,7 @@ impl OnChargeEVMTransaction for CustomEVMCurrencyAdapter { who: &H160, fee: U256, ) -> Result> { - let pallet_services_address = pallet_services::Pallet::::address(); + let pallet_services_address = pallet_services::Pallet::::pallet_evm_account(); // Make pallet services account free to use if who == &pallet_services_address { return Ok(None); @@ -166,7 +245,7 @@ impl OnChargeEVMTransaction for CustomEVMCurrencyAdapter { base_fee: U256, already_withdrawn: Self::LiquidityInfo, ) -> Self::LiquidityInfo { - let pallet_services_address = pallet_services::Pallet::::address(); + let pallet_services_address = pallet_services::Pallet::::pallet_evm_account(); // Make pallet services account free to use if who == &pallet_services_address { return already_withdrawn; diff --git a/precompiles/services/src/tests.rs b/precompiles/services/src/tests.rs index 1eecb814f..6fe86af58 100644 --- a/precompiles/services/src/tests.rs +++ b/precompiles/services/src/tests.rs @@ -6,17 +6,29 @@ use crate::{ }; use frame_support::assert_ok; use k256::ecdsa::{SigningKey, VerifyingKey}; -use pallet_services::{types::ConstraintsOf, Instances, Operators, OperatorsProfile}; +use pallet_services::{types::ConstraintsOf, Instances}; use parity_scale_codec::Encode; use precompile_utils::{prelude::UnboundedBytes, testing::*}; use sp_core::{ecdsa, Pair, H160, U256}; -use sp_runtime::{bounded_vec, AccountId32}; +use sp_runtime::{bounded_vec, AccountId32, Percent}; use tangle_primitives::services::{ - BlueprintServiceManager, FieldType, JobDefinition, JobMetadata, - MasterBlueprintServiceManagerRevision, OperatorPreferences, PriceTargets, ServiceBlueprint, - ServiceMetadata, + Asset, AssetSecurityCommitment, AssetSecurityRequirement, BlueprintServiceManager, FieldType, + JobDefinition, JobMetadata, MasterBlueprintServiceManagerRevision, MembershipModelType, + OperatorPreferences, PriceTargets, ServiceBlueprint, ServiceMetadata, }; +fn get_security_requirement(a: AssetId, p: &[u8; 2]) -> AssetSecurityRequirement { + AssetSecurityRequirement { + asset: Asset::Custom(a), + min_exposure_percent: Percent::from_percent(p[0]), + max_exposure_percent: Percent::from_percent(p[1]), + } +} + +fn get_security_commitment(a: AssetId, p: u8) -> AssetSecurityCommitment { + AssetSecurityCommitment { asset: Asset::Custom(a), exposure_percent: Percent::from_percent(p) } +} + fn test_ecdsa_key() -> [u8; 65] { let (ecdsa_key, _) = ecdsa::Pair::generate(); let secret = SigningKey::from_slice(&ecdsa_key.seed()) @@ -67,19 +79,32 @@ fn cggmp21_blueprint() -> ServiceBlueprint> { JobDefinition { metadata: JobMetadata { name: "keygen".try_into().unwrap(), ..Default::default() }, params: bounded_vec![FieldType::Uint8], - result: bounded_vec![FieldType::Bytes], + result: bounded_vec![FieldType::List(Box::new(FieldType::Uint8))], }, JobDefinition { metadata: JobMetadata { name: "sign".try_into().unwrap(), ..Default::default() }, - params: bounded_vec![FieldType::Uint64, FieldType::Bytes], - result: bounded_vec![FieldType::Bytes], + params: bounded_vec![ + FieldType::Uint64, + FieldType::List(Box::new(FieldType::Uint8)) + ], + result: bounded_vec![FieldType::List(Box::new(FieldType::Uint8))], }, ], registration_params: bounded_vec![], request_params: bounded_vec![], gadget: Default::default(), + supported_membership_models: bounded_vec![ + MembershipModelType::Fixed, + MembershipModelType::Dynamic, + ], } } + +#[test] +fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() { + check_precompile_implements_solidity_interfaces(&["Services.sol"], PCall::supports_selector) +} + #[test] fn test_create_blueprint() { ExtBuilder.build().execute_with(|| { @@ -102,53 +127,10 @@ fn test_create_blueprint() { }); } -#[test] -fn test_register_operator() { - ExtBuilder.build().execute_with(|| { - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - // First create the blueprint - let blueprint_data = cggmp21_blueprint(); - - PrecompilesValue::get() - .prepare_test( - TestAccount::Alex, - H160::from_low_u64_be(1), - PCall::create_blueprint { - blueprint_data: UnboundedBytes::from(blueprint_data.encode()), - }, - ) - .execute_returns(()); - - // Now register operator - let preferences_data = OperatorPreferences { - key: test_ecdsa_key(), - price_targets: price_targets(MachineKind::Large), - } - .encode(); - - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::register_operator { - blueprint_id: U256::from(0), // We use the first blueprint - preferences: UnboundedBytes::from(preferences_data), - registration_args: UnboundedBytes::from(Vec::new()), - }, - ) - .execute_returns(()); - - // Check that the operator profile exists - let account: AccountId32 = TestAccount::Bob.into(); - assert!(OperatorsProfile::::get(account).is_ok()); - }); -} - #[test] fn test_request_service() { ExtBuilder.build().execute_with(|| { assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - // First create the blueprint let blueprint_data = cggmp21_blueprint(); PrecompilesValue::get() @@ -161,28 +143,22 @@ fn test_request_service() { ) .execute_returns(()); - // Now register operator - let preferences_data = OperatorPreferences { - key: test_ecdsa_key(), - price_targets: price_targets(MachineKind::Large), - } - .encode(); - - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::register_operator { - blueprint_id: U256::from(0), - preferences: UnboundedBytes::from(preferences_data), - registration_args: UnboundedBytes::from(vec![0u8]), - }, - ) - .execute_returns(()); + // Register operator using pallet function + let bob: AccountId32 = TestAccount::Bob.into(); + assert_ok!(Services::register( + RuntimeOrigin::signed(bob.clone()), + 0, + OperatorPreferences { + key: test_ecdsa_key(), + price_targets: price_targets(MachineKind::Large), + }, + Default::default(), + 0, + )); - // Finally, request the service + // Request service from EVM let permitted_callers_data: Vec = vec![TestAccount::Alex.into()]; - let service_providers_data: Vec = vec![TestAccount::Bob.into()]; + let service_providers_data: Vec = vec![bob.clone()]; let request_args_data = vec![0u8]; PrecompilesValue::get() @@ -190,27 +166,30 @@ fn test_request_service() { TestAccount::Alex, H160::from_low_u64_be(1), PCall::request_service { - blueprint_id: U256::from(0), // Use the first blueprint + blueprint_id: U256::from(0), permitted_callers_data: UnboundedBytes::from(permitted_callers_data.encode()), service_providers_data: UnboundedBytes::from(service_providers_data.encode()), request_args_data: UnboundedBytes::from(request_args_data), - assets: [WETH].into_iter().map(Into::into).collect(), + asset_security_requirements: vec![get_security_requirement(WETH, &[10, 20])] + .into_iter() + .map(|r| r.encode().into()) + .collect(), ttl: U256::from(1000), payment_asset_id: U256::from(0), payment_token_address: Default::default(), amount: U256::from(0), + min_operators: 1, + max_operators: u32::MAX, }, ) .execute_returns(()); - // Approve the service request by the operator(s) - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::approve { request_id: U256::from(0), restaking_percent: 10 }, - ) - .execute_returns(()); + // Approve using pallet function + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); // Ensure the service instance is created assert!(Instances::::contains_key(0)); @@ -221,7 +200,6 @@ fn test_request_service() { fn test_request_service_with_erc20() { ExtBuilder.build().execute_with(|| { assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - // First create the blueprint let blueprint_data = cggmp21_blueprint(); PrecompilesValue::get() @@ -234,33 +212,27 @@ fn test_request_service_with_erc20() { ) .execute_returns(()); - // Now register operator - let preferences_data = OperatorPreferences { - key: test_ecdsa_key(), - price_targets: price_targets(MachineKind::Large), - } - .encode(); - - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::register_operator { - blueprint_id: U256::from(0), - preferences: UnboundedBytes::from(preferences_data), - registration_args: UnboundedBytes::from(vec![0u8]), - }, - ) - .execute_returns(()); + // Register operator using pallet function + let bob: AccountId32 = TestAccount::Bob.into(); + assert_ok!(Services::register( + RuntimeOrigin::signed(bob.clone()), + 0, + OperatorPreferences { + key: test_ecdsa_key(), + price_targets: price_targets(MachineKind::Large), + }, + Default::default(), + 0, + )); assert_ok!( - Services::query_erc20_balance_of(USDC_ERC20, Services::address()) + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) .map(|(balance, _)| balance), U256::zero(), ); - // Finally, request the service + let permitted_callers_data: Vec = vec![TestAccount::Alex.into()]; - let service_providers_data: Vec = vec![TestAccount::Bob.into()]; + let service_providers_data: Vec = vec![bob.clone()]; let request_args_data = vec![0u8]; let payment_amount = U256::from(5).mul(U256::from(10).pow(6.into())); // 5 USDC @@ -270,34 +242,37 @@ fn test_request_service_with_erc20() { TestAccount::Alex, H160::from_low_u64_be(1), PCall::request_service { - blueprint_id: U256::from(0), // Use the first blueprint + blueprint_id: U256::from(0), permitted_callers_data: UnboundedBytes::from(permitted_callers_data.encode()), service_providers_data: UnboundedBytes::from(service_providers_data.encode()), request_args_data: UnboundedBytes::from(request_args_data), - assets: [TNT, WETH].into_iter().map(Into::into).collect(), + asset_security_requirements: vec![get_security_requirement(WETH, &[10, 20])] + .into_iter() + .map(|r| r.encode().into()) + .collect(), ttl: U256::from(1000), payment_asset_id: U256::from(0), payment_token_address: USDC_ERC20.into(), amount: payment_amount, + min_operators: 1, + max_operators: u32::MAX, }, ) .execute_returns(()); // Services pallet address now should have 5 USDC assert_ok!( - Services::query_erc20_balance_of(USDC_ERC20, Services::address()) + Services::query_erc20_balance_of(USDC_ERC20, Services::pallet_evm_account()) .map(|(balance, _)| balance), payment_amount ); - // Approve the service request by the operator(s) - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::approve { request_id: U256::from(0), restaking_percent: 10 }, - ) - .execute_returns(()); + // Approve using pallet function + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); // Ensure the service instance is created assert!(Instances::::contains_key(0)); @@ -308,7 +283,6 @@ fn test_request_service_with_erc20() { fn test_request_service_with_asset() { ExtBuilder.build().execute_with(|| { assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - // First create the blueprint let blueprint_data = cggmp21_blueprint(); PrecompilesValue::get() @@ -321,30 +295,23 @@ fn test_request_service_with_asset() { ) .execute_returns(()); - // Now register operator - let preferences_data = OperatorPreferences { - key: test_ecdsa_key(), - price_targets: price_targets(MachineKind::Large), - } - .encode(); - - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::register_operator { - blueprint_id: U256::from(0), - preferences: UnboundedBytes::from(preferences_data), - registration_args: UnboundedBytes::from(vec![0u8]), - }, - ) - .execute_returns(()); + // Register operator using pallet function + let bob: AccountId32 = TestAccount::Bob.into(); + assert_ok!(Services::register( + RuntimeOrigin::signed(bob.clone()), + 0, + OperatorPreferences { + key: test_ecdsa_key(), + price_targets: price_targets(MachineKind::Large), + }, + Default::default(), + 0, + )); - assert_eq!(Assets::balance(USDC, Services::account_id()), 0); + assert_eq!(Assets::balance(USDC, Services::pallet_account()), 0); - // Finally, request the service let permitted_callers_data: Vec = vec![TestAccount::Alex.into()]; - let service_providers_data: Vec = vec![TestAccount::Bob.into()]; + let service_providers_data: Vec = vec![bob.clone()]; let request_args_data = vec![0u8]; let payment_amount = U256::from(5).mul(U256::from(10).pow(6.into())); // 5 USDC @@ -354,91 +321,43 @@ fn test_request_service_with_asset() { TestAccount::Alex, H160::from_low_u64_be(1), PCall::request_service { - blueprint_id: U256::from(0), // Use the first blueprint + blueprint_id: U256::from(0), permitted_callers_data: UnboundedBytes::from(permitted_callers_data.encode()), service_providers_data: UnboundedBytes::from(service_providers_data.encode()), request_args_data: UnboundedBytes::from(request_args_data), - assets: [TNT, WETH].into_iter().map(Into::into).collect(), + asset_security_requirements: vec![get_security_requirement(WETH, &[10, 20])] + .into_iter() + .map(|r| r.encode().into()) + .collect(), ttl: U256::from(1000), payment_asset_id: U256::from(USDC), payment_token_address: Default::default(), amount: payment_amount, + min_operators: 1, + max_operators: u32::MAX, }, ) .execute_returns(()); // Services pallet address now should have 5 USDC - assert_eq!(Assets::balance(USDC, Services::account_id()), payment_amount.as_u128()); + assert_eq!(Assets::balance(USDC, Services::pallet_account()), payment_amount.as_u128()); - // Approve the service request by the operator(s) - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::approve { request_id: U256::from(0), restaking_percent: 10 }, - ) - .execute_returns(()); + // Approve using pallet function + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); // Ensure the service instance is created assert!(Instances::::contains_key(0)); }); } -#[test] -fn test_unregister_operator() { - ExtBuilder.build().execute_with(|| { - assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - // First register operator (after blueprint creation) - let blueprint_data = cggmp21_blueprint(); - - PrecompilesValue::get() - .prepare_test( - TestAccount::Alex, - H160::from_low_u64_be(1), - PCall::create_blueprint { - blueprint_data: UnboundedBytes::from(blueprint_data.encode()), - }, - ) - .execute_returns(()); - - let preferences_data = OperatorPreferences { - key: test_ecdsa_key(), - price_targets: price_targets(MachineKind::Large), - } - .encode(); - - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::register_operator { - blueprint_id: U256::from(0), - preferences: UnboundedBytes::from(preferences_data), - registration_args: UnboundedBytes::from(vec![0u8]), - }, - ) - .execute_returns(()); - - // Now unregister operator - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::unregister_operator { blueprint_id: U256::from(0) }, - ) - .execute_returns(()); - - // Ensure the operator is removed - let bob_account: AccountId32 = TestAccount::Bob.into(); - assert!(!Operators::::contains_key(0, bob_account)); - }); -} - #[test] fn test_terminate_service() { ExtBuilder.build().execute_with(|| { assert_ok!(Services::update_master_blueprint_service_manager(RuntimeOrigin::root(), MBSM)); - // First request a service let blueprint_data = cggmp21_blueprint(); PrecompilesValue::get() @@ -451,26 +370,21 @@ fn test_terminate_service() { ) .execute_returns(()); - let preferences_data = OperatorPreferences { - key: test_ecdsa_key(), - price_targets: price_targets(MachineKind::Large), - } - .encode(); - - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::register_operator { - blueprint_id: U256::from(0), - preferences: UnboundedBytes::from(preferences_data), - registration_args: UnboundedBytes::from(vec![0u8]), - }, - ) - .execute_returns(()); + // Register operator using pallet function + let bob: AccountId32 = TestAccount::Bob.into(); + assert_ok!(Services::register( + RuntimeOrigin::signed(bob.clone()), + 0, + OperatorPreferences { + key: test_ecdsa_key(), + price_targets: price_targets(MachineKind::Large), + }, + Default::default(), + 0, + )); let permitted_callers_data: Vec = vec![TestAccount::Alex.into()]; - let service_providers_data: Vec = vec![TestAccount::Bob.into()]; + let service_providers_data: Vec = vec![bob.clone()]; let request_args_data = vec![0u8]; PrecompilesValue::get() @@ -482,23 +396,26 @@ fn test_terminate_service() { permitted_callers_data: UnboundedBytes::from(permitted_callers_data.encode()), service_providers_data: UnboundedBytes::from(service_providers_data.encode()), request_args_data: UnboundedBytes::from(request_args_data), - assets: [WETH].into_iter().map(Into::into).collect(), + asset_security_requirements: vec![get_security_requirement(WETH, &[10, 20])] + .into_iter() + .map(|r| r.encode().into()) + .collect(), ttl: U256::from(1000), payment_asset_id: U256::from(0), payment_token_address: Default::default(), amount: U256::from(0), + min_operators: 1, + max_operators: u32::MAX, }, ) .execute_returns(()); - // Approve the service request by the operator(s) - PrecompilesValue::get() - .prepare_test( - TestAccount::Bob, - H160::from_low_u64_be(1), - PCall::approve { request_id: U256::from(0), restaking_percent: 10 }, - ) - .execute_returns(()); + // Approve using pallet function + assert_ok!(Services::approve( + RuntimeOrigin::signed(bob.clone()), + 0, + vec![get_security_commitment(WETH, 10), get_security_commitment(TNT, 10)], + )); assert!(Instances::::contains_key(0)); diff --git a/precompiles/staking/Cargo.toml b/precompiles/staking/Cargo.toml index 25c82c0e6..c06b34a64 100644 --- a/precompiles/staking/Cargo.toml +++ b/precompiles/staking/Cargo.toml @@ -30,10 +30,8 @@ derive_more = { workspace = true, features = ["full"] } serde = { workspace = true } sha3 = { workspace = true } - precompile-utils = { workspace = true, features = ["std", "testing"] } -# Substrate frame-election-provider-support = { workspace = true, features = ["std"] } pallet-balances = { workspace = true, features = ["std"] } pallet-session = { workspace = true, features = ["std"] } diff --git a/primitives/rpc/debug/src/lib.rs b/primitives/rpc/debug/src/lib.rs index e518d19b1..3099af758 100644 --- a/primitives/rpc/debug/src/lib.rs +++ b/primitives/rpc/debug/src/lib.rs @@ -17,6 +17,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[warn(unused_imports)] use ethereum::{TransactionV0 as LegacyTransaction, TransactionV2 as Transaction}; use ethereum_types::{H160, H256, U256}; use parity_scale_codec::{Decode, Encode}; diff --git a/primitives/src/services/constraints.rs b/primitives/src/services/constraints.rs new file mode 100644 index 000000000..eaf0d4112 --- /dev/null +++ b/primitives/src/services/constraints.rs @@ -0,0 +1,61 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use frame_support::pallet_prelude::*; + +/// A Higher level abstraction of all the constraints. +pub trait Constraints { + /// Maximum number of fields in a job call. + type MaxFields: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum size of a field in a job call. + type MaxFieldsSize: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum length of metadata string length. + type MaxMetadataLength: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum number of jobs per service. + type MaxJobsPerService: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum number of Operators per service. + type MaxOperatorsPerService: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum number of permitted callers per service. + type MaxPermittedCallers: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum number of services per operator. + type MaxServicesPerOperator: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum number of blueprints per operator. + type MaxBlueprintsPerOperator: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum number of services per user. + type MaxServicesPerUser: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum number of binaries per gadget. + type MaxBinariesPerGadget: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum number of sources per gadget. + type MaxSourcesPerGadget: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Git owner maximum length. + type MaxGitOwnerLength: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Git repository maximum length. + type MaxGitRepoLength: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Git tag maximum length. + type MaxGitTagLength: Get + Default + Parameter + MaybeSerializeDeserialize; + /// binary name maximum length. + type MaxBinaryNameLength: Get + Default + Parameter + MaybeSerializeDeserialize; + /// IPFS hash maximum length. + type MaxIpfsHashLength: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Container registry maximum length. + type MaxContainerRegistryLength: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Container image name maximum length. + type MaxContainerImageNameLength: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Container image tag maximum length. + type MaxContainerImageTagLength: Get + Default + Parameter + MaybeSerializeDeserialize; + /// Maximum number of assets per service. + type MaxAssetsPerService: Get + Default + Parameter + MaybeSerializeDeserialize; +} diff --git a/primitives/src/services/evm.rs b/primitives/src/services/evm.rs new file mode 100644 index 000000000..85192a293 --- /dev/null +++ b/primitives/src/services/evm.rs @@ -0,0 +1,68 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use crate::Weight; +use fp_evm::CallInfo; +use frame_system::Config; +use sp_core::{H160, U256}; +use sp_std::vec::Vec; + +#[derive(Debug)] +pub struct RunnerError> { + pub error: E, + pub weight: Weight, +} + +#[allow(clippy::too_many_arguments)] +pub trait EvmRunner { + type Error: Into; + + fn call( + source: H160, + target: H160, + input: Vec, + value: U256, + gas_limit: u64, + is_transactional: bool, + validate: bool, + ) -> Result>; +} + +/// A mapping function that converts EVM gas to Substrate weight and vice versa +pub trait EvmGasWeightMapping { + /// Convert EVM gas to Substrate weight + fn gas_to_weight(gas: u64, without_base_weight: bool) -> Weight; + /// Convert Substrate weight to EVM gas + fn weight_to_gas(weight: Weight) -> u64; +} + +impl EvmGasWeightMapping for () { + fn gas_to_weight(_gas: u64, _without_base_weight: bool) -> Weight { + Default::default() + } + fn weight_to_gas(_weight: Weight) -> u64 { + Default::default() + } +} + +/// Trait to be implemented for evm address mapping. +pub trait EvmAddressMapping { + /// Convert an address to an account id. + fn into_account_id(address: H160) -> A; + + /// Convert an account id to an address. + fn into_address(account_id: A) -> H160; +} diff --git a/primitives/src/services/field.rs b/primitives/src/services/field.rs index 087328c06..fa46c54fa 100644 --- a/primitives/src/services/field.rs +++ b/primitives/src/services/field.rs @@ -275,9 +275,6 @@ pub enum FieldType { /// A Field of `String` type. #[codec(index = 10)] String, - /// A Field of `Vec` type. - #[codec(index = 11)] - Bytes, /// A Field of `Option` type. #[codec(index = 12)] Optional(Box), diff --git a/primitives/src/services/gadget.rs b/primitives/src/services/gadget.rs new file mode 100644 index 000000000..b605dd7b5 --- /dev/null +++ b/primitives/src/services/gadget.rs @@ -0,0 +1,299 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::{constraints::Constraints, BoundedString}; +use educe::Educe; +use frame_support::pallet_prelude::*; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub enum Gadget { + /// A Gadget that is a WASM binary that will be executed. + /// inside the shell using the wasm runtime. + Wasm(WasmGadget), + /// A Gadget that is a native binary that will be executed. + /// inside the shell using the OS. + Native(NativeGadget), + /// A Gadget that is a container that will be executed. + /// inside the shell using the container runtime (e.g. Docker, Podman, etc.) + Container(ContainerGadget), +} + +impl Default for Gadget { + fn default() -> Self { + Gadget::Wasm(WasmGadget { runtime: WasmRuntime::Wasmtime, sources: Default::default() }) + } +} + +/// A binary that is stored in the Github release. +/// this will constuct the URL to the release and download the binary. +/// The URL will be in the following format: +/// https://github.com///releases/download/v/ +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct GithubFetcher { + /// The owner of the repository. + pub owner: BoundedString, + /// The repository name. + pub repo: BoundedString, + /// The release tag of the repository. + /// NOTE: The tag should be a valid semver tag. + pub tag: BoundedString, + /// The names of the binary in the release by the arch and the os. + pub binaries: BoundedVec, C::MaxBinariesPerGadget>, +} + +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct TestFetcher { + /// The cargo package name that contains the blueprint logic + pub cargo_package: BoundedString, + /// The specific binary name that contains the blueprint logic. + /// Should match up what is in the Cargo.toml file under [[bin]]/name + pub cargo_bin: BoundedString, + /// The base path to the workspace/crate + pub base_path: BoundedString, +} + +/// The CPU or System architecture. +#[derive( + PartialEq, + PartialOrd, + Ord, + Eq, + Encode, + Decode, + RuntimeDebug, + TypeInfo, + Clone, + Copy, + MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum Architecture { + /// WebAssembly architecture (32-bit). + #[codec(index = 0)] + Wasm, + /// WebAssembly architecture (64-bit). + #[codec(index = 1)] + Wasm64, + /// WASI architecture (32-bit). + #[codec(index = 2)] + Wasi, + /// WASI architecture (64-bit). + #[codec(index = 3)] + Wasi64, + /// Amd architecture (32-bit). + #[codec(index = 4)] + Amd, + /// Amd64 architecture (x86_64). + #[codec(index = 5)] + Amd64, + /// Arm architecture (32-bit). + #[codec(index = 6)] + Arm, + /// Arm64 architecture (64-bit). + #[codec(index = 7)] + Arm64, + /// Risc-V architecture (32-bit). + #[codec(index = 8)] + RiscV, + /// Risc-V architecture (64-bit). + #[codec(index = 9)] + RiscV64, +} + +/// Operating System that the binary is compiled for. +#[derive( + Default, + PartialEq, + PartialOrd, + Ord, + Eq, + Encode, + Decode, + RuntimeDebug, + TypeInfo, + Clone, + Copy, + MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum OperatingSystem { + /// Unknown operating system. + /// This is used when the operating system is not known + /// for example, for WASM, where the OS is not relevant. + #[default] + #[codec(index = 0)] + Unknown, + /// Linux operating system. + #[codec(index = 1)] + Linux, + /// Windows operating system. + #[codec(index = 2)] + Windows, + /// MacOS operating system. + #[codec(index = 3)] + MacOS, + /// BSD operating system. + #[codec(index = 4)] + BSD, +} + +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct GadgetBinary { + /// CPU or System architecture. + pub arch: Architecture, + /// Operating System that the binary is compiled for. + pub os: OperatingSystem, + /// The name of the binary. + pub name: BoundedString, + /// The sha256 hash of the binary. + /// used to verify the downloaded binary. + pub sha256: [u8; 32], +} + +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct GadgetSource { + /// The fetcher that will fetch the gadget from a remote source. + fetcher: GadgetSourceFetcher, +} + +/// A Gadget Source Fetcher is a fetcher that will fetch the gadget +/// from a remote source. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub enum GadgetSourceFetcher { + /// A Gadget that will be fetched from the IPFS. + #[codec(index = 0)] + IPFS(BoundedVec), + /// A Gadget that will be fetched from the Github release. + #[codec(index = 1)] + Github(GithubFetcher), + /// A Gadgets that will be fetched from the container registry. + #[codec(index = 2)] + ContainerImage(ImageRegistryFetcher), + /// For tests only + #[codec(index = 3)] + Testing(TestFetcher), +} + +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct ImageRegistryFetcher { + /// The URL of the container registry. + registry: BoundedString, + /// The name of the image. + image: BoundedString, + /// The tag of the image. + tag: BoundedString, +} + +/// A WASM binary that contains all the compiled gadget code. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct WasmGadget { + /// Which runtime to use to execute the WASM binary. + pub runtime: WasmRuntime, + /// Where the WASM binary is stored. + pub sources: BoundedVec, C::MaxSourcesPerGadget>, +} + +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub enum WasmRuntime { + /// The WASM binary will be executed using the WASMtime runtime. + #[codec(index = 0)] + Wasmtime, + /// The WASM binary will be executed using the Wasmer runtime. + #[codec(index = 1)] + Wasmer, +} + +/// A Native binary that contains all the gadget code. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct NativeGadget { + /// Where the WASM binary is stored. + pub sources: BoundedVec, C::MaxSourcesPerGadget>, +} + +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct ContainerGadget { + /// Where the Image of the gadget binary is stored. + pub sources: BoundedVec, C::MaxSourcesPerGadget>, +} diff --git a/primitives/src/services/jobs.rs b/primitives/src/services/jobs.rs new file mode 100644 index 000000000..c279bdd73 --- /dev/null +++ b/primitives/src/services/jobs.rs @@ -0,0 +1,171 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use crate::services::{constraints::Constraints, types::TypeCheckError}; +use educe::Educe; +use frame_support::pallet_prelude::*; +use parity_scale_codec::Encode; +use sp_core::H160; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +use super::{ + field::{Field, FieldType}, + BoundedString, +}; + +/// A Job Definition is a definition of a job that can be called. +/// It contains the input and output fields of the job with the permitted caller. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Default(bound()), Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct JobDefinition { + /// The metadata of the job. + pub metadata: JobMetadata, + /// These are parameters that are required for this job. + /// i.e. the input. + pub params: BoundedVec, + /// These are the result, the return values of this job. + /// i.e. the output. + pub result: BoundedVec, +} + +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Default(bound()), Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct JobMetadata { + /// The Job name. + pub name: BoundedString, + /// The Job description. + pub description: Option>, +} + +/// A Job Call is a call to execute a job using it's job definition. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe( + Default(bound(AccountId: Default)), + Clone(bound(AccountId: Clone)), + PartialEq(bound(AccountId: PartialEq)), + Eq +)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] +#[cfg_attr( + feature = "std", + derive(Serialize, Deserialize), + serde(bound(serialize = "AccountId: Serialize", deserialize = "AccountId: Deserialize<'de>")), + educe(Debug(bound(AccountId: core::fmt::Debug))) +)] +pub struct JobCall { + /// The Service ID that this call is for. + pub service_id: u64, + /// The job definition index in the service that this call is for. + pub job: u8, + /// The supplied arguments for this job call. + pub args: BoundedVec, C::MaxFields>, +} + +/// A Job Call Result is the result of a job call. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe( + Default(bound(AccountId: Default)), + Clone(bound(AccountId: Clone)), + PartialEq(bound(AccountId: PartialEq)), + Eq +)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] +#[cfg_attr( + feature = "std", + derive(Serialize, Deserialize), + serde(bound(serialize = "AccountId: Serialize", deserialize = "AccountId: Deserialize<'de>")), + educe(Debug(bound(AccountId: core::fmt::Debug))) +)] +pub struct JobCallResult { + /// The id of the service. + pub service_id: u64, + /// The id of the job call. + pub call_id: u64, + /// The result of the job call. + pub result: BoundedVec, C::MaxFields>, +} + +/// A Job Result verifier is a verifier that will verify the result of a job call +/// using different verification methods. +#[derive(Default, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Clone, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum JobResultVerifier { + /// No verification is needed. + #[default] + None, + /// An EVM Contract Address that will verify the result. + Evm(H160), + // NOTE(@shekohex): Add more verification methods here. +} + +/// Type checks the supplied arguments against the parameters. +pub fn type_checker( + params: &[FieldType], + args: &[Field], +) -> Result<(), TypeCheckError> { + if params.len() != args.len() { + return Err(TypeCheckError::NotEnoughArguments { + expected: params.len() as u8, + actual: args.len() as u8, + }); + } + for i in 0..args.len() { + let arg = &args[i]; + let expected = ¶ms[i]; + if arg != expected { + return Err(TypeCheckError::ArgumentTypeMismatch { + index: i as u8, + expected: expected.clone(), + actual: arg.clone().into(), + }); + } + } + Ok(()) +} + +impl JobCall { + /// Check if the supplied arguments match the job definition types. + pub fn type_check(&self, job_def: &JobDefinition) -> Result<(), TypeCheckError> { + type_checker(&job_def.params, &self.args) + } +} + +impl JobCallResult { + /// Check if the supplied result match the job definition types. + pub fn type_check(&self, job_def: &JobDefinition) -> Result<(), TypeCheckError> { + type_checker(&job_def.result, &self.result) + } +} diff --git a/primitives/src/services/mod.rs b/primitives/src/services/mod.rs index 31ba7b53b..f25c34642 100644 --- a/primitives/src/services/mod.rs +++ b/primitives/src/services/mod.rs @@ -15,1222 +15,19 @@ // along with Tangle. If not, see . //! Services primitives. -use crate::Weight; -use educe::Educe; -use fp_evm::CallInfo; -use frame_support::pallet_prelude::*; -#[cfg(feature = "std")] -use serde::{Deserialize, Deserializer, Serialize}; -use sp_core::{ByteArray, RuntimeDebug, H160, U256}; -use sp_runtime::Percent; - -#[cfg(not(feature = "std"))] -use alloc::{string::String, vec, vec::Vec}; +pub mod constraints; +pub mod evm; pub mod field; -pub use field::*; - -use super::Account; - -/// A Higher level abstraction of all the constraints. -pub trait Constraints { - /// Maximum number of fields in a job call. - type MaxFields: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum size of a field in a job call. - type MaxFieldsSize: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum length of metadata string length. - type MaxMetadataLength: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum number of jobs per service. - type MaxJobsPerService: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum number of Operators per service. - type MaxOperatorsPerService: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum number of permitted callers per service. - type MaxPermittedCallers: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum number of services per operator. - type MaxServicesPerOperator: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum number of blueprints per operator. - type MaxBlueprintsPerOperator: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum number of services per user. - type MaxServicesPerUser: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum number of binaries per gadget. - type MaxBinariesPerGadget: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum number of sources per gadget. - type MaxSourcesPerGadget: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Git owner maximum length. - type MaxGitOwnerLength: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Git repository maximum length. - type MaxGitRepoLength: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Git tag maximum length. - type MaxGitTagLength: Get + Default + Parameter + MaybeSerializeDeserialize; - /// binary name maximum length. - type MaxBinaryNameLength: Get + Default + Parameter + MaybeSerializeDeserialize; - /// IPFS hash maximum length. - type MaxIpfsHashLength: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Container registry maximum length. - type MaxContainerRegistryLength: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Container image name maximum length. - type MaxContainerImageNameLength: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Container image tag maximum length. - type MaxContainerImageTagLength: Get + Default + Parameter + MaybeSerializeDeserialize; - /// Maximum number of assets per service. - type MaxAssetsPerService: Get + Default + Parameter + MaybeSerializeDeserialize; -} - -/// A Job Definition is a definition of a job that can be called. -/// It contains the input and output fields of the job with the permitted caller. - -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Default(bound()), Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct JobDefinition { - /// The metadata of the job. - pub metadata: JobMetadata, - /// These are parameters that are required for this job. - /// i.e. the input. - pub params: BoundedVec, - /// These are the result, the return values of this job. - /// i.e. the output. - pub result: BoundedVec, -} - -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Default(bound()), Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct JobMetadata { - /// The Job name. - pub name: BoundedString, - /// The Job description. - pub description: Option>, -} - -/// A Job Call is a call to execute a job using it's job definition. -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe( - Default(bound(AccountId: Default)), - Clone(bound(AccountId: Clone)), - PartialEq(bound(AccountId: PartialEq)), - Eq -)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] -#[cfg_attr( - feature = "std", - derive(Serialize, Deserialize), - serde(bound(serialize = "AccountId: Serialize", deserialize = "AccountId: Deserialize<'de>")), - educe(Debug(bound(AccountId: core::fmt::Debug))) -)] -pub struct JobCall { - /// The Service ID that this call is for. - pub service_id: u64, - /// The job definition index in the service that this call is for. - pub job: u8, - /// The supplied arguments for this job call. - pub args: BoundedVec, C::MaxFields>, -} - -/// Type checks the supplied arguments against the parameters. -pub fn type_checker( - params: &[FieldType], - args: &[Field], -) -> Result<(), TypeCheckError> { - if params.len() != args.len() { - return Err(TypeCheckError::NotEnoughArguments { - expected: params.len() as u8, - actual: args.len() as u8, - }); - } - for i in 0..args.len() { - let arg = &args[i]; - let expected = ¶ms[i]; - if arg != expected { - return Err(TypeCheckError::ArgumentTypeMismatch { - index: i as u8, - expected: expected.clone(), - actual: arg.clone().into(), - }); - } - } - Ok(()) -} - -impl JobCall { - /// Check if the supplied arguments match the job definition types. - pub fn type_check(&self, job_def: &JobDefinition) -> Result<(), TypeCheckError> { - type_checker(&job_def.params, &self.args) - } -} - -/// A Job Call Result is the result of a job call. -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe( - Default(bound(AccountId: Default)), - Clone(bound(AccountId: Clone)), - PartialEq(bound(AccountId: PartialEq)), - Eq -)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] -#[cfg_attr( - feature = "std", - derive(Serialize, Deserialize), - serde(bound(serialize = "AccountId: Serialize", deserialize = "AccountId: Deserialize<'de>")), - educe(Debug(bound(AccountId: core::fmt::Debug))) -)] -pub struct JobCallResult { - /// The id of the service. - pub service_id: u64, - /// The id of the job call. - pub call_id: u64, - /// The result of the job call. - pub result: BoundedVec, C::MaxFields>, -} - -impl JobCallResult { - /// Check if the supplied result match the job definition types. - pub fn type_check(&self, job_def: &JobDefinition) -> Result<(), TypeCheckError> { - type_checker(&job_def.result, &self.result) - } -} - -/// A Job Result verifier is a verifier that will verify the result of a job call -/// using different verification methods. -#[derive(Default, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Clone, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum JobResultVerifier { - /// No verification is needed. - #[default] - None, - /// An EVM Contract Address that will verify the result. - Evm(sp_core::H160), - // NOTE(@shekohex): Add more verification methods here. -} - -/// An error that can occur during type checking. -#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Clone, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum TypeCheckError { - /// The argument type does not match the expected type. - ArgumentTypeMismatch { - /// The index of the argument. - index: u8, - /// The expected type. - expected: FieldType, - /// The actual type. - actual: FieldType, - }, - /// Not enough arguments were supplied. - NotEnoughArguments { - /// The number of arguments that were expected. - expected: u8, - /// The number of arguments that were supplied. - actual: u8, - }, - /// The result type does not match the expected type. - ResultTypeMismatch { - /// The index of the argument. - index: u8, - /// The expected type. - expected: FieldType, - /// The actual type. - actual: FieldType, - }, -} - -impl frame_support::traits::PalletError for TypeCheckError { - const MAX_ENCODED_SIZE: usize = 2; -} - -// -*** Service ***- - -/// Blueprint Service Manager is a smart contract that will manage the service lifecycle. -#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Clone, Copy, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[non_exhaustive] -pub enum BlueprintServiceManager { - /// A Smart contract that will manage the service lifecycle. - Evm(sp_core::H160), -} - -impl BlueprintServiceManager { - pub fn try_into_evm(self) -> Result { - match self { - Self::Evm(addr) => Ok(addr), - } - } -} - -impl Default for BlueprintServiceManager { - fn default() -> Self { - Self::Evm(Default::default()) - } -} - -/// Master Blueprint Service Manager Revision. -#[derive( - Default, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Clone, Copy, MaxEncodedLen, -)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[non_exhaustive] -pub enum MasterBlueprintServiceManagerRevision { - /// Use Whatever the latest revision available on-chain. - /// - /// This is the default value. - #[default] - #[codec(index = 0)] - Latest, - - /// Use a specific revision number. - /// - /// Note: Must be already deployed on-chain. - #[codec(index = 1)] - Specific(u32), -} - -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Default(bound()), Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct ServiceMetadata { - /// The Service name. - pub name: BoundedString, - /// The Service description. - pub description: Option>, - /// The Service author. - /// Could be a company or a person. - pub author: Option>, - /// The Job category. - pub category: Option>, - /// Code Repository URL. - /// Could be a github, gitlab, or any other code repository. - pub code_repository: Option>, - /// Service Logo URL. - pub logo: Option>, - /// Service Website URL. - pub website: Option>, - /// Service License. - pub license: Option>, -} - -/// A Service Blueprint is a the main definition of a service. -/// it contains the metadata of the service, the job definitions, and other hooks, along with the -/// gadget that will be executed when one of the jobs is calling this service. -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Default(bound()), Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct ServiceBlueprint { - /// The metadata of the service. - pub metadata: ServiceMetadata, - /// The job definitions that are available in this service. - pub jobs: BoundedVec, C::MaxJobsPerService>, - /// The parameters that are required for the service registration. - pub registration_params: BoundedVec, - /// The request hook that will be called before creating a service from the service blueprint. - /// The parameters that are required for the service request. - pub request_params: BoundedVec, - /// A Blueprint Manager is a smart contract that implements the `IBlueprintServiceManager` - /// interface. - pub manager: BlueprintServiceManager, - /// The Revision number of the Master Blueprint Service Manager. - /// - /// If not sure what to use, use `MasterBlueprintServiceManagerRevision::default()` which will - /// use the latest revision available. - pub master_manager_revision: MasterBlueprintServiceManagerRevision, - /// The gadget that will be executed for the service. - pub gadget: Gadget, -} - -impl ServiceBlueprint { - /// Check if the supplied arguments match the registration parameters. - pub fn type_check_registration( - &self, - args: &[Field], - ) -> Result<(), TypeCheckError> { - type_checker(&self.registration_params, args) - } - - /// Check if the supplied arguments match the request parameters. - pub fn type_check_request( - &self, - args: &[Field], - ) -> Result<(), TypeCheckError> { - type_checker(&self.request_params, args) - } - - /// Converts the struct to ethabi ParamType. - pub fn to_ethabi_param_type() -> ethabi::ParamType { - ethabi::ParamType::Tuple(vec![ - // Service Metadata - ethabi::ParamType::Tuple(vec![ - // Service Name - ethabi::ParamType::String, - // Service Description - ethabi::ParamType::String, - // Service Author - ethabi::ParamType::String, - // Service Category - ethabi::ParamType::String, - // Code Repository - ethabi::ParamType::String, - // Service Logo - ethabi::ParamType::String, - // Service Website - ethabi::ParamType::String, - // Service License - ethabi::ParamType::String, - ]), - // Job Definitions ? - // Registration Parameters ? - // Request Parameters ? - // Blueprint Manager - ethabi::ParamType::Address, - // Master Manager Revision - ethabi::ParamType::Uint(32), - // Gadget ? - ]) - } - - /// Converts the struct to ethabi Param. - pub fn to_ethabi_param() -> ethabi::Param { - ethabi::Param { - name: String::from("blueprint"), - kind: Self::to_ethabi_param_type(), - internal_type: Some(String::from("struct MasterBlueprintServiceManager.Blueprint")), - } - } - - /// Converts the struct to ethabi Token. - pub fn to_ethabi(&self) -> ethabi::Token { - ethabi::Token::Tuple(vec![ - // Service Metadata - ethabi::Token::Tuple(vec![ - // Service Name - ethabi::Token::String(self.metadata.name.as_str().into()), - // Service Description - ethabi::Token::String( - self.metadata - .description - .as_ref() - .map(|v| v.as_str().into()) - .unwrap_or_default(), - ), - // Service Author - ethabi::Token::String( - self.metadata.author.as_ref().map(|v| v.as_str().into()).unwrap_or_default(), - ), - // Service Category - ethabi::Token::String( - self.metadata.category.as_ref().map(|v| v.as_str().into()).unwrap_or_default(), - ), - // Code Repository - ethabi::Token::String( - self.metadata - .code_repository - .as_ref() - .map(|v| v.as_str().into()) - .unwrap_or_default(), - ), - // Service Logo - ethabi::Token::String( - self.metadata.logo.as_ref().map(|v| v.as_str().into()).unwrap_or_default(), - ), - // Service Website - ethabi::Token::String( - self.metadata.website.as_ref().map(|v| v.as_str().into()).unwrap_or_default(), - ), - // Service License - ethabi::Token::String( - self.metadata.license.as_ref().map(|v| v.as_str().into()).unwrap_or_default(), - ), - ]), - // Job Definitions ? - // Registration Parameters ? - // Request Parameters ? - // Blueprint Manager - match self.manager { - BlueprintServiceManager::Evm(addr) => ethabi::Token::Address(addr), - }, - // Master Manager Revision - match self.master_manager_revision { - MasterBlueprintServiceManagerRevision::Latest => { - ethabi::Token::Uint(ethabi::Uint::MAX) - }, - MasterBlueprintServiceManagerRevision::Specific(rev) => { - ethabi::Token::Uint(rev.into()) - }, - }, - // Gadget ? - ]) - } -} - -/// A service request is a request to create a service from a service blueprint. -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe( - Default(bound(AccountId: Default, BlockNumber: Default, AssetId: Default)), - Clone(bound(AccountId: Clone, BlockNumber: Clone, AssetId: Clone)), - PartialEq(bound(AccountId: PartialEq, BlockNumber: PartialEq, AssetId: PartialEq)), - Eq -)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] -#[cfg_attr( - feature = "std", - derive(Serialize, Deserialize), - serde(bound( - serialize = "AccountId: Serialize, BlockNumber: Serialize, AssetId: Serialize", - deserialize = "AccountId: Deserialize<'de>, BlockNumber: Deserialize<'de>, AssetId: Deserialize<'de>" - )), - educe(Debug(bound(AccountId: core::fmt::Debug, BlockNumber: core::fmt::Debug, AssetId: core::fmt::Debug))) -)] -pub struct ServiceRequest { - /// The service blueprint ID. - pub blueprint: u64, - /// The owner of the service. - pub owner: AccountId, - /// The permitted caller(s) of the service. - pub permitted_callers: BoundedVec, - /// Asset(s) used to secure the service instance. - pub assets: BoundedVec, - /// The Lifetime of the service. - pub ttl: BlockNumber, - /// The supplied arguments for the service request. - pub args: BoundedVec, C::MaxFields>, - /// The Selected Operator(s) with their approval state. - pub operators_with_approval_state: - BoundedVec<(AccountId, ApprovalState), C::MaxOperatorsPerService>, -} - -impl - ServiceRequest -{ - /// Returns true if all the operators are [ApprovalState::Approved]. - pub fn is_approved(&self) -> bool { - self.operators_with_approval_state - .iter() - .all(|(_, state)| matches!(state, ApprovalState::Approved { .. })) - } - - /// Returns true if any the operators are [ApprovalState::Pending]. - pub fn is_pending(&self) -> bool { - self.operators_with_approval_state - .iter() - .any(|(_, state)| state == &ApprovalState::Pending) - } - - /// Returns true if any the operators are [ApprovalState::Rejected]. - pub fn is_rejected(&self) -> bool { - self.operators_with_approval_state - .iter() - .any(|(_, state)| state == &ApprovalState::Rejected) - } -} - -/// A staging service payment is a payment that is made for a service request -/// but will be paid when the service is created or refunded if the service is rejected. -#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Copy, Clone, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct StagingServicePayment { - /// The service request ID. - pub request_id: u64, - /// Where the refund should go. - pub refund_to: Account, - /// The Asset used in the payment. - pub asset: Asset, - /// The amount of the asset that is paid. - pub amount: Balance, -} - -impl Default for StagingServicePayment -where - AccountId: ByteArray, - AssetId: sp_runtime::traits::Zero, - Balance: Default, -{ - fn default() -> Self { - Self { - request_id: Default::default(), - refund_to: Account::default(), - asset: Asset::default(), - amount: Default::default(), - } - } -} - -/// A Service is an instance of a service blueprint. -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe( - Default(bound(AccountId: Default, BlockNumber: Default, AssetId: Default)), - Clone(bound(AccountId: Clone, BlockNumber: Clone, AssetId: Clone)), - PartialEq(bound(AccountId: PartialEq, BlockNumber: PartialEq, AssetId: PartialEq)), - Eq -)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] -#[cfg_attr( - feature = "std", - derive(Serialize, Deserialize), - serde(bound( - serialize = "AccountId: Serialize, BlockNumber: Serialize, AssetId: Serialize", - deserialize = "AccountId: Deserialize<'de>, BlockNumber: Deserialize<'de>, AssetId: Deserialize<'de>", - )), - educe(Debug(bound(AccountId: core::fmt::Debug, BlockNumber: core::fmt::Debug, AssetId: core::fmt::Debug))) -)] -pub struct Service { - /// The service ID. - pub id: u64, - /// The Blueprint ID of the service. - pub blueprint: u64, - /// The owner of the service. - pub owner: AccountId, - /// The Permitted caller(s) of the service. - pub permitted_callers: BoundedVec, - /// The Selected operators(s) for this service with their restaking Percentage. - // This a Vec instead of a BTreeMap because the number of operators is expected to be small - // (smaller than 512) and the overhead of a BTreeMap is not worth it, plus BoundedBTreeMap is - // not serde compatible. - pub operators: BoundedVec<(AccountId, Percent), C::MaxOperatorsPerService>, - /// Asset(s) used to secure the service instance. - pub assets: BoundedVec, - /// The Lifetime of the service. - pub ttl: BlockNumber, -} - -/// Operator's Approval State. -#[derive( - Default, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Copy, Clone, MaxEncodedLen, -)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum ApprovalState { - /// The operator is pending approval. - #[codec(index = 0)] - #[default] - Pending, - /// The operator is approved to provide the service. - #[codec(index = 1)] - Approved { - /// The restaking percentage of the operator. - restaking_percent: Percent, - }, - /// The operator is rejected to provide the service. - #[codec(index = 2)] - Rejected, -} - -/// Different types of assets that can be used. -#[derive( - PartialEq, - Eq, - Encode, - Decode, - RuntimeDebug, - TypeInfo, - Copy, - Clone, - MaxEncodedLen, - Ord, - PartialOrd, -)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum Asset { - /// Use the specified AssetId. - #[codec(index = 0)] - Custom(AssetId), - - /// Use an ERC20-like token with the specified contract address. - #[codec(index = 1)] - Erc20(sp_core::H160), -} - -impl Default for Asset { - fn default() -> Self { - Asset::Custom(sp_runtime::traits::Zero::zero()) - } -} - -impl Asset { - pub fn to_ethabi_param_type() -> ethabi::ParamType { - ethabi::ParamType::Tuple(vec![ - // Kind of the Asset - ethabi::ParamType::Uint(8), - // Data of the Asset (Contract Address or AssetId) - ethabi::ParamType::FixedBytes(32), - ]) - } +pub mod gadget; +pub mod jobs; +pub mod service; +pub mod types; - pub fn to_ethabi_param() -> ethabi::Param { - ethabi::Param { - name: String::from("asset"), - kind: Self::to_ethabi_param_type(), - internal_type: Some(String::from("struct ServiceOperators.Asset")), - } - } - - pub fn to_ethabi(&self) -> ethabi::Token { - match self { - Asset::Custom(asset_id) => { - let asset_id = asset_id.using_encoded(ethabi::Uint::from_little_endian); - let mut asset_id_bytes = [0u8; core::mem::size_of::()]; - asset_id.to_big_endian(&mut asset_id_bytes); - ethabi::Token::Tuple(vec![ - ethabi::Token::Uint(0.into()), - ethabi::Token::FixedBytes(asset_id_bytes.into()), - ]) - }, - Asset::Erc20(addr) => ethabi::Token::Tuple(vec![ - ethabi::Token::Uint(1.into()), - ethabi::Token::FixedBytes(addr.to_fixed_bytes().into()), - ]), - } - } -} - -/// Represents the pricing structure for various hardware resources. -/// All prices are specified in USD/hr, calculated based on the average block time. -#[derive( - PartialEq, Eq, Default, Encode, Decode, RuntimeDebug, TypeInfo, Copy, Clone, MaxEncodedLen, -)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct PriceTargets { - /// Price per vCPU per hour - pub cpu: u64, - /// Price per MB of memory per hour - pub mem: u64, - /// Price per GB of HDD storage per hour - pub storage_hdd: u64, - /// Price per GB of SSD storage per hour - pub storage_ssd: u64, - /// Price per GB of NVMe storage per hour - pub storage_nvme: u64, -} - -impl PriceTargets { - /// Converts the struct to ethabi ParamType. - pub fn to_ethabi_param_type() -> ethabi::ParamType { - ethabi::ParamType::Tuple(vec![ - // Price per vCPU per hour - ethabi::ParamType::Uint(64), - // Price per MB of memory per hour - ethabi::ParamType::Uint(64), - // Price per GB of HDD storage per hour - ethabi::ParamType::Uint(64), - // Price per GB of SSD storage per hour - ethabi::ParamType::Uint(64), - // Price per GB of NVMe storage per hour - ethabi::ParamType::Uint(64), - ]) - } - - /// Converts the struct to ethabi Param. - pub fn to_ethabi_param() -> ethabi::Param { - ethabi::Param { - name: String::from("priceTargets"), - kind: Self::to_ethabi_param_type(), - internal_type: Some(String::from("struct ServiceOperators.PriceTargets")), - } - } - - /// Converts the struct to ethabi Token. - pub fn to_ethabi(&self) -> ethabi::Token { - ethabi::Token::Tuple(vec![ - ethabi::Token::Uint(self.cpu.into()), - ethabi::Token::Uint(self.mem.into()), - ethabi::Token::Uint(self.storage_hdd.into()), - ethabi::Token::Uint(self.storage_ssd.into()), - ethabi::Token::Uint(self.storage_nvme.into()), - ]) - } -} - -#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Copy, Clone, MaxEncodedLen)] -pub struct OperatorPreferences { - /// The operator ECDSA public key. - pub key: [u8; 65], - /// The pricing targets for the operator's resources. - pub price_targets: PriceTargets, -} - -#[cfg(feature = "std")] -impl Serialize for OperatorPreferences { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeTuple; - let mut tup = serializer.serialize_tuple(2)?; - tup.serialize_element(&self.key[..])?; - tup.serialize_element(&self.price_targets)?; - tup.end() - } -} - -#[cfg(feature = "std")] -struct OperatorPreferencesVisitor; - -#[cfg(feature = "std")] -impl<'de> serde::de::Visitor<'de> for OperatorPreferencesVisitor { - type Value = OperatorPreferences; - - fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { - formatter.write_str("a tuple of 2 elements") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let key = seq - .next_element::>()? - .ok_or_else(|| serde::de::Error::custom("key is missing"))?; - let price_targets = seq - .next_element::()? - .ok_or_else(|| serde::de::Error::custom("price_targets is missing"))?; - let key_arr: [u8; 65] = key.try_into().map_err(|_| { - serde::de::Error::custom( - "key must be in the uncompressed format with length of 65 bytes", - ) - })?; - Ok(OperatorPreferences { key: key_arr, price_targets }) - } -} - -#[cfg(feature = "std")] -impl<'de> Deserialize<'de> for OperatorPreferences { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_tuple(2, OperatorPreferencesVisitor) - } -} - -impl OperatorPreferences { - /// Returns the ethabi ParamType for OperatorPreferences. - pub fn to_ethabi_param_type() -> ethabi::ParamType { - ethabi::ParamType::Tuple(vec![ - // Operator's ECDSA Public Key (33 bytes) - ethabi::ParamType::Bytes, - // Operator's price targets - PriceTargets::to_ethabi_param_type(), - ]) - } - /// Returns the ethabi Param for OperatorPreferences. - pub fn to_ethabi_param() -> ethabi::Param { - ethabi::Param { - name: String::from("operatorPreferences"), - kind: Self::to_ethabi_param_type(), - internal_type: Some(String::from( - "struct IBlueprintServiceManager.OperatorPreferences", - )), - } - } - - /// Encode the fields to ethabi bytes. - pub fn to_ethabi(&self) -> ethabi::Token { - ethabi::Token::Tuple(vec![ - // operator public key - ethabi::Token::Bytes(self.key.to_vec()), - // price targets - self.price_targets.to_ethabi(), - ]) - } -} - -/// Operator Profile is a profile of an operator that -/// contains metadata about the services that the operator is providing. -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Default(bound()), Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct OperatorProfile { - /// The Service IDs that I'm currently providing. - pub services: BoundedBTreeSet, - /// The Blueprint IDs that I'm currently registered for. - pub blueprints: BoundedBTreeSet, -} - -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub enum Gadget { - /// A Gadget that is a WASM binary that will be executed. - /// inside the shell using the wasm runtime. - Wasm(WasmGadget), - /// A Gadget that is a native binary that will be executed. - /// inside the shell using the OS. - Native(NativeGadget), - /// A Gadget that is a container that will be executed. - /// inside the shell using the container runtime (e.g. Docker, Podman, etc.) - Container(ContainerGadget), -} - -impl Default for Gadget { - fn default() -> Self { - Gadget::Wasm(WasmGadget { runtime: WasmRuntime::Wasmtime, sources: Default::default() }) - } -} - -/// A binary that is stored in the Github release. -/// this will constuct the URL to the release and download the binary. -/// The URL will be in the following format: -/// https://github.com///releases/download/v/ -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct GithubFetcher { - /// The owner of the repository. - pub owner: BoundedString, - /// The repository name. - pub repo: BoundedString, - /// The release tag of the repository. - /// NOTE: The tag should be a valid semver tag. - pub tag: BoundedString, - /// The names of the binary in the release by the arch and the os. - pub binaries: BoundedVec, C::MaxBinariesPerGadget>, -} - -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct TestFetcher { - /// The cargo package name that contains the blueprint logic - pub cargo_package: BoundedString, - /// The specific binary name that contains the blueprint logic. - /// Should match up what is in the Cargo.toml file under [[bin]]/name - pub cargo_bin: BoundedString, - /// The base path to the workspace/crate - pub base_path: BoundedString, -} - -/// The CPU or System architecture. -#[derive( - PartialEq, - PartialOrd, - Ord, - Eq, - Encode, - Decode, - RuntimeDebug, - TypeInfo, - Clone, - Copy, - MaxEncodedLen, -)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum Architecture { - /// WebAssembly architecture (32-bit). - #[codec(index = 0)] - Wasm, - /// WebAssembly architecture (64-bit). - #[codec(index = 1)] - Wasm64, - /// WASI architecture (32-bit). - #[codec(index = 2)] - Wasi, - /// WASI architecture (64-bit). - #[codec(index = 3)] - Wasi64, - /// Amd architecture (32-bit). - #[codec(index = 4)] - Amd, - /// Amd64 architecture (x86_64). - #[codec(index = 5)] - Amd64, - /// Arm architecture (32-bit). - #[codec(index = 6)] - Arm, - /// Arm64 architecture (64-bit). - #[codec(index = 7)] - Arm64, - /// Risc-V architecture (32-bit). - #[codec(index = 8)] - RiscV, - /// Risc-V architecture (64-bit). - #[codec(index = 9)] - RiscV64, -} - -/// Operating System that the binary is compiled for. -#[derive( - Default, - PartialEq, - PartialOrd, - Ord, - Eq, - Encode, - Decode, - RuntimeDebug, - TypeInfo, - Clone, - Copy, - MaxEncodedLen, -)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub enum OperatingSystem { - /// Unknown operating system. - /// This is used when the operating system is not known - /// for example, for WASM, where the OS is not relevant. - #[default] - #[codec(index = 0)] - Unknown, - /// Linux operating system. - #[codec(index = 1)] - Linux, - /// Windows operating system. - #[codec(index = 2)] - Windows, - /// MacOS operating system. - #[codec(index = 3)] - MacOS, - /// BSD operating system. - #[codec(index = 4)] - BSD, -} - -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct GadgetBinary { - /// CPU or System architecture. - pub arch: Architecture, - /// Operating System that the binary is compiled for. - pub os: OperatingSystem, - /// The name of the binary. - pub name: BoundedString, - /// The sha256 hash of the binary. - /// used to verify the downloaded binary. - pub sha256: [u8; 32], -} - -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct GadgetSource { - /// The fetcher that will fetch the gadget from a remote source. - fetcher: GadgetSourceFetcher, -} - -/// A Gadget Source Fetcher is a fetcher that will fetch the gadget -/// from a remote source. -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub enum GadgetSourceFetcher { - /// A Gadget that will be fetched from the IPFS. - #[codec(index = 0)] - IPFS(BoundedVec), - /// A Gadget that will be fetched from the Github release. - #[codec(index = 1)] - Github(GithubFetcher), - /// A Gadgets that will be fetched from the container registry. - #[codec(index = 2)] - ContainerImage(ImageRegistryFetcher), - /// For tests only - #[codec(index = 3)] - Testing(TestFetcher), -} - -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct ImageRegistryFetcher { - /// The URL of the container registry. - registry: BoundedString, - /// The name of the image. - image: BoundedString, - /// The tag of the image. - tag: BoundedString, -} - -/// A WASM binary that contains all the compiled gadget code. -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct WasmGadget { - /// Which runtime to use to execute the WASM binary. - pub runtime: WasmRuntime, - /// Where the WASM binary is stored. - pub sources: BoundedVec, C::MaxSourcesPerGadget>, -} - -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub enum WasmRuntime { - /// The WASM binary will be executed using the WASMtime runtime. - #[codec(index = 0)] - Wasmtime, - /// The WASM binary will be executed using the Wasmer runtime. - #[codec(index = 1)] - Wasmer, -} - -/// A Native binary that contains all the gadget code. -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct NativeGadget { - /// Where the WASM binary is stored. - pub sources: BoundedVec, C::MaxSourcesPerGadget>, -} - -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe(Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] -pub struct ContainerGadget { - /// Where the Image of the gadget binary is stored. - pub sources: BoundedVec, C::MaxSourcesPerGadget>, -} - -// -***- RPC -***- - -/// RPC Response for query the blueprint along with the services instances of that blueprint. -#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[educe( - Default(bound(AccountId: Default, BlockNumber: Default, AssetId: Default)), - Clone(bound(AccountId: Clone, BlockNumber: Clone, AssetId: Clone)), - PartialEq(bound(AccountId: PartialEq, BlockNumber: PartialEq, AssetId: PartialEq)), - Eq -)] -#[scale_info(skip_type_params(C))] -#[codec(encode_bound(skip_type_params(C)))] -#[codec(decode_bound(skip_type_params(C)))] -#[codec(mel_bound(skip_type_params(C)))] -#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] -#[cfg_attr( - feature = "std", - derive(Serialize, Deserialize), - serde(bound( - serialize = "AccountId: Serialize, BlockNumber: Serialize, AssetId: Serialize", - deserialize = "AccountId: Deserialize<'de>, BlockNumber: Deserialize<'de>, AssetId: Deserialize<'de>", - )), - educe(Debug(bound(AccountId: core::fmt::Debug, BlockNumber: core::fmt::Debug, AssetId: core::fmt::Debug))) -)] -pub struct RpcServicesWithBlueprint { - /// The blueprint ID. - pub blueprint_id: u64, - /// The service blueprint. - pub blueprint: ServiceBlueprint, - /// The services instances of that blueprint. - pub services: Vec>, -} - -#[derive(Debug)] -pub struct RunnerError> { - pub error: E, - pub weight: Weight, -} - -#[allow(clippy::too_many_arguments)] -pub trait EvmRunner { - type Error: Into; - - fn call( - source: H160, - target: H160, - input: Vec, - value: U256, - gas_limit: u64, - is_transactional: bool, - validate: bool, - ) -> Result>; -} - -/// A mapping function that converts EVM gas to Substrate weight and vice versa -pub trait EvmGasWeightMapping { - /// Convert EVM gas to Substrate weight - fn gas_to_weight(gas: u64, without_base_weight: bool) -> Weight; - /// Convert Substrate weight to EVM gas - fn weight_to_gas(weight: Weight) -> u64; -} - -impl EvmGasWeightMapping for () { - fn gas_to_weight(_gas: u64, _without_base_weight: bool) -> Weight { - Default::default() - } - fn weight_to_gas(_weight: Weight) -> u64 { - Default::default() - } -} - -/// Trait to be implemented for evm address mapping. -pub trait EvmAddressMapping { - /// Convert an address to an account id. - fn into_account_id(address: H160) -> A; - - /// Convert an account id to an address. - fn into_address(account_id: A) -> H160; -} +pub use constraints::*; +pub use evm::*; +pub use field::*; +pub use gadget::*; +pub use jobs::*; +pub use service::*; +pub use types::*; diff --git a/primitives/src/services/service.rs b/primitives/src/services/service.rs new file mode 100644 index 000000000..09dfe0d96 --- /dev/null +++ b/primitives/src/services/service.rs @@ -0,0 +1,506 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::{ + constraints::Constraints, + jobs::{type_checker, JobDefinition}, + types::{ApprovalState, Asset, MembershipModel}, + AssetIdT, AssetSecurityCommitment, AssetSecurityRequirement, BoundedString, Gadget, + MembershipModelType, TypeCheckError, +}; +use crate::{Account, BlueprintId}; +use educe::Educe; +use frame_support::pallet_prelude::*; +use sp_core::H160; +use sp_std::{vec, vec::Vec}; + +#[cfg(not(feature = "std"))] +use alloc::string::String; +#[cfg(feature = "std")] +use std::string::String; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +use super::field::{Field, FieldType}; + +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Default(bound()), Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize), serde(bound = ""))] +pub struct ServiceMetadata { + /// The Service name. + pub name: BoundedString, + /// The Service description. + pub description: Option>, + /// The Service author. + /// Could be a company or a person. + pub author: Option>, + /// The Job category. + pub category: Option>, + /// Code Repository URL. + /// Could be a github, gitlab, or any other code repository. + pub code_repository: Option>, + /// Service Logo URL. + pub logo: Option>, + /// Service Website URL. + pub website: Option>, + /// Service License. + pub license: Option>, +} + +/// Blueprint Service Manager is a smart contract that will manage the service lifecycle. +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Clone, Copy, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[non_exhaustive] +pub enum BlueprintServiceManager { + /// A Smart contract that will manage the service lifecycle. + Evm(H160), +} + +impl BlueprintServiceManager { + pub fn try_into_evm(self) -> Result { + match self { + Self::Evm(addr) => Ok(addr), + } + } +} + +impl Default for BlueprintServiceManager { + fn default() -> Self { + Self::Evm(Default::default()) + } +} + +/// Master Blueprint Service Manager Revision. +#[derive( + Default, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Clone, Copy, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[non_exhaustive] +pub enum MasterBlueprintServiceManagerRevision { + /// Use Whatever the latest revision available on-chain. + /// + /// This is the default value. + #[default] + #[codec(index = 0)] + Latest, + + /// Use a specific revision number. + /// + /// Note: Must be already deployed on-chain. + #[codec(index = 1)] + Specific(u32), +} + +/// A Service Blueprint is a the main definition of a service. +/// it contains the metadata of the service, the job definitions, and other hooks, along with the +/// gadget that will be executed when one of the jobs is calling this service. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Default(bound()), Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize), serde(bound = ""))] +pub struct ServiceBlueprint { + /// The metadata of the service. + pub metadata: ServiceMetadata, + /// The job definitions that are available in this service. + pub jobs: BoundedVec, C::MaxJobsPerService>, + /// The parameters that are required for the service registration. + pub registration_params: BoundedVec, + /// The request hook that will be called before creating a service from the service blueprint. + /// The parameters that are required for the service request. + pub request_params: BoundedVec, + /// A Blueprint Manager is a smart contract that implements the `IBlueprintServiceManager` + /// interface. + pub manager: BlueprintServiceManager, + /// The Revision number of the Master Blueprint Service Manager. + /// + /// If not sure what to use, use `MasterBlueprintServiceManagerRevision::default()` which will + /// use the latest revision available. + pub master_manager_revision: MasterBlueprintServiceManagerRevision, + /// The gadget that will be executed for the service. + pub gadget: Gadget, + /// The membership models supported by this blueprint + pub supported_membership_models: BoundedVec>, +} + +impl ServiceBlueprint { + /// Check if the supplied arguments match the registration parameters. + pub fn type_check_registration( + &self, + args: &[Field], + ) -> Result<(), TypeCheckError> { + type_checker(&self.registration_params, args) + } + + /// Check if the supplied arguments match the request parameters. + pub fn type_check_request( + &self, + args: &[Field], + ) -> Result<(), TypeCheckError> { + type_checker(&self.request_params, args) + } + + /// Converts the struct to ethabi ParamType. + pub fn to_ethabi_param_type() -> ethabi::ParamType { + ethabi::ParamType::Tuple(vec![ + // Service Metadata + ethabi::ParamType::Tuple(vec![ + // Service Name + ethabi::ParamType::String, + // Service Description + ethabi::ParamType::String, + // Service Author + ethabi::ParamType::String, + // Service Category + ethabi::ParamType::String, + // Code Repository + ethabi::ParamType::String, + // Service Logo + ethabi::ParamType::String, + // Service Website + ethabi::ParamType::String, + // Service License + ethabi::ParamType::String, + ]), + // Job Definitions ? + // Registration Parameters ? + // Request Parameters ? + // Blueprint Manager + ethabi::ParamType::Address, + // Master Manager Revision + ethabi::ParamType::Uint(32), + // Gadget ? + ]) + } + + /// Converts the struct to ethabi Param. + pub fn to_ethabi_param() -> ethabi::Param { + ethabi::Param { + name: String::from("blueprint"), + kind: Self::to_ethabi_param_type(), + internal_type: Some(String::from("struct MasterBlueprintServiceManager.Blueprint")), + } + } + + /// Converts the struct to ethabi Token. + pub fn to_ethabi(&self) -> ethabi::Token { + ethabi::Token::Tuple(vec![ + // Service Metadata + ethabi::Token::Tuple(vec![ + // Service Name + ethabi::Token::String(self.metadata.name.as_str().into()), + // Service Description + ethabi::Token::String( + self.metadata + .description + .as_ref() + .map(|v| v.as_str().into()) + .unwrap_or_default(), + ), + // Service Author + ethabi::Token::String( + self.metadata.author.as_ref().map(|v| v.as_str().into()).unwrap_or_default(), + ), + // Service Category + ethabi::Token::String( + self.metadata.category.as_ref().map(|v| v.as_str().into()).unwrap_or_default(), + ), + // Code Repository + ethabi::Token::String( + self.metadata + .code_repository + .as_ref() + .map(|v| v.as_str().into()) + .unwrap_or_default(), + ), + // Service Logo + ethabi::Token::String( + self.metadata.logo.as_ref().map(|v| v.as_str().into()).unwrap_or_default(), + ), + // Service Website + ethabi::Token::String( + self.metadata.website.as_ref().map(|v| v.as_str().into()).unwrap_or_default(), + ), + // Service License + ethabi::Token::String( + self.metadata.license.as_ref().map(|v| v.as_str().into()).unwrap_or_default(), + ), + ]), + // Job Definitions ? + // Registration Parameters ? + // Request Parameters ? + // Blueprint Manager + match self.manager { + BlueprintServiceManager::Evm(addr) => ethabi::Token::Address(addr), + }, + // Master Manager Revision + match self.master_manager_revision { + MasterBlueprintServiceManagerRevision::Latest => { + ethabi::Token::Uint(ethabi::Uint::MAX) + }, + MasterBlueprintServiceManagerRevision::Specific(rev) => { + ethabi::Token::Uint(rev.into()) + }, + }, + // Gadget ? + ]) + } +} + +/// Represents a request for service with specific security requirements for each asset. +/// The security requirements define the minimum and maximum exposure percentages that +/// operators must commit to be eligible for the service. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe( + Default(bound(AccountId: Default, BlockNumber: Default, AssetId: Default)), + Clone(bound(AccountId: Clone, BlockNumber: Clone, AssetId: Clone)), + PartialEq(bound(AccountId: PartialEq, BlockNumber: PartialEq, AssetId: PartialEq)), + Eq +)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] +#[cfg_attr( + feature = "std", + derive(serde::Serialize, serde::Deserialize), + serde(bound( + serialize = "AccountId: Serialize, BlockNumber: Serialize, AssetId: Serialize", + deserialize = "AccountId: Deserialize<'de>, BlockNumber: Deserialize<'de>, AssetId: AssetIdT" + )), + educe(Debug(bound(AccountId: core::fmt::Debug, BlockNumber: core::fmt::Debug, AssetId: AssetIdT))) +)] + +pub struct ServiceRequest { + /// The blueprint ID this request is for + pub blueprint: BlueprintId, + /// The account that requested the service + pub owner: AccountId, + /// The assets required for this service along with their security requirements. + /// This defines both which assets are needed and how much security backing is required. + pub security_requirements: + BoundedVec, C::MaxAssetsPerService>, + /// Time-to-live for this request in blocks + pub ttl: BlockNumber, + /// Arguments for service initialization + pub args: BoundedVec, C::MaxFields>, + /// Accounts permitted to call service functions + pub permitted_callers: BoundedVec, + /// Operators and their approval states + pub operators_with_approval_state: + BoundedVec<(AccountId, ApprovalState), C::MaxOperatorsPerService>, + /// The membership model to use for this service instance + pub membership_model: MembershipModel, +} + +impl + ServiceRequest +{ + /// Returns true if all the operators are [ApprovalState::Approved]. + pub fn is_approved(&self) -> bool { + let approved_count = self + .operators_with_approval_state + .iter() + .filter(|(_, state)| matches!(state, ApprovalState::Approved { .. })) + .count(); + + match self.membership_model { + MembershipModel::Fixed { min_operators } => approved_count >= min_operators as usize, + MembershipModel::Dynamic { min_operators, max_operators: _ } => { + approved_count >= min_operators as usize + }, + } + } + + /// Returns true if any the operators are [ApprovalState::Pending]. + pub fn is_pending(&self) -> bool { + self.operators_with_approval_state + .iter() + .any(|(_, state)| state == &ApprovalState::Pending) + } + + /// Returns true if any the operators are [ApprovalState::Rejected]. + pub fn is_rejected(&self) -> bool { + self.operators_with_approval_state + .iter() + .any(|(_, state)| state == &ApprovalState::Rejected) + } + + /// Validates that an operator's security commitments meet the requirements + pub fn validate_security_commitments( + &self, + security_commitments: &[AssetSecurityCommitment], + ) -> bool + where + AssetId: PartialEq, + { + validate_security(&self.security_requirements, security_commitments) + } +} + +pub fn validate_security( + security_requirements: &[AssetSecurityRequirement], + asset_commitments: &[AssetSecurityCommitment], +) -> bool { + // Validate that all security requirements are met by commitments in the same order + // For each requirement: + // - Check that the commitment at the same index has matching asset and exposure + // - Return false if arrays have different lengths or any requirements not met + if security_requirements.len() != asset_commitments.len() { + return false; + } + + security_requirements.iter().enumerate().all(|(i, req)| { + let commit = &asset_commitments[i]; + // Check asset matches and exposure percent is within bounds + commit.asset == req.asset + && commit.exposure_percent >= req.min_exposure_percent + && commit.exposure_percent <= req.max_exposure_percent + }) +} + +/// A staging service payment is a payment that is made for a service request +/// but will be paid when the service is created or refunded if the service is rejected. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen, Copy)] +#[educe( + Default(bound(AccountId: Default, Balance: Default, AssetId: Default)), + Clone(bound(AccountId: Clone, Balance: Clone, AssetId: Clone)), + PartialEq(bound(AccountId: PartialEq, Balance: PartialEq, AssetId: PartialEq)), + Eq +)] +#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] +#[cfg_attr( + feature = "std", + derive(serde::Serialize, serde::Deserialize), + serde(bound( + serialize = "AccountId: Serialize, Balance: Serialize, AssetId: Serialize", + deserialize = "AccountId: Deserialize<'de>, Balance: Deserialize<'de>, AssetId: AssetIdT", + )), + educe(Debug(bound(AccountId: core::fmt::Debug, Balance: core::fmt::Debug, AssetId: AssetIdT))) +)] +pub struct StagingServicePayment { + /// The service request ID. + pub request_id: u64, + /// Where the refund should go. + pub refund_to: Account, + /// The Asset used in the payment. + pub asset: Asset, + /// The amount of the asset that is paid. + pub amount: Balance, +} + +/// Type alias for asset security commitments per operator +pub type OperatorAssetCommitments = + BoundedVec, ::MaxAssetsPerService>; + +/// Type alias for operator security commitments +pub type OperatorSecurityCommitments = BoundedVec< + (AccountId, OperatorAssetCommitments), + ::MaxOperatorsPerService, +>; + +/// A Service is an instance of a service blueprint. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe( + Default(bound(AccountId: Default, BlockNumber: Default, AssetId: Default)), + Clone(bound(AccountId: Clone, BlockNumber: Clone, AssetId: Clone)), + PartialEq(bound(AccountId: PartialEq, BlockNumber: PartialEq, AssetId: PartialEq)), + Eq +)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] +#[cfg_attr( + feature = "std", + derive(serde::Serialize, serde::Deserialize), + serde(bound( + serialize = "AccountId: Serialize, BlockNumber: Serialize, AssetId: Serialize", + deserialize = "AccountId: Deserialize<'de>, BlockNumber: Deserialize<'de>, AssetId: AssetIdT", + )), + educe(Debug(bound(AccountId: core::fmt::Debug, BlockNumber: core::fmt::Debug, AssetId: AssetIdT))) +)] +pub struct Service { + /// Unique identifier for this service instance + pub id: u64, + /// The blueprint this service was created from + pub blueprint: BlueprintId, + /// The account that owns this service + pub owner: AccountId, + /// The assets and their security commitments from operators. + /// This represents the actual security backing the service. + pub operator_security_commitments: OperatorSecurityCommitments, + /// The security requirements for the service + pub security_requirements: + BoundedVec, C::MaxAssetsPerService>, + /// Accounts permitted to call service functions + pub permitted_callers: BoundedVec, + /// Time-to-live in blocks + pub ttl: BlockNumber, + /// The membership model of the service + pub membership_model: MembershipModel, +} + +impl + Service +{ + pub fn validate_security_commitments( + &self, + security_commitments: &[AssetSecurityCommitment], + ) -> bool { + validate_security(&self.security_requirements, security_commitments) + } +} + +/// RPC Response for query the blueprint along with the services instances of that blueprint. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe( + Default(bound(AccountId: Default, BlockNumber: Default, AssetId: Default)), + Clone(bound(AccountId: Clone, BlockNumber: Clone, AssetId: Clone)), + PartialEq(bound(AccountId: PartialEq, BlockNumber: PartialEq, AssetId: PartialEq)), + Eq +)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] +#[cfg_attr( + feature = "std", + derive(serde::Serialize, serde::Deserialize), + serde(bound( + serialize = "AccountId: Serialize, BlockNumber: Serialize, AssetId: Serialize", + deserialize = "AccountId: Deserialize<'de>, BlockNumber: Deserialize<'de>, AssetId: AssetIdT", + )), + educe(Debug(bound(AccountId: core::fmt::Debug, BlockNumber: core::fmt::Debug, AssetId: core::fmt::Debug))) +)] +pub struct RpcServicesWithBlueprint { + /// The blueprint ID. + pub blueprint_id: u64, + /// The service blueprint. + pub blueprint: ServiceBlueprint, + /// The services instances of that blueprint. + pub services: Vec>, +} diff --git a/primitives/src/services/types.rs b/primitives/src/services/types.rs new file mode 100644 index 000000000..6563d477c --- /dev/null +++ b/primitives/src/services/types.rs @@ -0,0 +1,454 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use educe::Educe; +use frame_support::pallet_prelude::*; +#[cfg(feature = "std")] +use serde::{Deserialize, Deserializer, Serialize}; +use sp_core::{RuntimeDebug, H160}; +use sp_runtime::{traits::AtLeast32BitUnsigned, Percent}; +use sp_staking::EraIndex; +use sp_std::fmt::Display; + +#[cfg(not(feature = "std"))] +use alloc::{string::String, vec, vec::Vec}; + +use super::{field::FieldType, Constraints}; + +/// An error that can occur during type checking. +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Clone, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum TypeCheckError { + /// The argument type does not match the expected type. + ArgumentTypeMismatch { + /// The index of the argument. + index: u8, + /// The expected type. + expected: FieldType, + /// The actual type. + actual: FieldType, + }, + /// Not enough arguments were supplied. + NotEnoughArguments { + /// The number of arguments that were expected. + expected: u8, + /// The number of arguments that were supplied. + actual: u8, + }, + /// The result type does not match the expected type. + ResultTypeMismatch { + /// The index of the argument. + index: u8, + /// The expected type. + expected: FieldType, + /// The actual type. + actual: FieldType, + }, +} + +impl frame_support::traits::PalletError for TypeCheckError { + const MAX_ENCODED_SIZE: usize = 2; +} + +/// Different types of assets that can be used. +#[derive( + PartialEq, + Eq, + Encode, + Decode, + RuntimeDebug, + TypeInfo, + Copy, + Clone, + MaxEncodedLen, + Ord, + PartialOrd, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum Asset { + /// Use the specified AssetId. + #[codec(index = 0)] + Custom(AssetId), + + /// Use an ERC20-like token with the specified contract address. + #[codec(index = 1)] + Erc20(H160), +} + +impl Default for Asset { + fn default() -> Self { + Asset::Custom(AssetId::default()) + } +} + +impl Asset { + pub fn is_erc20(&self) -> bool { + matches!(self, Asset::Erc20(_)) + } + + pub fn is_native(&self) -> bool { + if let Asset::Custom(asset_id) = self { + asset_id == &AssetId::default() + } else { + false + } + } + + pub fn to_ethabi_param_type() -> ethabi::ParamType { + ethabi::ParamType::Tuple(vec![ + // Kind of the Asset + ethabi::ParamType::Uint(8), + // Data of the Asset (Contract Address or AssetId) + ethabi::ParamType::FixedBytes(32), + ]) + } + + pub fn to_ethabi_param() -> ethabi::Param { + ethabi::Param { + name: String::from("asset"), + kind: Self::to_ethabi_param_type(), + internal_type: Some(String::from("struct ServiceOperators.Asset")), + } + } + + pub fn to_ethabi(&self) -> ethabi::Token { + match self { + Asset::Custom(asset_id) => { + let asset_id = asset_id.using_encoded(ethabi::Uint::from_little_endian); + let mut asset_id_bytes = [0u8; core::mem::size_of::()]; + asset_id.to_big_endian(&mut asset_id_bytes); + ethabi::Token::Tuple(vec![ + ethabi::Token::Uint(0.into()), + ethabi::Token::FixedBytes(asset_id_bytes.into()), + ]) + }, + Asset::Erc20(addr) => { + let mut addr_bytes = [0u8; 32]; + addr_bytes[12..].copy_from_slice(addr.as_fixed_bytes()); + ethabi::Token::Tuple(vec![ + ethabi::Token::Uint(1.into()), + ethabi::Token::FixedBytes(addr_bytes.into()), + ]) + }, + } + } +} + +/// Represents the pricing structure for various hardware resources. +/// All prices are specified in USD/hr, calculated based on the average block time. +#[derive( + PartialEq, Eq, Default, Encode, Decode, RuntimeDebug, TypeInfo, Copy, Clone, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct PriceTargets { + /// Price per vCPU per hour + pub cpu: u64, + /// Price per MB of memory per hour + pub mem: u64, + /// Price per GB of HDD storage per hour + pub storage_hdd: u64, + /// Price per GB of SSD storage per hour + pub storage_ssd: u64, + /// Price per GB of NVMe storage per hour + pub storage_nvme: u64, +} + +impl PriceTargets { + /// Converts the struct to ethabi ParamType. + pub fn to_ethabi_param_type() -> ethabi::ParamType { + ethabi::ParamType::Tuple(vec![ + // Price per vCPU per hour + ethabi::ParamType::Uint(64), + // Price per MB of memory per hour + ethabi::ParamType::Uint(64), + // Price per GB of HDD storage per hour + ethabi::ParamType::Uint(64), + // Price per GB of SSD storage per hour + ethabi::ParamType::Uint(64), + // Price per GB of NVMe storage per hour + ethabi::ParamType::Uint(64), + ]) + } + + /// Converts the struct to ethabi Param. + pub fn to_ethabi_param() -> ethabi::Param { + ethabi::Param { + name: String::from("priceTargets"), + kind: Self::to_ethabi_param_type(), + internal_type: Some(String::from("struct ServiceOperators.PriceTargets")), + } + } + + /// Converts the struct to ethabi Token. + pub fn to_ethabi(&self) -> ethabi::Token { + ethabi::Token::Tuple(vec![ + ethabi::Token::Uint(self.cpu.into()), + ethabi::Token::Uint(self.mem.into()), + ethabi::Token::Uint(self.storage_hdd.into()), + ethabi::Token::Uint(self.storage_ssd.into()), + ethabi::Token::Uint(self.storage_nvme.into()), + ]) + } +} + +/// Trait for asset identifiers +pub trait AssetIdT: + Default + + Clone + + Parameter + + Member + + PartialEq + + Eq + + PartialOrd + + Ord + + AtLeast32BitUnsigned + + parity_scale_codec::FullCodec + + MaxEncodedLen + + TypeInfo + + core::fmt::Debug + + MaybeSerializeDeserialize + + Display +{ +} + +impl AssetIdT for T where + T: Default + + Clone + + Parameter + + Member + + PartialEq + + Eq + + PartialOrd + + Ord + + AtLeast32BitUnsigned + + parity_scale_codec::FullCodec + + MaxEncodedLen + + TypeInfo + + core::fmt::Debug + + MaybeSerializeDeserialize + + Display +{ +} + +/// The approval state of an operator for a service request +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Clone(bound()), PartialEq(bound()), Eq, PartialOrd, Ord(bound()))] +#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] +#[cfg_attr( + feature = "std", + derive(serde::Serialize, serde::Deserialize), + serde(bound = ""), + educe(Debug(bound())) +)] +pub enum ApprovalState { + /// The operator has not yet responded to the request + Pending, + /// The operator has approved the request with specific asset commitments + Approved { + /// Asset-specific exposure commitments + security_commitments: Vec>, + }, + /// The operator has rejected the request + Rejected, +} + +/// Asset-specific security requirements for a service request +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Default(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] +#[cfg_attr( + feature = "std", + derive(serde::Serialize, serde::Deserialize), + serde(bound = ""), + educe(Debug(bound())) +)] +pub struct AssetSecurityRequirement { + /// The asset that needs to be secured + pub asset: Asset, + /// The minimum percentage of the asset that needs to be exposed for slashing + pub min_exposure_percent: Percent, + /// The maximum percentage of the asset that can be exposed for slashing + pub max_exposure_percent: Percent, +} + +/// Asset-specific security commitment from an operator +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Default(bound()), Clone(bound()), PartialEq(bound()), Eq, PartialOrd, Ord(bound()))] +#[cfg_attr(not(feature = "std"), derive(RuntimeDebugNoBound))] +#[cfg_attr( + feature = "std", + derive(serde::Serialize, serde::Deserialize), + serde(bound = ""), + educe(Debug(bound())) +)] +pub struct AssetSecurityCommitment { + /// The asset being secured + pub asset: Asset, + /// The percentage of the asset exposed for slashing + pub exposure_percent: Percent, +} + +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Copy, Clone, MaxEncodedLen)] +pub struct OperatorPreferences { + /// The operator ECDSA public key. + pub key: [u8; 65], + /// The pricing targets for the operator's resources. + pub price_targets: PriceTargets, +} + +#[cfg(feature = "std")] +impl Serialize for OperatorPreferences { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeTuple; + let mut tup = serializer.serialize_tuple(2)?; + tup.serialize_element(&self.key[..])?; + tup.serialize_element(&self.price_targets)?; + tup.end() + } +} + +#[cfg(feature = "std")] +struct OperatorPreferencesVisitor; + +#[cfg(feature = "std")] +impl<'de> serde::de::Visitor<'de> for OperatorPreferencesVisitor { + type Value = OperatorPreferences; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a tuple of 2 elements") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let key = seq + .next_element::>()? + .ok_or_else(|| serde::de::Error::custom("key is missing"))?; + let price_targets = seq + .next_element::()? + .ok_or_else(|| serde::de::Error::custom("price_targets is missing"))?; + let key_arr: [u8; 65] = key.try_into().map_err(|_| { + serde::de::Error::custom( + "key must be in the uncompressed format with length of 65 bytes", + ) + })?; + Ok(OperatorPreferences { key: key_arr, price_targets }) + } +} + +#[cfg(feature = "std")] +impl<'de> Deserialize<'de> for OperatorPreferences { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_tuple(2, OperatorPreferencesVisitor) + } +} + +impl OperatorPreferences { + /// Returns the ethabi ParamType for OperatorPreferences. + pub fn to_ethabi_param_type() -> ethabi::ParamType { + ethabi::ParamType::Tuple(vec![ + // Operator's ECDSA Public Key (33 bytes) + ethabi::ParamType::Bytes, + // Operator's price targets + PriceTargets::to_ethabi_param_type(), + ]) + } + /// Returns the ethabi Param for OperatorPreferences. + pub fn to_ethabi_param() -> ethabi::Param { + ethabi::Param { + name: String::from("operatorPreferences"), + kind: Self::to_ethabi_param_type(), + internal_type: Some(String::from( + "struct IBlueprintServiceManager.OperatorPreferences", + )), + } + } + + /// Encode the fields to ethabi bytes. + pub fn to_ethabi(&self) -> ethabi::Token { + ethabi::Token::Tuple(vec![ + // operator public key + ethabi::Token::Bytes(self.key.to_vec()), + // price targets + self.price_targets.to_ethabi(), + ]) + } +} + +/// Operator Profile is a profile of an operator that +/// contains metadata about the services that the operator is providing. +#[derive(Educe, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[educe(Default(bound()), Debug(bound()), Clone(bound()), PartialEq(bound()), Eq)] +#[scale_info(skip_type_params(C))] +#[codec(encode_bound(skip_type_params(C)))] +#[codec(decode_bound(skip_type_params(C)))] +#[codec(mel_bound(skip_type_params(C)))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub struct OperatorProfile { + /// The Service IDs that I'm currently providing. + pub services: BoundedBTreeSet, + /// The Blueprint IDs that I'm currently registered for. + pub blueprints: BoundedBTreeSet, +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub enum MembershipModelType { + /// Fixed set of operators defined at service creation + Fixed, + /// Operators can join/leave subject to blueprint rules + Dynamic, +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize), serde(bound = ""))] +pub enum MembershipModel { + /// Fixed set of operators defined at service creation + Fixed { min_operators: u32 }, + /// Operators can join/leave subject to blueprint rules + Dynamic { min_operators: u32, max_operators: Option }, +} + +impl Default for MembershipModel { + fn default() -> Self { + MembershipModel::Fixed { min_operators: 1 } + } +} + +/// A pending slash record. The value of the slash has been computed but not applied yet, +/// rather deferred for several eras. +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Clone, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[scale_info(skip_type_params(Balance))] +pub struct UnappliedSlash { + /// The era the slash was reported. + pub era: EraIndex, + /// The Blueprint Id of the service being slashed. + pub blueprint_id: u64, + /// The Service Instance Id on which the slash is applied. + pub service_id: u64, + /// The account ID of the offending operator. + pub operator: AccountId, + /// The slash percentage + pub slash_percent: Percent, +} diff --git a/primitives/src/traits/mod.rs b/primitives/src/traits/mod.rs index 0fb9c6b99..c32c09ca3 100644 --- a/primitives/src/traits/mod.rs +++ b/primitives/src/traits/mod.rs @@ -3,9 +3,11 @@ pub mod data_provider; pub mod multi_asset_delegation; pub mod rewards; pub mod services; +pub mod slash; pub use assets::*; pub use data_provider::*; pub use multi_asset_delegation::*; pub use rewards::*; pub use services::*; +pub use slash::*; diff --git a/primitives/src/traits/multi_asset_delegation.rs b/primitives/src/traits/multi_asset_delegation.rs index f9639e532..eed7491ee 100644 --- a/primitives/src/traits/multi_asset_delegation.rs +++ b/primitives/src/traits/multi_asset_delegation.rs @@ -14,9 +14,7 @@ use sp_std::prelude::*; /// * `AssetId`: The type representing an asset identifier. /// * `Balance`: The type representing a balance or amount. /// * `BlockNumber`: The type representing a block number. -pub trait MultiAssetDelegationInfo { - type AssetId; - +pub trait MultiAssetDelegationInfo { /// Get the current round index. /// /// This method returns the current round index, which may be used to track @@ -76,15 +74,12 @@ pub trait MultiAssetDelegationInfo { /// # Parameters /// /// * `operator`: A reference to the account identifier of the operator. - /// * `asset_id`: A reference to the asset identifier for which the total delegation amount is requested. + /// * `asset`: A reference to the asset identifier for which the total delegation amount is requested. /// /// # Returns /// /// The total delegation amount as a `Balance`. - fn get_total_delegation_by_asset_id( - operator: &AccountId, - asset_id: &Asset, - ) -> Balance; + fn get_total_delegation_by_asset(operator: &AccountId, asset_id: &Asset) -> Balance; /// Get all delegators for a specific operator. /// @@ -101,16 +96,25 @@ pub trait MultiAssetDelegationInfo { /// delegator account identifier, delegation amount, and asset identifier. fn get_delegators_for_operator( operator: &AccountId, - ) -> Vec<(AccountId, Balance, Asset)>; - - fn slash_operator( - operator: &AccountId, - blueprint_id: crate::BlueprintId, - percentage: sp_runtime::Percent, - ); + ) -> Vec<(AccountId, Balance, Asset)>; + /// Get a user's deposit and associated locks for a specific asset. + /// + /// This method retrieves information about a user's deposit for a given asset, + /// including both the unlocked amount and any time-locked portions. + /// + /// # Parameters + /// + /// * `who`: A reference to the account identifier of the user. + /// * `asset`: The asset identifier for which to get deposit information. + /// + /// # Returns + /// + /// An `Option` containing the user's deposit information if it exists: + /// - `Some(UserDepositWithLocks)` containing the unlocked amount and any time-locks + /// - `None` if no deposit exists for this user and asset fn get_user_deposit_with_locks( who: &AccountId, - asset_id: Asset, + asset: Asset, ) -> Option>; } diff --git a/primitives/src/traits/services.rs b/primitives/src/traits/services.rs index e550c188f..0c7a1a447 100644 --- a/primitives/src/traits/services.rs +++ b/primitives/src/traits/services.rs @@ -58,4 +58,18 @@ pub trait ServiceManager { /// /// `true` if the operator can exit, otherwise `false`. fn can_exit(operator: &AccountId) -> bool; + + /// Check if the operator has any active services. + /// + /// This method determines whether the specified operator has any active services + /// currently running. + /// + /// # Parameters + /// + /// * `operator`: A reference to the account identifier of the operator. + /// + /// # Returns + /// + /// `true` if the operator has active services, otherwise `false`. + fn has_active_services(operator: &AccountId) -> bool; } diff --git a/primitives/src/traits/slash.rs b/primitives/src/traits/slash.rs new file mode 100644 index 000000000..d0cbfc77c --- /dev/null +++ b/primitives/src/traits/slash.rs @@ -0,0 +1,38 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use crate::services::UnappliedSlash; +use frame_support::weights::Weight; +use sp_runtime::DispatchError; + +/// Trait for managing slashing in the Tangle network. +/// This trait provides functionality to slash operators and delegators. +pub trait SlashManager { + /// Slash an operator's stake for an offense. + /// + /// # Parameters + /// * `unapplied_slash` - The unapplied slash record containing slash details + fn slash_operator(unapplied_slash: &UnappliedSlash) + -> Result; +} + +impl SlashManager for () { + fn slash_operator( + _unapplied_slash: &UnappliedSlash, + ) -> Result { + Ok(Weight::zero()) + } +} diff --git a/primitives/src/types.rs b/primitives/src/types.rs index a78d88eab..a8779389f 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -19,7 +19,7 @@ pub mod rewards; use frame_support::pallet_prelude::*; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use sp_core::{ByteArray, RuntimeDebug}; +use sp_core::RuntimeDebug; use sp_runtime::{generic, AccountId32, OpaqueExtrinsic}; /// Block header type as expected by this runtime. @@ -59,6 +59,15 @@ pub type RoundIndex = u32; /// Blueprint ID pub type BlueprintId = u64; +/// Service request ID +pub type ServiceRequestId = u64; + +/// Service instance ID +pub type InstanceId = u64; + +/// Job call ID +pub type JobCallId = u64; + /// The address format for describing accounts. pub type Address = MultiAddress; @@ -99,16 +108,9 @@ pub enum Account { Address(sp_core::H160), } -impl Default for Account -where - AccountId: ByteArray, -{ +impl Default for Account { fn default() -> Self { - // This should be good enough to make the account for any account id type. - let empty = [0u8; 64]; - let account_id = - AccountId::from_slice(&empty[0..AccountId::LEN]).expect("never fails; qed"); - Account::Id(account_id) + Account::::Address(sp_core::H160([0u8; 20])) } } diff --git a/primitives/src/types/rewards.rs b/primitives/src/types/rewards.rs index 19912cb77..01a3dcb7d 100644 --- a/primitives/src/types/rewards.rs +++ b/primitives/src/types/rewards.rs @@ -1,11 +1,14 @@ use super::*; use crate::services::Asset; use frame_system::Config; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use services::AssetIdT; use sp_std::vec::Vec; /// Represents different types of rewards a user can earn #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Eq)] -pub struct UserRewards> { +pub struct UserRewards> { /// Rewards earned from restaking (in TNT) pub restaking_rewards: Balance, /// Boost rewards information @@ -15,15 +18,15 @@ pub struct UserRewards { +pub struct UserRestakeUpdate { pub asset: Asset, pub amount: Balance, pub multiplier: LockMultiplier, } #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Eq)] -pub struct ServiceRewards { - asset_id: Asset, +pub struct ServiceRewards { + asset: Asset, amount: Balance, } @@ -48,7 +51,7 @@ impl Default for BoostInfo> Default +impl> Default for UserRewards { fn default() -> Self { diff --git a/runtime/mainnet/src/lib.rs b/runtime/mainnet/src/lib.rs index 752eeb2f7..f6626ac2c 100644 --- a/runtime/mainnet/src/lib.rs +++ b/runtime/mainnet/src/lib.rs @@ -1296,8 +1296,11 @@ parameter_types! { impl pallet_multi_asset_delegation::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; + type SlashRecipient = TreasuryAccount; type MinOperatorBondAmount = MinOperatorBondAmount; type BondDuration = BondDuration; + type CurrencyToVote = U128CurrencyToVote; + type StakingInterface = Staking; type ServiceManager = Services; type LeaveOperatorsDelay = ConstU32<10>; type OperatorBondLessDelay = ConstU32<1>; @@ -1308,7 +1311,6 @@ impl pallet_multi_asset_delegation::Config for Runtime { type AssetId = AssetId; type ForceOrigin = frame_system::EnsureRoot; type PalletId = PID; - type SlashedAmountRecipient = TreasuryAccount; type MaxDelegatorBlueprints = MaxDelegatorBlueprints; type MaxOperatorBlueprints = MaxOperatorBlueprints; type MaxWithdrawRequests = MaxWithdrawRequests; diff --git a/runtime/mainnet/src/tangle_services.rs b/runtime/mainnet/src/tangle_services.rs index a6cbcf267..1d6bcc6d8 100644 --- a/runtime/mainnet/src/tangle_services.rs +++ b/runtime/mainnet/src/tangle_services.rs @@ -4,7 +4,7 @@ use pallet_evm::GasWeightMapping; use scale_info::TypeInfo; parameter_types! { - pub const ServicesEVMAddress: H160 = H160([0x11; 20]); + pub const ServicesPalletId: PalletId = PalletId(*b"Services"); } pub struct PalletEvmRunner; @@ -143,6 +143,16 @@ parameter_types! { #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Serialize, Deserialize)] pub const MaxMasterBlueprintServiceManagerVersions: u32 = u32::MAX; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Serialize, Deserialize)] + pub const MinimumNativeSecurityRequirement: Percent = Percent::from_percent(10); + + // Ripemd160(keccak256("ServicesPalletEvmAccount")) + pub const ServicesPalletEvmAccount: H160 = H160([ + 0x09, 0xdf, 0x6a, 0x94, 0x1e, 0xe0, 0x3b, 0x1e, + 0x63, 0x29, 0x04, 0xe3, 0x82, 0xe1, 0x08, 0x62, + 0xfa, 0x9c, 0xc0, 0xe3 + ]); } pub type PalletServicesConstraints = pallet_services::types::ConstraintsOf; @@ -152,7 +162,8 @@ impl pallet_services::Config for Runtime { type ForceOrigin = EnsureRootOrHalfCouncil; type Currency = Balances; type Fungibles = Assets; - type PalletEVMAddress = ServicesEVMAddress; + type PalletEvmAccount = ServicesPalletEvmAccount; + type SlashManager = (); type EvmRunner = PalletEvmRunner; type EvmGasWeightMapping = PalletEVMGasWeightMapping; type EvmAddressMapping = PalletEVMAddressMapping; @@ -178,6 +189,7 @@ impl pallet_services::Config for Runtime { type MaxContainerImageTagLength = MaxContainerImageTagLength; type MaxAssetsPerService = MaxAssetsPerService; type MaxMasterBlueprintServiceManagerVersions = MaxMasterBlueprintServiceManagerVersions; + type MinimumNativeSecurityRequirement = MinimumNativeSecurityRequirement; type Constraints = PalletServicesConstraints; type SlashDeferDuration = SlashDeferDuration; type MasterBlueprintServiceManagerUpdateOrigin = EnsureRootOrHalfCouncil; diff --git a/runtime/testnet/src/frontier_evm.rs b/runtime/testnet/src/frontier_evm.rs index 23fb1f1a3..2358248dc 100644 --- a/runtime/testnet/src/frontier_evm.rs +++ b/runtime/testnet/src/frontier_evm.rs @@ -96,7 +96,7 @@ impl OnChargeEVMTransaction for CustomEVMCurrencyAdapter { who: &H160, fee: U256, ) -> Result> { - let pallet_services_address = pallet_services::Pallet::::address(); + let pallet_services_address = pallet_services::Pallet::::pallet_evm_account(); // Make pallet services account free to use if who == &pallet_services_address { return Ok(None); @@ -113,7 +113,7 @@ impl OnChargeEVMTransaction for CustomEVMCurrencyAdapter { base_fee: U256, already_withdrawn: Self::LiquidityInfo, ) -> Self::LiquidityInfo { - let pallet_services_address = pallet_services::Pallet::::address(); + let pallet_services_address = pallet_services::Pallet::::pallet_evm_account(); // Make pallet services account free to use if who == &pallet_services_address { return already_withdrawn; diff --git a/runtime/testnet/src/lib.rs b/runtime/testnet/src/lib.rs index 0c35cbef8..16555a03c 100644 --- a/runtime/testnet/src/lib.rs +++ b/runtime/testnet/src/lib.rs @@ -832,7 +832,7 @@ where ); let raw_payload = SignedPayload::new(call, extra) .map_err(|e| { - log::warn!("Unable to create signed payload: {:?}", e); + log::warn!("Unable to create signed payload: {e:?}"); }) .ok()?; let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?; @@ -1564,8 +1564,11 @@ parameter_types! { impl pallet_multi_asset_delegation::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; + type SlashRecipient = TreasuryAccount; type MinOperatorBondAmount = MinOperatorBondAmount; type BondDuration = BondDuration; + type CurrencyToVote = U128CurrencyToVote; + type StakingInterface = Staking; type ServiceManager = Services; type LeaveOperatorsDelay = LeaveOperatorsDelay; type OperatorBondLessDelay = OperatorBondLessDelay; @@ -1574,7 +1577,6 @@ impl pallet_multi_asset_delegation::Config for Runtime { type MinDelegateAmount = MinDelegateAmount; type Fungibles = Assets; type AssetId = AssetId; - type SlashedAmountRecipient = TreasuryAccount; type ForceOrigin = frame_system::EnsureRoot; type PalletId = PID; type RewardsManager = Rewards; diff --git a/runtime/testnet/src/tangle_services.rs b/runtime/testnet/src/tangle_services.rs index a2beafd87..1df464dbe 100644 --- a/runtime/testnet/src/tangle_services.rs +++ b/runtime/testnet/src/tangle_services.rs @@ -1,7 +1,7 @@ use super::*; parameter_types! { - pub const ServicesEVMAddress: H160 = H160([0x11; 20]); + pub const ServicesPalletId: PalletId = PalletId(*b"Services"); } pub struct PalletEvmRunner; @@ -140,6 +140,16 @@ parameter_types! { #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Serialize, Deserialize)] pub const MaxMasterBlueprintServiceManagerVersions: u32 = u32::MAX; + + #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo, Serialize, Deserialize)] + pub const MinimumNativeSecurityRequirement: Percent = Percent::from_percent(10); + + // Ripemd160(keccak256("ServicesPalletEvmAccount")) + pub const ServicesPalletEvmAccount: H160 = H160([ + 0x09, 0xdf, 0x6a, 0x94, 0x1e, 0xe0, 0x3b, 0x1e, + 0x63, 0x29, 0x04, 0xe3, 0x82, 0xe1, 0x08, 0x62, + 0xfa, 0x9c, 0xc0, 0xe3 + ]); } pub type PalletServicesConstraints = pallet_services::types::ConstraintsOf; @@ -149,7 +159,8 @@ impl pallet_services::Config for Runtime { type ForceOrigin = EnsureRootOrHalfCouncil; type Currency = Balances; type Fungibles = Assets; - type PalletEVMAddress = ServicesEVMAddress; + type PalletEvmAccount = ServicesPalletEvmAccount; + type SlashManager = (); type EvmRunner = PalletEvmRunner; type EvmGasWeightMapping = PalletEVMGasWeightMapping; type EvmAddressMapping = PalletEVMAddressMapping; @@ -178,6 +189,7 @@ impl pallet_services::Config for Runtime { type SlashDeferDuration = SlashDeferDuration; type MaxMasterBlueprintServiceManagerVersions = MaxMasterBlueprintServiceManagerVersions; type MasterBlueprintServiceManagerUpdateOrigin = EnsureRootOrHalfCouncil; + type MinimumNativeSecurityRequirement = MinimumNativeSecurityRequirement; #[cfg(not(feature = "runtime-benchmarks"))] type OperatorDelegationManager = MultiAssetDelegation; #[cfg(feature = "runtime-benchmarks")]