diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..3c71b3b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,13 @@ +{ + "image": "ghcr.io/near/near-devcontainer:latest", + "customizations": { + "vscode": { + "extensions": [ + "dtsvet.vscode-wasm", + "rust-lang.rust-analyzer", + "github.vscode-github-actions" + ] + } + }, + "postCreateCommand": "./.devcontainer/post-create.sh" +} diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh new file mode 100755 index 0000000..e9d1ebb --- /dev/null +++ b/.devcontainer/post-create.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +(cd discussions && cargo near build) +(cd community && cargo near build) +(cd community-factory && cargo near build) +cargo near build diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d0f1e0d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +res/near_ideas.wasm binary \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..2706a0f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,26 @@ +name: CI +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +jobs: + rununittest: + name: Unit tests + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v1 + - name: Install cargo-near + run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/latest/download/cargo-near-installer.sh | sh + - name: Build discussions contract + run: cd discussions && cargo near build + - name: Build community contract + run: cd community && cargo near build + - name: Build community factory contract + run: cd community-factory && cargo near build + - name: Build devhub contract + run: cargo near build + - name: Unit tests + run: cargo test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3db7b0d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +neardev/ +target/ +res/*.wasm +.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9082446 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4709 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "actix" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba56612922b907719d4a01cf11c8d5b458e7d3dba946d0435f20f58d6795ed2" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags 2.4.1", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util 0.7.8", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.28", +] + +[[package]] +name = "actix-rt" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c7db3d5a9718568e4cf4a537cfd7070e6e6ff7481510d0237fb529ac850f6d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.10", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys 0.45.0", +] + +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "async-trait" +version = "0.1.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "binary-install" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93bff426ff93f3610dd2b946f3eb8cb2d1285ca8682834d43be531a3f93db2ff" +dependencies = [ + "anyhow", + "dirs-next", + "flate2", + "fs2", + "hex", + "is_executable", + "siphasher", + "tar", + "ureq", + "zip 0.6.6", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" +dependencies = [ + "crypto-mac", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive 0.9.3", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" +dependencies = [ + "borsh-derive 1.3.1", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" +dependencies = [ + "once_cell", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.28", + "syn_derive", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "brownstone" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030ea61398f34f1395ccbeb046fb68c87b631d1f34567fed0f0f11fa35d18d8d" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "bytesize" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38fcc2979eff34a4b84e1cf9a1e3da42a7d44b3b690a40cdcb23e3d556cfb2e5" +dependencies = [ + "serde", +] + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "c2-chacha" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27dae93fe7b1e0424dc57179ac396908c26b035a87234809f5c4dfd1b47dc80" +dependencies = [ + "cipher 0.2.5", + "ppv-lite86", +] + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-near" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f73eb01da3b6737778d2006645533e75563d1080c64bf714bfb88d3fb0ac09b" +dependencies = [ + "anyhow", + "atty", + "bs58 0.4.0", + "camino", + "cargo_metadata 0.14.2", + "clap 3.2.25", + "colored", + "env_logger", + "libloading", + "log", + "near-abi", + "rustc_version", + "schemars", + "serde_json", + "sha2", + "symbolic-debuginfo", + "zstd", +] + +[[package]] +name = "cargo-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.48.1", +] + +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_derive 3.2.25", + "clap_lex 0.2.4", + "indexmap 1.9.3", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6efb5f0a41b5ef5b50c5da28c07609c20091df0c1fc33d418fa2a7e693c2b624" +dependencies = [ + "clap_builder", + "clap_derive 4.2.0", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671fcaa5debda4b9a84aa7fde49c907c8986c0e6ab927e04217c9cb74e7c8bc9" +dependencies = [ + "anstream", + "anstyle", + "bitflags 1.3.2", + "clap_lex 0.4.1", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "concolor-override" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a4925288e39d5923e024781971aab940995fa31bab3ffceebbadfc87591e90" +dependencies = [ + "colorchoice", +] + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys 0.45.0", +] + +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "platforms", + "rand_core 0.6.4", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.28", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "debugid" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ee87af31d84ef885378aebca32be3d682b0e0dc119d5b4860a2c5bb5046730" +dependencies = [ + "uuid", +] + +[[package]] +name = "deranged" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "devhub" +version = "0.2.0" +dependencies = [ + "anyhow", + "devhub_common", + "html-escape", + "insta", + "near-contract-standards", + "near-sdk", + "near-workspaces", + "regex", + "serde_json", + "tokio", +] + +[[package]] +name = "devhub_common" +version = "0.1.0" +dependencies = [ + "near-sdk", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dmsort" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0bc8fbe9441c17c9f46f75dfe27fa1ddb6c68a461ccaed0481419219d4f10d3" + +[[package]] +name = "dyn-clone" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d2f3407d9a573d666de4b5bdf10569d73ca9478087346697dcbae6244bfbcd" + +[[package]] +name = "easy-ext" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53aff6fdc1b181225acdcb5b14c47106726fd8e486707315b1b138baed68ee31" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "sha2", + "subtle", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "elementtree" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6319c9433cf1e95c60c8533978bccf0614f27f03bb4e514253468eeeaa7fe3" +dependencies = [ + "string_cache", + "xml-rs", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + +[[package]] +name = "fiat-crypto" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" + +[[package]] +name = "filetime" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +dependencies = [ + "fallible-iterator", + "stable_deref_trait", +] + +[[package]] +name = "goblin" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7666983ed0dd8d21a6f6576ee00053ca0926fb281a5522577a4dbd0f1b54143" +dependencies = [ + "log", + "plain", + "scroll 0.11.0", +] + +[[package]] +name = "h2" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util 0.7.8", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indent_write" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "insta" +version = "1.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "pest", + "pest_derive", + "serde", + "similar", + "yaml-rust", +] + +[[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.2", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "is_executable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d553b8abc8187beb7d663e34c065ac4570b273bc9511a50e940e99409c577" +dependencies = [ + "winapi", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "joinery" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "treediff", +] + +[[package]] +name = "json_comments" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbbfed4e59ba9750e15ba154fdfd9329cee16ff3df539c2666b70f58cc32105" + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "near-abi" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885db39b08518fa700b73fa2214e8adbbfba316ba82dd510f50519173eadaf73" +dependencies = [ + "borsh 0.9.3", + "schemars", + "semver", + "serde", +] + +[[package]] +name = "near-account-id" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35cbb989542587b47205e608324ddd391f0cee1c22b4b64ae49f458334b95907" +dependencies = [ + "borsh 1.3.1", + "serde", +] + +[[package]] +name = "near-chain-configs" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e5a8ace81c09d7eb165dffc1742358a021b2fa761f2160943305f83216003" +dependencies = [ + "anyhow", + "bytesize", + "chrono", + "derive_more", + "near-config-utils", + "near-crypto", + "near-parameters", + "near-primitives", + "num-rational", + "once_cell", + "serde", + "serde_json", + "sha2", + "smart-default", + "tracing", +] + +[[package]] +name = "near-config-utils" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae1eaab1d545a9be7a55b6ef09f365c2017f93a03063547591d12c0c6d27e58" +dependencies = [ + "anyhow", + "json_comments", + "thiserror", + "tracing", +] + +[[package]] +name = "near-contract-standards" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0e78fe14d389ae76f792735a2108a6b8332a2bed61197310bf5ad718fb1e424" +dependencies = [ + "near-sdk", +] + +[[package]] +name = "near-crypto" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2991d2912218a80ec0733ac87f84fa803accea105611eea209d4419271957667" +dependencies = [ + "blake2", + "borsh 1.3.1", + "bs58 0.4.0", + "c2-chacha", + "curve25519-dalek", + "derive_more", + "ed25519-dalek", + "hex", + "near-account-id", + "near-config-utils", + "near-stdx", + "once_cell", + "primitive-types", + "rand 0.7.3", + "secp256k1", + "serde", + "serde_json", + "subtle", + "thiserror", +] + +[[package]] +name = "near-fmt" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d998dfc1e04001608899b2498ad5a782c7d036b73769d510de21964db99286" +dependencies = [ + "near-primitives-core", +] + +[[package]] +name = "near-gas" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e75c875026229902d065e4435804497337b631ec69ba746b102954273e9ad1" +dependencies = [ + "borsh 1.3.1", + "schemars", + "serde", +] + +[[package]] +name = "near-jsonrpc-client" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18ad81e015f7aced8925d5b9ba3f369b36da9575c15812cfd0786bc1213284ca" +dependencies = [ + "borsh 1.3.1", + "lazy_static", + "log", + "near-chain-configs", + "near-crypto", + "near-jsonrpc-primitives", + "near-primitives", + "reqwest", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "near-jsonrpc-primitives" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0ce745e954ae776eef05957602e638ee9581106a3675946fb43c2fe7e38ef03" +dependencies = [ + "arbitrary", + "near-chain-configs", + "near-crypto", + "near-primitives", + "near-rpc-error-macro", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "near-o11y" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d20762631bc8253030013bbae9b5f0542691edc1aa6722f1e8141cc9b928ae5b" +dependencies = [ + "actix", + "base64 0.21.2", + "clap 4.2.0", + "near-crypto", + "near-fmt", + "near-primitives-core", + "once_cell", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "prometheus", + "serde", + "serde_json", + "strum 0.24.1", + "thiserror", + "tokio", + "tracing", + "tracing-appender", + "tracing-opentelemetry", + "tracing-subscriber", +] + +[[package]] +name = "near-parameters" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f16a59b6c3e69b0585be951af6fe42a0ba86c0e207cb8c63badd19efd16680" +dependencies = [ + "assert_matches", + "borsh 1.3.1", + "enum-map", + "near-account-id", + "near-primitives-core", + "num-rational", + "serde", + "serde_repr", + "serde_yaml", + "strum 0.24.1", + "thiserror", +] + +[[package]] +name = "near-primitives" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0462b067732132babcc89d5577db3bfcb0a1bcfbaaed3f2db4c11cd033666314" +dependencies = [ + "arbitrary", + "base64 0.21.2", + "borsh 1.3.1", + "bytesize", + "cfg-if 1.0.0", + "chrono", + "derive_more", + "easy-ext", + "enum-map", + "hex", + "near-crypto", + "near-fmt", + "near-o11y", + "near-parameters", + "near-primitives-core", + "near-rpc-error-macro", + "near-stdx", + "near-vm-runner", + "num-rational", + "once_cell", + "primitive-types", + "rand 0.8.5", + "rand_chacha 0.3.1", + "reed-solomon-erasure", + "serde", + "serde_json", + "serde_with", + "serde_yaml", + "sha3", + "smart-default", + "strum 0.24.1", + "thiserror", + "time", + "tracing", +] + +[[package]] +name = "near-primitives-core" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8443eb718606f572c438be6321a097a8ebd69f8e48d953885b4f16601af88225" +dependencies = [ + "arbitrary", + "base64 0.21.2", + "borsh 1.3.1", + "bs58 0.4.0", + "derive_more", + "enum-map", + "near-account-id", + "num-rational", + "serde", + "serde_repr", + "serde_with", + "sha2", + "strum 0.24.1", + "thiserror", +] + +[[package]] +name = "near-rpc-error-core" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80fca203c51edd9595ec14db1d13359fb9ede32314990bf296b6c5c4502f6ab7" +dependencies = [ + "quote", + "serde", + "syn 2.0.28", +] + +[[package]] +name = "near-rpc-error-macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897a445de2102f6732c8a185d922f5e3bf7fd0a41243ce40854df2197237f799" +dependencies = [ + "fs2", + "near-rpc-error-core", + "serde", + "syn 2.0.28", +] + +[[package]] +name = "near-sandbox-utils" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2de216bb0152bfb64f59016d9e6a5b1ac56dd85f729e5fde08461571e2182c8f" +dependencies = [ + "anyhow", + "binary-install", + "chrono", + "fs2", + "home", + "tokio", +] + +[[package]] +name = "near-sdk" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520234cfdf04a805ac2f04715889d096eb83fdd5b99ca7d0f8027ae473f891a8" +dependencies = [ + "base64 0.21.2", + "borsh 1.3.1", + "bs58 0.5.0", + "near-account-id", + "near-crypto", + "near-gas", + "near-parameters", + "near-primitives", + "near-primitives-core", + "near-sdk-macros", + "near-sys", + "near-token", + "near-vm-runner", + "once_cell", + "serde", + "serde_json", + "wee_alloc", +] + +[[package]] +name = "near-sdk-macros" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2fe3fc30068c5f20e89b0985d6104c5cc1c6742dbc6efbf352be4189b9bbf7" +dependencies = [ + "Inflector", + "darling", + "proc-macro2", + "quote", + "serde", + "serde_json", + "strum 0.26.1", + "strum_macros 0.26.1", + "syn 2.0.28", +] + +[[package]] +name = "near-stdx" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "855fd5540e3b4ff6fedf12aba2db1ee4b371b36f465da1363a6d022b27cb43b8" + +[[package]] +name = "near-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397688591acf8d3ebf2c2485ba32d4b24fc10aad5334e3ad8ec0b7179bfdf06b" + +[[package]] +name = "near-token" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b68f3f8a2409f72b43efdbeff8e820b81e70824c49fee8572979d789d1683fb" +dependencies = [ + "borsh 1.3.1", + "serde", +] + +[[package]] +name = "near-vm-runner" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56c80bdb1954808f59bd36a9112377197b38d424991383bf05f52d0fe2e0da5" +dependencies = [ + "base64 0.21.2", + "borsh 1.3.1", + "ed25519-dalek", + "enum-map", + "memoffset", + "near-crypto", + "near-parameters", + "near-primitives-core", + "near-stdx", + "num-rational", + "once_cell", + "prefix-sum-vec", + "ripemd", + "serde", + "serde_repr", + "serde_with", + "sha2", + "sha3", + "strum 0.24.1", + "thiserror", + "tracing", + "zeropool-bn", +] + +[[package]] +name = "near-workspaces" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e597da87d0c1a722e23efb8c24ae42a0ad99a15f37101dad45c15defb051c1" +dependencies = [ + "async-trait", + "base64 0.21.2", + "bs58 0.5.0", + "cargo-near", + "cargo_metadata 0.18.1", + "chrono", + "fs2", + "json-patch", + "libc", + "near-account-id", + "near-crypto", + "near-gas", + "near-jsonrpc-client", + "near-jsonrpc-primitives", + "near-primitives", + "near-sandbox-utils", + "near-token", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "sha2", + "tempfile", + "thiserror", + "tokio", + "tokio-retry", + "tracing", + "url", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom-supreme" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aadc66631948f6b65da03be4c4cd8bd104d481697ecbb9bbd65719b1ec60bc9f" +dependencies = [ + "brownstone", + "indent_write", + "joinery", + "memchr", + "nom", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.2", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +dependencies = [ + "bitflags 1.3.2", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "opentelemetry" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "js-sys", + "lazy_static", + "percent-encoding", + "pin-project", + "rand 0.8.5", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1a6ca9de4c8b00aa7f1a153bd76cb263287155cec642680d79d98706f3d28a" +dependencies = [ + "async-trait", + "futures", + "futures-util", + "http", + "opentelemetry", + "prost", + "thiserror", + "tokio", + "tonic", + "tonic-build", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985cc35d832d412224b2cffe2f9194b1b89b6aa5d0bef76d080dce09d90e62bd" +dependencies = [ + "opentelemetry", +] + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets 0.48.1", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "pdb" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13f4d162ecaaa1467de5afbe62d597757b674b51da8bb4e587430c5fdb2af7aa" +dependencies = [ + "fallible-iterator", + "scroll 0.10.2", + "uuid", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pest" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "pest_meta" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.0.0", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "platforms" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prefix-sum-vec" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa06bd51638b6e76ac9ba9b6afb4164fa647bd2916d722f2623fbb6d1ed8bdba" + +[[package]] +name = "primitive-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" +dependencies = [ + "fixed-hash", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +dependencies = [ + "cfg-if 1.0.0", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +dependencies = [ + "bytes", + "heck 0.3.3", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "regex", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +dependencies = [ + "bytes", + "prost", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.10", + "redox_syscall 0.2.16", + "thiserror", +] + +[[package]] +name = "reed-solomon-erasure" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a415a013dd7c5d4221382329a5a3482566da675737494935cbbbcdec04662f9d" +dependencies = [ + "smallvec", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.3.4", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "reqwest" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +dependencies = [ + "base64 0.21.2", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom 0.2.10", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "schemars" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scroll" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec" + +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "rand 0.8.5", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +dependencies = [ + "indexmap 2.0.0", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +dependencies = [ + "base64 0.21.2", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.0.0", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "serde_yaml" +version = "0.9.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" +dependencies = [ + "indexmap 2.0.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" + +[[package]] +name = "similar" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "smart-default" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "strum_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.28", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "symbolic-common" +version = "8.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f551f902d5642e58039aee6a9021a61037926af96e071816361644983966f540" +dependencies = [ + "debugid", + "memmap2", + "stable_deref_trait", + "uuid", +] + +[[package]] +name = "symbolic-debuginfo" +version = "8.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165dabf9fc1d6bb6819c2c0e27c8dd0e3068d2c53cf186d319788e96517f0d6" +dependencies = [ + "bitvec", + "dmsort", + "elementtree", + "fallible-iterator", + "flate2", + "gimli", + "goblin", + "lazy_static", + "lazycell", + "nom", + "nom-supreme", + "parking_lot", + "pdb", + "regex", + "scroll 0.11.0", + "serde", + "serde_json", + "smallvec", + "symbolic-common", + "thiserror", + "wasmparser", + "zip 0.5.13", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tar" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96d2ffad078296368d46ff1cb309be1c23c513b4ab0e22a45de0185275ac96" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "termcolor" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "thiserror" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand 0.8.5", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.0.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tonic" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +dependencies = [ + "async-stream", + "async-trait", + "base64 0.13.1", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util 0.6.10", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +dependencies = [ + "proc-macro2", + "prost-build", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util 0.7.8", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbbe89715c1dbbb790059e2565353978564924ee85017b5fff365c872ff6721f" +dependencies = [ + "once_cell", + "opentelemetry", + "tracing", + "tracing-core", + "tracing-log 0.1.4", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log 0.2.0", +] + +[[package]] +name = "treediff" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" +dependencies = [ + "serde_json", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97" +dependencies = [ + "base64 0.21.2", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-webpki", + "url", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasmparser" +version = "0.83.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "xml-rs" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" + +[[package]] +name = "zeropool-bn" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e61de68ede9ffdd69c01664f65a178c5188b73f78faa21f0936016a888ff7c" +dependencies = [ + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.5", + "rustc-hex", +] + +[[package]] +name = "zip" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +dependencies = [ + "byteorder", + "crc32fast", + "flate2", + "thiserror", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bb0518e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "devhub" +version = "0.2.0" +authors = ["NEAR DevHub "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +near-sdk = { version = "5.1.0", features = ["unstable"] } +near-contract-standards = "5.1.0" +serde_json = { version = "1.0", features = ["preserve_order"] } +devhub_common = { path = "./devhub_common" } +html-escape = "0.2.13" + +[dev-dependencies] +near-sdk = { version = "5.1.0", features = ["unit-testing"] } +insta = { version = "1.31.0", features = ["json", "redactions"] } +regex = "1" +near-workspaces = { version = "0.10.0", features = ["unstable"], default-features = false } +tokio = { version = "1.10.0", features = ["full"] } +anyhow = "1.0" + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "s" +lto = true +debug = false +panic = "abort" +overflow-checks = true diff --git a/community-factory/Cargo.lock b/community-factory/Cargo.lock new file mode 100644 index 0000000..99e77e1 --- /dev/null +++ b/community-factory/Cargo.lock @@ -0,0 +1,475 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "borsh" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a744ac76a433734df0902926ed12edd997391a8da3add87f6d706afc2dcbea" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22bf794b9f8c87b51ea4d9e2710907ce13aa81dd2b8ac18a78fcca68ac738ef" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "syn_derive", +] + +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "devhub-community-factory" +version = "0.1.0" +dependencies = [ + "devhub_common", + "near-sdk", +] + +[[package]] +name = "devhub_common" +version = "0.1.0" +dependencies = [ + "near-sdk", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "near-account-id" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35cbb989542587b47205e608324ddd391f0cee1c22b4b64ae49f458334b95907" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-gas" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e75c875026229902d065e4435804497337b631ec69ba746b102954273e9ad1" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-sdk" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520234cfdf04a805ac2f04715889d096eb83fdd5b99ca7d0f8027ae473f891a8" +dependencies = [ + "base64", + "borsh", + "bs58", + "near-account-id", + "near-gas", + "near-sdk-macros", + "near-sys", + "near-token", + "once_cell", + "serde", + "serde_json", + "wee_alloc", +] + +[[package]] +name = "near-sdk-macros" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2fe3fc30068c5f20e89b0985d6104c5cc1c6742dbc6efbf352be4189b9bbf7" +dependencies = [ + "Inflector", + "darling", + "proc-macro2", + "quote", + "serde", + "serde_json", + "strum", + "strum_macros", + "syn", +] + +[[package]] +name = "near-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397688591acf8d3ebf2c2485ba32d4b24fc10aad5334e3ad8ec0b7179bfdf06b" + +[[package]] +name = "near-token" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b68f3f8a2409f72b43efdbeff8e820b81e70824c49fee8572979d789d1683fb" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro-crate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +dependencies = [ + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strum" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" + +[[package]] +name = "strum_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" +dependencies = [ + "memchr", +] diff --git a/community-factory/Cargo.toml b/community-factory/Cargo.toml new file mode 100644 index 0000000..221fd26 --- /dev/null +++ b/community-factory/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "devhub-community-factory" +version = "0.1.0" +authors = ["NEAR DevHub "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +near-sdk = "5.1.0" +devhub_common = { path = "../devhub_common" } + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "s" +lto = true +debug = false +panic = "abort" +overflow-checks = true diff --git a/community-factory/src/lib.rs b/community-factory/src/lib.rs new file mode 100644 index 0000000..7e87d1f --- /dev/null +++ b/community-factory/src/lib.rs @@ -0,0 +1,88 @@ +use near_sdk::{env, near, require, AccountId, Gas, NearToken, Promise}; +use near_sdk::serde_json::json; + +use devhub_common::social_db_contract; + +const CODE: &[u8] = include_bytes!("../../community/target/near/devhub_community.wasm"); +const INITIAL_BALANCE: NearToken = NearToken::from_near(4); +const PUBKEY_STR: &str = "ed25519:4deBAvg1S4MF7qe9GBDJwDCGLyyXtJa73JnMXwyG9vsB"; + +#[near(contract_state)] +#[derive(Default)] +pub struct Contract {} + +#[near] +impl Contract { + #[payable] + pub fn create_community_account(&mut self, community: String) -> Promise { + let parent_account: AccountId = env::current_account_id() + .get_parent_account_id() + .expect("Community factory should be deployed on a child account") + .into(); + require!( + env::predecessor_account_id() == parent_account, + "Can only be called from parent contract" + ); + require!( + env::attached_deposit() >= INITIAL_BALANCE, + "Require 4 NEAR to create community account" + ); + + let community_account_id: AccountId = + format!("{}.{}", community, env::current_account_id()).parse().unwrap(); + + let pubkey = PUBKEY_STR.parse().unwrap(); + Promise::new(community_account_id.clone()) + .create_account() + .add_full_access_key(pubkey) + .transfer(INITIAL_BALANCE) + .deploy_contract(CODE.to_vec()) + .function_call( + "new".to_string(), + b"{}".to_vec(), + NearToken::from_near(0), + Gas::from_tgas(50), + ) + .then( + self.subscribe_to_community_accounts(community_account_id) + ) + } + + pub fn subscribe_to_community_accounts(&mut self, community_account_id: AccountId) -> Promise { + let community_factory_account = env::current_account_id(); + let discussions_account_id: AccountId = + format!("discussions.{}", community_account_id.clone()).parse().unwrap(); + + social_db_contract() + .with_static_gas(env::prepaid_gas().saturating_div(3)) + .with_attached_deposit(env::attached_deposit()) + .set(json!({ + community_factory_account: { + "graph": { + "follow": { + community_account_id.clone(): "", + discussions_account_id.clone(): "", + } + }, + "index": { + "graph": json!([ + { + "key": "follow", + "value": { + "type": "follow", + "accountId": community_account_id + } + }, + { + "key": "follow", + "value": { + "type": "follow", + "accountId": discussions_account_id + } + } + ]).to_string() + } + }, + })) + } +} diff --git a/community/Cargo.lock b/community/Cargo.lock new file mode 100644 index 0000000..ca29f82 --- /dev/null +++ b/community/Cargo.lock @@ -0,0 +1,475 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "borsh" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a744ac76a433734df0902926ed12edd997391a8da3add87f6d706afc2dcbea" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22bf794b9f8c87b51ea4d9e2710907ce13aa81dd2b8ac18a78fcca68ac738ef" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "syn_derive", +] + +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "devhub-community" +version = "0.1.0" +dependencies = [ + "devhub_common", + "near-sdk", +] + +[[package]] +name = "devhub_common" +version = "0.1.0" +dependencies = [ + "near-sdk", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "near-account-id" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35cbb989542587b47205e608324ddd391f0cee1c22b4b64ae49f458334b95907" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-gas" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e75c875026229902d065e4435804497337b631ec69ba746b102954273e9ad1" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-sdk" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520234cfdf04a805ac2f04715889d096eb83fdd5b99ca7d0f8027ae473f891a8" +dependencies = [ + "base64", + "borsh", + "bs58", + "near-account-id", + "near-gas", + "near-sdk-macros", + "near-sys", + "near-token", + "once_cell", + "serde", + "serde_json", + "wee_alloc", +] + +[[package]] +name = "near-sdk-macros" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2fe3fc30068c5f20e89b0985d6104c5cc1c6742dbc6efbf352be4189b9bbf7" +dependencies = [ + "Inflector", + "darling", + "proc-macro2", + "quote", + "serde", + "serde_json", + "strum", + "strum_macros", + "syn", +] + +[[package]] +name = "near-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397688591acf8d3ebf2c2485ba32d4b24fc10aad5334e3ad8ec0b7179bfdf06b" + +[[package]] +name = "near-token" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b68f3f8a2409f72b43efdbeff8e820b81e70824c49fee8572979d789d1683fb" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro-crate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +dependencies = [ + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strum" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" + +[[package]] +name = "strum_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" +dependencies = [ + "memchr", +] diff --git a/community/Cargo.toml b/community/Cargo.toml new file mode 100644 index 0000000..c560d97 --- /dev/null +++ b/community/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "devhub-community" +version = "0.1.0" +authors = ["NEAR DevHub "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +near-sdk = "5.1.0" +devhub_common = { path = "../devhub_common" } + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "s" +lto = true +debug = false +panic = "abort" +overflow-checks = true diff --git a/community/src/lib.rs b/community/src/lib.rs new file mode 100644 index 0000000..d928a30 --- /dev/null +++ b/community/src/lib.rs @@ -0,0 +1,63 @@ +use devhub_common::social_db_contract; +use near_sdk; +use near_sdk::Gas; +use near_sdk::{env, near, require, AccountId, NearToken, Promise}; + +const CODE: &[u8] = include_bytes!("../../discussions/target/near/devhub_discussions.wasm"); +const PUBKEY_STR: &str = "ed25519:4deBAvg1S4MF7qe9GBDJwDCGLyyXtJa73JnMXwyG9vsB"; + +#[near(contract_state)] +#[derive(Default)] +pub struct Contract {} + +#[near] +impl Contract { + #[payable] + pub fn new(&mut self) -> Promise { + social_db_contract() + .with_unused_gas_weight(1) + .with_attached_deposit(NearToken::from_near(1)) + .grant_write_permission( + Some(Contract::get_devhub_account()), + None, + vec![env::current_account_id().to_string()], + ) + .then(self.create_discussions_account()) + } + + pub fn destroy(&mut self) -> Promise { + let devhub_account = Contract::get_devhub_account(); + require!( + env::predecessor_account_id() == devhub_account, + "Can only destroy community account from DevHub contract" + ); + Promise::new(env::current_account_id()).delete_account(devhub_account) + } + + fn get_devhub_account() -> AccountId { + env::current_account_id() + .get_parent_account_id() + .expect("Community contract should be deployed on a child account") + .get_parent_account_id() + .expect("Community factory should be deployed on a child account") + .into() + } + + pub fn create_discussions_account(&mut self) -> Promise { + let account_id: AccountId = + format!("discussions.{}", env::current_account_id()).parse().unwrap(); + + let pubkey = PUBKEY_STR.parse().unwrap(); + Promise::new(account_id) + .create_account() + .add_full_access_key(pubkey) + .transfer(NearToken::from_near(2)) + .deploy_contract(CODE.to_vec()) + .function_call( + "new".to_string(), + b"{}".to_vec(), + NearToken::from_near(0), + Gas::from_tgas(20), + ) + } +} diff --git a/devhub_common/Cargo.lock b/devhub_common/Cargo.lock new file mode 100644 index 0000000..6f3fb95 --- /dev/null +++ b/devhub_common/Cargo.lock @@ -0,0 +1,466 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "borsh" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "syn_derive", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "devhub_common" +version = "0.1.0" +dependencies = [ + "near-sdk", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "near-account-id" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35cbb989542587b47205e608324ddd391f0cee1c22b4b64ae49f458334b95907" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-gas" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e75c875026229902d065e4435804497337b631ec69ba746b102954273e9ad1" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-sdk" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c2e7c9524308b1b301cca05d875de13b3b20dc8b92e71f3890b380372e4c88" +dependencies = [ + "base64", + "borsh", + "bs58", + "near-account-id", + "near-gas", + "near-sdk-macros", + "near-sys", + "near-token", + "once_cell", + "serde", + "serde_json", + "wee_alloc", +] + +[[package]] +name = "near-sdk-macros" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e9b23d9d7757ade258921c9cbc7923542e64d9d3b52a6cd91f746c77cb0a0f" +dependencies = [ + "Inflector", + "darling", + "proc-macro2", + "quote", + "serde", + "serde_json", + "strum", + "strum_macros", + "syn", +] + +[[package]] +name = "near-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397688591acf8d3ebf2c2485ba32d4b24fc10aad5334e3ad8ec0b7179bfdf06b" + +[[package]] +name = "near-token" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b68f3f8a2409f72b43efdbeff8e820b81e70824c49fee8572979d789d1683fb" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] diff --git a/devhub_common/Cargo.toml b/devhub_common/Cargo.toml new file mode 100644 index 0000000..ae388bc --- /dev/null +++ b/devhub_common/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "devhub_common" +version = "0.1.0" +edition = "2021" + +[dependencies] +near-sdk = "5.1.0" + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "s" +lto = true +debug = false +panic = "abort" +overflow-checks = true \ No newline at end of file diff --git a/devhub_common/src/lib.rs b/devhub_common/src/lib.rs new file mode 100644 index 0000000..c21889d --- /dev/null +++ b/devhub_common/src/lib.rs @@ -0,0 +1,31 @@ +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::serde_json::Value; +use near_sdk::{env, ext_contract, AccountId, PublicKey, NearSchema}; + +#[derive(Copy, Clone, Serialize, Deserialize, NearSchema)] +#[serde(crate = "near_sdk::serde")] +pub struct SetReturnType { + pub block_height: near_sdk::json_types::U64, +} + +#[ext_contract(ext_social_db)] +pub trait SocialDB { + fn set(&mut self, data: Value) -> SetReturnType; + fn grant_write_permission( + &mut self, + predecessor_id: Option, + public_key: Option, + keys: Vec, + ); +} + +pub fn social_db_contract() -> ext_social_db::SocialDBExt { + let social_db: AccountId = if env::current_account_id().to_string().ends_with("testnet") { + "v1.social08.testnet" + } else { + "social.near" + } + .parse() + .unwrap(); + ext_social_db::ext(social_db) +} diff --git a/discussions/Cargo.lock b/discussions/Cargo.lock new file mode 100644 index 0000000..089de0f --- /dev/null +++ b/discussions/Cargo.lock @@ -0,0 +1,475 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "borsh" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a744ac76a433734df0902926ed12edd997391a8da3add87f6d706afc2dcbea" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22bf794b9f8c87b51ea4d9e2710907ce13aa81dd2b8ac18a78fcca68ac738ef" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "syn_derive", +] + +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "devhub-discussions" +version = "0.1.0" +dependencies = [ + "devhub_common", + "near-sdk", +] + +[[package]] +name = "devhub_common" +version = "0.1.0" +dependencies = [ + "near-sdk", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "near-account-id" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35cbb989542587b47205e608324ddd391f0cee1c22b4b64ae49f458334b95907" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-gas" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e75c875026229902d065e4435804497337b631ec69ba746b102954273e9ad1" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "near-sdk" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520234cfdf04a805ac2f04715889d096eb83fdd5b99ca7d0f8027ae473f891a8" +dependencies = [ + "base64", + "borsh", + "bs58", + "near-account-id", + "near-gas", + "near-sdk-macros", + "near-sys", + "near-token", + "once_cell", + "serde", + "serde_json", + "wee_alloc", +] + +[[package]] +name = "near-sdk-macros" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2fe3fc30068c5f20e89b0985d6104c5cc1c6742dbc6efbf352be4189b9bbf7" +dependencies = [ + "Inflector", + "darling", + "proc-macro2", + "quote", + "serde", + "serde_json", + "strum", + "strum_macros", + "syn", +] + +[[package]] +name = "near-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397688591acf8d3ebf2c2485ba32d4b24fc10aad5334e3ad8ec0b7179bfdf06b" + +[[package]] +name = "near-token" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b68f3f8a2409f72b43efdbeff8e820b81e70824c49fee8572979d789d1683fb" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "proc-macro-crate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +dependencies = [ + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strum" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" + +[[package]] +name = "strum_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" +dependencies = [ + "memchr", +] diff --git a/discussions/Cargo.toml b/discussions/Cargo.toml new file mode 100644 index 0000000..5af8ed7 --- /dev/null +++ b/discussions/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "devhub-discussions" +version = "0.1.0" +authors = ["NEAR DevHub "] +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +near-sdk = "5.1.0" +devhub_common = { path = "../devhub_common" } + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "s" +lto = true +debug = false +panic = "abort" +overflow-checks = true diff --git a/discussions/src/lib.rs b/discussions/src/lib.rs new file mode 100644 index 0000000..5307215 --- /dev/null +++ b/discussions/src/lib.rs @@ -0,0 +1,47 @@ +use devhub_common::social_db_contract; +use near_sdk::{env, require, near, AccountId, NearToken, Promise}; + +#[near(contract_state)] +#[derive(Default)] +pub struct Contract {} + +#[near] +impl Contract { + #[init] + pub fn new() -> Self { + social_db_contract() + .with_unused_gas_weight(1) + .with_attached_deposit(NearToken::from_near(1)) + .grant_write_permission( + Some(Contract::get_devhub_account()), + None, + vec![env::current_account_id().to_string()], + ) + .as_return(); + Contract {} + } + + pub fn destroy(&mut self) -> Promise { + let devhub_account = Contract::get_devhub_account(); + require!( + env::predecessor_account_id() == devhub_account, + "Can only destroy community account from DevHub contract" + ); + Promise::new(env::current_account_id()).delete_account(devhub_account) + } + + /** + * current_account_id = discussions.{{community}}.community.devhub.near + * returns devhub.near + */ + fn get_devhub_account() -> AccountId { + env::current_account_id() + .get_parent_account_id() + .expect("Discussions contract should be deployed on a child account") + .get_parent_account_id() + .expect("Community contract should be deployed on a child account") + .get_parent_account_id() + .expect("Community factory should be deployed on a child account") + .into() + } +} diff --git a/res/.gitkeep b/res/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..9ccbc7a --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.75.0" +components = ["rustfmt"] +targets = ["wasm32-unknown-unknown"] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..53efc17 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +use_small_heuristics = "Max" +reorder_imports = true +edition = "2021" diff --git a/src/access_control/members.rs b/src/access_control/members.rs new file mode 100644 index 0000000..b934656 --- /dev/null +++ b/src/access_control/members.rs @@ -0,0 +1,409 @@ +use crate::access_control::rules::Rule; +use near_sdk::{near, AccountId}; +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; + +#[near(serializers=[borsh, json])] +#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] +#[serde(from = "String")] +#[serde(into = "String")] +pub enum Member { + /// NEAR account names do not allow `:` character so this structure cannot be abused. + Account(AccountId), + Team(String), +} + +/// JSON string representation prefix of `Member::Team` variant. +const TEAM: &str = "team:"; + +impl From for Member { + fn from(full_str: String) -> Self { + if let Some(s) = full_str.strip_prefix(TEAM) { + Member::Team(s.to_string()) + } else { + Member::Account(full_str.parse().unwrap()) + } + } +} + +impl Into for Member { + fn into(self) -> String { + match self { + Member::Account(s) => s.to_string(), + Member::Team(s) => format!("{}{}", TEAM, s).to_string(), + } + } +} + +#[near(serializers=[borsh, json])] +#[derive(Clone, Default, Debug, Eq, PartialEq)] +pub struct MemberMetadata { + pub description: String, + pub permissions: HashMap>, + pub children: HashSet, + pub parents: HashSet, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug)] +#[serde(rename_all = "kebab-case")] +pub enum ActionType { + /// Can edit posts that have these labels. + EditPost, + /// Can add/remove labels that fall under these rules. + UseLabels, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone, Debug, Eq, PartialEq)] +#[serde(tag = "member_metadata_version")] +pub enum VersionedMemberMetadata { + V0(MemberMetadata), +} + +impl VersionedMemberMetadata { + pub fn last_version(&self) -> MemberMetadata { + match self { + VersionedMemberMetadata::V0(v0) => v0.clone(), + } + } +} + +impl From for VersionedMemberMetadata { + fn from(m: MemberMetadata) -> Self { + VersionedMemberMetadata::V0(m) + } +} + +#[near(serializers=[borsh, json])] +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct MembersList { + #[serde(flatten)] + pub members: HashMap, +} + +impl MembersList { + /// Get members that do not belong to any team. + pub fn get_root_members(&self) -> HashMap { + self.members + .iter() + .filter(|(_, v)| v.last_version().parents.is_empty()) + .map(|(k, v)| (k.clone(), v.clone())) + .collect() + } + + /// Whether given account has special permissions for a post with the given labels. + /// Labels are restricted labels. + pub fn check_permissions( + &self, + account: AccountId, + labels: Vec, + ) -> HashSet { + let member_account = Member::Account(account); + if !self.members.contains_key(&member_account) { + return HashSet::new(); + } + + let mut stack = HashSet::new(); + stack.insert(member_account); + + let mut permissions = HashSet::new(); + while let Some(member) = stack.iter().next().cloned() { + stack.remove(&member); + + let metadata = self + .members + .get(&member) + .unwrap_or_else(|| panic!("Metadata not found for {:#?}", member)) + .last_version(); + + for (member_rule, member_permissions) in metadata.permissions { + if member_rule.applies_to_any(&labels) { + permissions.extend(member_permissions); + } + } + + stack.extend(metadata.parents); + } + permissions + } + + pub fn add_member(&mut self, member: Member, metadata: VersionedMemberMetadata) { + assert!( + self.members.insert(member.clone(), metadata.clone()).is_none(), + "Member already exists" + ); + + // Update child members that this member is a parent of. + for child in &metadata.last_version().children { + match self.members.entry(child.clone()) { + Entry::Occupied(mut occ) => { + let MemberMetadata { description, children, mut parents, permissions } = + occ.get().last_version(); + assert!(parents.insert(member.clone()), "Child already had this parent"); + let new_child = MemberMetadata { description, children, parents, permissions }; + occ.insert(new_child.into()); + } + Entry::Vacant(_) => { + panic!("Member declares a child {:#?} that does not exist", child) + } + } + } + + // Update parent members that this member is now a child of. + for parent in &metadata.last_version().parents { + match self.members.entry(parent.clone()) { + Entry::Occupied(mut occ) => { + let MemberMetadata { description, mut children, parents, permissions } = + occ.get().last_version(); + assert!(children.insert(member.clone()), "Parent already had this child"); + let new_parent = MemberMetadata { description, children, parents, permissions }; + occ.insert(new_parent.into()); + } + Entry::Vacant(_) => { + panic!("Member declares a parent {:#?} that does not exist", parent) + } + } + } + } + + pub fn remove_member(&mut self, member: &Member) { + let metadata = self.members.remove(member).expect("Member does not exist"); + + // Update child members that this member is not a parent of anymore. + for child in &metadata.last_version().children { + match self.members.entry(child.clone()) { + Entry::Occupied(mut occ) => { + let MemberMetadata { description, children, mut parents, permissions } = + occ.get().last_version(); + assert!(parents.remove(member), "Child did not have this parent."); + let new_child = MemberMetadata { description, children, parents, permissions }; + occ.insert(new_child.into()); + } + Entry::Vacant(_) => { + panic!("Member declares a child {:#?} that does not exist", child) + } + } + } + + // Update parent members that this member is not a child of anymore. + for parent in &metadata.last_version().parents { + match self.members.entry(parent.clone()) { + Entry::Occupied(mut occ) => { + let MemberMetadata { description, mut children, parents, permissions } = + occ.get().last_version(); + assert!(children.remove(member), "Parent did not have this child."); + let new_parent = MemberMetadata { description, children, parents, permissions }; + occ.insert(new_parent.into()); + } + Entry::Vacant(_) => { + panic!("Member declares a parent {:#?} that does not exist", parent) + } + } + } + } + + pub fn edit_member(&mut self, member: Member, metadata: VersionedMemberMetadata) { + self.remove_member(&member); + self.add_member(member, metadata); + } + + pub fn get_moderators(&self) -> HashSet { + self.members + .get(&Member::Team("moderators".to_string())) + .map(|team| team.last_version().children) + .unwrap_or(HashSet::new()) + } +} + +#[cfg(test)] +mod tests { + use crate::access_control::members::{ + ActionType, Member, MemberMetadata, MembersList, VersionedMemberMetadata, + }; + use crate::access_control::rules::Rule; + use near_sdk::serde_json; + use std::collections::{HashMap, HashSet}; + + #[test] + fn member_serialization() { + let member = Member::Account("alice.near".parse().unwrap()); + assert_eq!(serde_json::to_value(&member).unwrap(), serde_json::json!("alice.near")); + + let member = Member::Team("funding".to_string()); + assert_eq!(serde_json::to_value(&member).unwrap(), serde_json::json!("team:funding")); + } + + #[test] + fn member_deserialization() { + let member: Member = serde_json::from_str(r#""alice.near""#).unwrap(); + assert_eq!(member, Member::Account("alice.near".parse().unwrap())); + + let member: Member = serde_json::from_str(r#""team:funding""#).unwrap(); + assert_eq!(member, Member::Team("funding".to_string())); + } + + fn root_member() -> (Member, VersionedMemberMetadata) { + ( + Member::Account("devgovgigs.near".parse().unwrap()), + MemberMetadata { + description: "Main account can do anything".to_string(), + permissions: HashMap::from([ + ( + Rule::StartsWith("wg-".to_string()), + HashSet::from([ActionType::EditPost, ActionType::UseLabels]), + ), + ( + Rule::StartsWith("funding".to_string()), + HashSet::from([ActionType::EditPost, ActionType::UseLabels]), + ), + ( + Rule::StartsWith("mnw".to_string()), + HashSet::from([ActionType::EditPost, ActionType::UseLabels]), + ), + ]), + ..Default::default() + } + .into(), + ) + } + + fn moderator_member(name: &str) -> (Member, VersionedMemberMetadata) { + ( + Member::Account(name.parse().unwrap()), + MemberMetadata { + description: format!("{} inherits everything from moderator group.", name) + .to_string(), + parents: HashSet::from([Member::Team("moderators".to_string())]), + ..Default::default() + } + .into(), + ) + } + + fn moderators() -> (Member, VersionedMemberMetadata) { + ( + Member::Team("moderators".to_string()), + MemberMetadata { + description: "Moderators can do anything except funding posts.".to_string(), + permissions: HashMap::from([ + ( + Rule::StartsWith("wg-".to_string()), + HashSet::from([ActionType::EditPost, ActionType::UseLabels]), + ), + ( + Rule::StartsWith("mnw".to_string()), + HashSet::from([ActionType::EditPost, ActionType::UseLabels]), + ), + ]), + children: HashSet::from([ + Member::Account("ori.near".parse().unwrap()), + Member::Account("max.near".parse().unwrap()), + Member::Account("vlad.near".parse().unwrap()), + ]), + ..Default::default() + } + .into(), + ) + } + + fn create_list() -> MembersList { + MembersList { + members: HashMap::from([ + moderators(), + root_member(), + moderator_member("ori.near"), + moderator_member("max.near"), + moderator_member("vlad.near"), + ]), + } + } + + #[test] + fn get_root_members() { + let list = create_list(); + let root_members: HashSet<_> = list.get_root_members().keys().cloned().collect(); + assert_eq!( + root_members, + HashSet::from([ + Member::Team("moderators".to_string()), + Member::Account("devgovgigs.near".parse().unwrap()) + ]) + ); + } + + #[test] + fn check_permissions() { + let list = create_list(); + let actual = list.check_permissions( + "max.near".parse().unwrap(), + vec!["wg-protocol".to_string(), "funding-requested".to_string()], + ); + assert_eq!( + actual, + serde_json::from_value::>(serde_json::json!([ + "edit-post", + "use-labels" + ])) + .unwrap() + ); + + let actual = list + .check_permissions("max.near".parse().unwrap(), vec!["funding-requested".to_string()]); + assert!(actual.is_empty()); + } + + #[test] + fn check_permissions_rules_any() { + let mut list = create_list(); + let given_permissions = HashSet::from([ActionType::EditPost, ActionType::UseLabels]); + list.add_member( + Member::Account("thomasguntenaar.near".parse().unwrap()), + MemberMetadata { + description: "Account has `Any` rules without labels".to_string(), + permissions: HashMap::from([(Rule::Any(), given_permissions.clone())]), + ..Default::default() + } + .into(), + ); + // Without labels + assert_eq!( + list.check_permissions("thomasguntenaar.near".parse().unwrap(), vec![]), + given_permissions, + ); + + // With labels + assert_eq!( + list.check_permissions( + "thomasguntenaar.near".parse().unwrap(), + vec!["funding-requested".to_string()] + ), + given_permissions, + ) + } + + #[test] + fn check_permissions_of_not_member() { + let list = create_list(); + let actual: HashSet = list.check_permissions( + "random.near".parse().unwrap(), + vec!["wg-protocol".to_string(), "funding-requested".to_string()], + ); + assert!(actual.is_empty()); + } + + #[test] + fn add_remove_member() { + let mut list = create_list(); + list.add_member( + Member::Account("bob.near".parse().unwrap()), + MemberMetadata { + parents: HashSet::from([Member::Team("moderators".to_string())]), + ..Default::default() + } + .into(), + ); + list.remove_member(&Member::Account("bob.near".parse().unwrap())); + assert_eq!(list, create_list()); + } +} diff --git a/src/access_control/mod.rs b/src/access_control/mod.rs new file mode 100644 index 0000000..783108d --- /dev/null +++ b/src/access_control/mod.rs @@ -0,0 +1,79 @@ +use crate::access_control::members::{Member, MembersList, VersionedMemberMetadata}; +use crate::access_control::rules::{Rule, RulesList}; +use crate::*; +use near_sdk::near; +use std::collections::{HashMap, HashSet}; + +pub mod members; +pub mod rules; + +#[near(serializers=[borsh, json])] +#[derive(Clone, Default)] +pub struct AccessControl { + pub rules_list: RulesList, + pub members_list: MembersList, +} + +#[near] +impl Contract { + pub fn get_access_control_info(&self) -> &AccessControl { + &self.access_control + } + + pub fn is_restricted_label(&self, label: String) -> bool { + self.access_control.rules_list.is_restricted(&label) + } + + pub fn find_restricted_labels(&self, labels: Vec) -> HashSet { + self.access_control.rules_list.find_restricted(&labels) + } + + pub fn set_restricted_rules(&mut self, rules: RulesList) { + require!( + self.has_moderator(env::predecessor_account_id()) + || env::predecessor_account_id() == env::current_account_id(), + "Only the admin and moderators can set restricted rules" + ); + self.access_control.rules_list.set_restricted(rules) + } + + pub fn unset_restricted_rules(&mut self, rules: Vec) { + require!( + self.has_moderator(env::predecessor_account_id()) + || env::predecessor_account_id() == env::current_account_id(), + "Only the admin and moderators can unset restricted rules" + ); + self.access_control.rules_list.unset_restricted(rules) + } + + pub fn get_root_members(&self) -> HashMap { + self.access_control.members_list.get_root_members() + } + + pub fn add_member(&mut self, member: Member, metadata: VersionedMemberMetadata) { + require!( + self.has_moderator(env::predecessor_account_id()) + || env::predecessor_account_id() == env::current_account_id(), + "Only the admin and moderators can add members" + ); + self.access_control.members_list.add_member(member, metadata) + } + + pub fn remove_member(&mut self, member: &Member) { + require!( + self.has_moderator(env::predecessor_account_id()) + || env::predecessor_account_id() == env::current_account_id(), + "Only the admin and moderators can remove members" + ); + self.access_control.members_list.remove_member(member) + } + + pub fn edit_member(&mut self, member: Member, metadata: VersionedMemberMetadata) { + require!( + self.has_moderator(env::predecessor_account_id()) + || env::predecessor_account_id() == env::current_account_id(), + "Only the admin and moderators can edit members" + ); + self.access_control.members_list.edit_member(member, metadata) + } +} diff --git a/src/access_control/rules.rs b/src/access_control/rules.rs new file mode 100644 index 0000000..a9331d0 --- /dev/null +++ b/src/access_control/rules.rs @@ -0,0 +1,237 @@ +use near_sdk::near; +use std::collections::{HashMap, HashSet}; + +#[near(serializers=[borsh, json])] +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct RulesList { + #[serde(flatten)] + pub rules: HashMap, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RuleMetadata { + pub description: String, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone, Debug, Eq, PartialEq)] +#[serde(tag = "rule_metadata_version")] +pub enum VersionedRuleMetadata { + V0(RuleMetadata), +} + +impl From for VersionedRuleMetadata { + fn from(rm: RuleMetadata) -> Self { + VersionedRuleMetadata::V0(rm) + } +} + +#[near(serializers=[borsh, json])] +#[derive(Clone, PartialOrd, PartialEq, Ord, Eq, Hash, Debug)] +#[serde(from = "String", into = "String")] +pub enum Rule { + /// Labels can be any string, but rules are created by the NEAR account owner of this contract, + /// or small circle of moderators. So this code cannot be abused. Likely creating a label that + /// mimics a rule makes this label only more restrictive, so there might be nothing to exploit. + /// TODO: Add extra logic to prevent malicious rules creation by creating labels that mimic rules. + ExactMatch(String), + StartsWith(String), + Any(), +} + +/// JSON string representation prefix of Rule::StartsWith variant. +const STARTS_WITH: &str = "starts-with:"; +const ANY: &str = "*"; + +impl From for Rule { + fn from(full_str: String) -> Self { + if full_str == ANY { + Rule::Any() + } else if let Some(s) = full_str.strip_prefix(STARTS_WITH) { + Rule::StartsWith(s.to_string()) + } else { + Rule::ExactMatch(full_str) + } + } +} + +impl Into for Rule { + fn into(self) -> String { + match self { + Rule::ExactMatch(s) => s.to_string(), + Rule::StartsWith(s) => format!("{}{}", STARTS_WITH, s).to_string(), + Rule::Any() => ANY.to_string(), + } + } +} + +impl Rule { + /// Check if this rule applies to a label. + pub fn applies(&self, label: &str) -> bool { + match self { + Rule::ExactMatch(rule) => label == rule, + Rule::StartsWith(rule) => label.starts_with(rule), + Rule::Any() => true, + } + } + + /// Check if this rule applies to any of the labels. + pub fn applies_to_any(&self, labels: &[String]) -> bool { + match self { + Rule::ExactMatch(rule) => labels.iter().any(|label| label == rule), + Rule::StartsWith(rule) => labels.iter().any(|label| label.starts_with(rule.as_str())), + Rule::Any() => true, + } + } +} + +impl RulesList { + /// Is this a restricted label. + pub fn is_restricted(&self, label: &str) -> bool { + self.rules.keys().any(|rule| rule.applies(label)) + } + + /// Get restricted labels out of this list. + pub fn find_restricted(&self, labels: &[String]) -> HashSet { + self.rules + .keys() + .map(|key| match key { + Rule::ExactMatch(rule) => { + labels.iter().filter(|label| label == &rule).collect::>() + } + Rule::StartsWith(rule) => { + labels.iter().filter(|label| label.starts_with(rule)).collect::>() + } + Rule::Any() => { + vec![] + } + }) + .flatten() + .cloned() + .collect() + } + + /// Set rules as restricted. Can be also used to override metadata on existing rules. + pub fn set_restricted(&mut self, rules: Self) { + for (rule, metadata) in rules.rules { + self.rules.insert(rule, metadata); + } + } + + /// Unset rules as restricted. + pub fn unset_restricted(&mut self, rules: Vec) { + for rule in rules { + self.rules.remove(&rule); + } + } +} + +#[cfg(test)] +mod tests { + use crate::access_control::rules::{Rule, RuleMetadata, RulesList}; + use near_sdk::serde_json; + use std::collections::{HashMap, HashSet}; + + #[test] + fn rule_serialization() { + let rule = Rule::ExactMatch("wg-protocol".to_string()); + assert_eq!(serde_json::to_value(&rule).unwrap(), serde_json::json!("wg-protocol")); + + let rule = Rule::StartsWith("funding".to_string()); + assert_eq!(serde_json::to_value(&rule).unwrap(), serde_json::json!("starts-with:funding")); + } + + #[test] + fn rule_deserialization() { + let rule: Rule = serde_json::from_str(r#""wg-protocol""#).unwrap(); + assert_eq!(rule, Rule::ExactMatch("wg-protocol".to_string())); + + let rule: Rule = serde_json::from_str(r#""starts-with:funding""#).unwrap(); + assert_eq!(rule, Rule::StartsWith("funding".to_string())); + } + + fn create_list() -> RulesList { + RulesList { + rules: HashMap::from([ + ( + Rule::ExactMatch("wg-protocol".to_string()), + RuleMetadata { description: "For Protocol WG only".to_string() }.into(), + ), + ( + Rule::ExactMatch("wg-tools".to_string()), + RuleMetadata { description: "For Tools WG only".to_string() }.into(), + ), + ( + Rule::StartsWith("funding".to_string()), + RuleMetadata { description: "For funding team only".to_string() }.into(), + ), + ( + Rule::StartsWith("mnw".to_string()), + RuleMetadata { description: "For Wallet WG only".to_string() }.into(), + ), + ]), + } + } + + #[test] + fn rule_list_serialization_deserialization() { + let list = create_list(); + + let list_json = serde_json::json!( + { + "wg-protocol": { "description": "For Protocol WG only", "rule_metadata_version": "V0"}, + "wg-tools": {"description": "For Tools WG only", "rule_metadata_version": "V0" }, + "starts-with:funding": {"description": "For funding team only", "rule_metadata_version": "V0" }, + "starts-with:mnw": {"description": "For Wallet WG only", "rule_metadata_version": "V0" } + } + ); + assert_eq!(serde_json::to_value(list.clone()).unwrap(), list_json); + assert_eq!(serde_json::from_value::(list_json).unwrap(), list); + } + + #[test] + fn is_restricted() { + let list = create_list(); + assert!(list.is_restricted(&"wg-protocol".to_string())); + assert!(list.is_restricted(&"wg-tools".to_string())); + assert!(!list.is_restricted(&"wg-wallet".to_string())); + assert!(list.is_restricted(&"funding".to_string())); + assert!(list.is_restricted(&"fundingfoobar".to_string())); + assert!(list.is_restricted(&"funding-requested".to_string())); + assert!(!list.is_restricted(&"nofunding".to_string())); + assert!(list.is_restricted(&"mnw".to_string())); + assert!(list.is_restricted(&"mnw-approved".to_string())); + assert!(!list.is_restricted(&"nomnw".to_string())); + } + + #[test] + fn find_restricted() { + let list = create_list(); + let actual = list.find_restricted(&[ + "wg-protocol".to_string(), + "wg-tools".to_string(), + "wg-wallet".to_string(), + "funding".to_string(), + "funding".to_string(), + "fundingfoobar".to_string(), + "fundingfoobar".to_string(), + "funding-requested".to_string(), + "nofunding".to_string(), + "nofunding".to_string(), + "mnw".to_string(), + "mnw-approved".to_string(), + "nomnw".to_string(), + ]); + let expected = HashSet::from([ + "wg-protocol".to_string(), + "wg-tools".to_string(), + "funding".to_string(), + "fundingfoobar".to_string(), + "funding-requested".to_string(), + "mnw".to_string(), + "mnw-approved".to_string(), + ]); + assert_eq!(actual, expected); + } +} diff --git a/src/community/mod.rs b/src/community/mod.rs new file mode 100644 index 0000000..1959202 --- /dev/null +++ b/src/community/mod.rs @@ -0,0 +1,211 @@ +use near_sdk::{env, ext_contract, near, require, AccountId, Gas, NearToken}; + +pub type CommunityHandle = String; + +pub type AddOnId = String; + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct CommunityInputs { + pub handle: CommunityHandle, + pub name: String, + pub tag: String, + pub description: String, + pub logo_url: String, + pub banner_url: String, + pub bio_markdown: Option, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct CommunityMetadata { + pub admins: Vec, + pub handle: CommunityHandle, + pub name: String, + pub tag: String, + pub description: String, + pub logo_url: String, + pub banner_url: String, + pub bio_markdown: Option, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct CommunityFeatureFlags { + pub telegram: bool, + pub github: bool, + pub board: bool, + pub wiki: bool, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct WikiPage { + name: String, + content_markdown: String, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone, PartialEq, Debug)] +pub struct CommunityAddOn { + pub id: String, + pub addon_id: AddOnId, + pub display_name: String, + pub enabled: bool, + pub parameters: String, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone, PartialEq, Debug)] +pub struct AddOn { + pub id: AddOnId, + pub title: String, + pub description: String, + pub icon: String, + // The path to the view on the community page + pub view_widget: String, + // The path to the view on the community configuration page + pub configurator_widget: String, +} + +impl AddOn { + pub fn validate(&self) { + if !matches!(self.id.chars().count(), 3..=120) { + panic!("Add-on id must contain 3 to 120 characters"); + } + + if !matches!(self.title.chars().count(), 3..=120) { + panic!("Add-on title must contain 3 to 120 characters"); + } + + if !matches!(self.description.chars().count(), 3..=120) { + panic!("Add-on description must contain 3 to 120 characters"); + } + if !matches!(self.view_widget.chars().count(), 6..=240) { + panic!("Add-on viewer must contain 6 to 240 characters"); + } + if !matches!(self.configurator_widget.chars().count(), 0..=240) { + panic!("Add-on configurator must contain 0 to 240 characters"); + } + if !matches!(self.icon.chars().count(), 6..=60) { + panic!("Add-on icon must contain 6 to 60 characters"); + } + } +} + +#[near(serializers=[borsh, json])] +pub struct Community { + pub admins: Vec, + pub handle: CommunityHandle, + pub name: String, + pub tag: String, + pub description: String, + pub logo_url: String, + pub banner_url: String, + pub bio_markdown: Option, + pub github_handle: Option, + pub telegram_handle: Option, + pub twitter_handle: Option, + pub website_url: Option, + pub addons: Vec, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct FeaturedCommunity { + pub handle: CommunityHandle, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct CommunityPermissions { + pub can_configure: bool, + pub can_delete: bool, +} + +impl Community { + pub fn validate(&self) { + require!( + matches!(self.handle.chars().count(), 3..=40), + "Community handle must contain 3 to 40 characters" + ); + require!( + self.handle.parse::().is_ok() && !self.handle.contains('.'), + "Community handle should be lowercase alphanumeric symbols separated either by `_` or `-`, separators are not permitted to immediately follow each other, start or end with separators" + ); + require!( + matches!(self.name.chars().count(), 3..=30), + "Community name must contain 3 to 30 characters" + ); + require!( + matches!(self.tag.chars().count(), 3..=30), + "Community tag must contain 3 to 30 characters" + ); + require!( + matches!(self.description.chars().count(), 6..=60), + "Community description must contain 6 to 60 characters" + ); + require!( + self.bio_markdown.is_none() + || matches!( + self.bio_markdown.to_owned().map_or(0, |text| text.chars().count()), + 3..=200 + ), + "Community bio must contain 3 to 200 characters" + ); + } + + pub fn add_addon(&mut self, addon_config: CommunityAddOn) { + self.addons.push(addon_config); + } + + pub fn remove_addon(&mut self, addon_to_remove: CommunityAddOn) { + self.addons.retain(|addon| addon != &addon_to_remove); + } + + pub fn set_addons(&mut self, addons: Vec) { + self.addons = addons; + } + + pub fn set_default_admin(&mut self) { + if self.admins.is_empty() { + self.admins = vec![env::predecessor_account_id()]; + } + } +} + +pub fn get_devhub_community_factory() -> AccountId { + format!("community.{}", env::current_account_id()).parse().unwrap() +} + +pub fn get_devhub_discussions_factory(handle: &CommunityHandle) -> AccountId { + get_devhub_community_account(handle).parse().unwrap() +} + +pub fn get_devhub_community_account(handle: &CommunityHandle) -> String { + format!("{}.{}", handle, get_devhub_community_factory()) +} + +pub fn get_devhub_discussions_account(handle: &CommunityHandle) -> String { + format!("discussions.{}", get_devhub_community_account(handle)) +} + +#[ext_contract(ext_devhub_community_factory)] +pub trait DevhubCommunityFactory { + fn create_community_account(&mut self, community: String); +} + +#[ext_contract(ext_devhub_community)] +pub trait DevhubCommunity { + fn destroy(&mut self); + + fn create_discussions_account(&mut self); +} + +pub const CREATE_COMMUNITY_BALANCE: NearToken = NearToken::from_near(4); +pub const CREATE_DISCUSSION_BALANCE: NearToken = NearToken::from_near(2); +pub const CREATE_COMMUNITY_GAS: Gas = Gas::from_tgas(200); +pub const UPDATE_COMMUNITY_GAS: Gas = Gas::from_tgas(30); +pub const DELETE_COMMUNITY_GAS: Gas = Gas::from_tgas(30); +pub const SET_COMMUNITY_SOCIALDB_GAS: Gas = Gas::from_tgas(30); +pub const CREATE_DISCUSSION_GAS: Gas = Gas::from_tgas(30); diff --git a/src/debug.rs b/src/debug.rs new file mode 100644 index 0000000..2e52a19 --- /dev/null +++ b/src/debug.rs @@ -0,0 +1,32 @@ +use crate::*; +use near_sdk::near; +use near_sdk::serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +#[serde(crate = "near_sdk::serde")] +pub struct Stats { + pub num_posts: u64, +} + +#[near] +impl Contract { + pub fn get_post_to_parent(&self) -> Vec<(PostId, PostId)> { + let mut res = vec![]; + for child_id in 0..self.posts.len() { + if let Some(parent_id) = self.post_to_parent.get(&child_id) { + res.push((child_id, parent_id)); + } + } + res + } + + pub fn get_parent_to_children(&self) -> Vec<(PostId, Vec)> { + let mut res = vec![]; + for parent_id in 0..self.posts.len() { + if let Some(children_ids) = self.post_to_children.get(&parent_id) { + res.push((parent_id, children_ids)); + } + } + res + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..687aacf --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,1301 @@ +pub mod access_control; +pub mod community; +pub mod debug; +pub mod migrations; +mod notify; +pub mod post; +pub mod proposal; +mod repost; +pub mod rfp; +pub mod stats; +pub mod str_serializers; +pub mod web4; + +use crate::access_control::members::ActionType; +use crate::access_control::members::Member; +use crate::access_control::AccessControl; +use community::*; +use post::*; +use proposal::timeline::TimelineStatus; +use proposal::*; +use rfp::{ + RFPId, RFPSnapshot, TimelineStatus as RFPTimelineStatus, VersionedRFP, VersionedRFPBody, RFP, +}; + +use devhub_common::{social_db_contract, SetReturnType}; + +use near_sdk::borsh::BorshDeserialize; +use near_sdk::collections::{LookupMap, UnorderedMap, Vector}; +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::serde_json::{json, Number, Value}; +use near_sdk::store::Lazy; +use near_sdk::{env, near, require, AccountId, NearSchema, PanicOnDefault, Promise}; +use web4::types::{Web4Request, Web4Response}; + +use std::collections::{HashMap, HashSet}; +use std::convert::TryInto; + +type PostId = u64; +type IdeaId = u64; +type AttestationId = u64; +type SolutionId = u64; +type SponsorshipId = u64; +type CommentId = u64; + +/// An imaginary top post representing the landing page. +const ROOT_POST_ID: u64 = u64::MAX; + +#[near(contract_state)] +#[derive(PanicOnDefault)] +pub struct Contract { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, + pub authors: UnorderedMap>, + pub proposals: Vector, + pub label_to_proposals: UnorderedMap>, + pub author_proposals: UnorderedMap>, + pub proposal_categories: Vec, + pub rfps: Vector, + pub label_to_rfps: UnorderedMap>, + pub global_labels_info: Lazy>, + pub communities: UnorderedMap, + pub featured_communities: Vec, + pub available_addons: UnorderedMap, +} + +#[near] +impl Contract { + #[init] + pub fn new() -> Self { + migrations::state_version_write(&migrations::StateVersion::V11); + + let mut contract = Self { + posts: Vector::new(StorageKey::Posts), + post_to_parent: LookupMap::new(StorageKey::PostToParent), + post_to_children: LookupMap::new(StorageKey::PostToChildren), + label_to_posts: UnorderedMap::new(StorageKey::LabelToPostsV2), + access_control: AccessControl::default(), + authors: UnorderedMap::new(StorageKey::AuthorToAuthorPosts), + proposals: Vector::new(StorageKey::Proposals), + label_to_proposals: UnorderedMap::new(StorageKey::LabelToProposals), + author_proposals: UnorderedMap::new(StorageKey::AuthorProposals), + proposal_categories: default_categories(), + rfps: Vector::new(StorageKey::RFPs), + label_to_rfps: UnorderedMap::new(StorageKey::LabelToRFPs), + global_labels_info: Lazy::new(StorageKey::LabelInfo, HashMap::new()), + communities: UnorderedMap::new(StorageKey::Communities), + featured_communities: Vec::new(), + available_addons: UnorderedMap::new(StorageKey::AddOns), + }; + + contract.post_to_children.insert(&ROOT_POST_ID, &Vec::new()); + contract + } + + /// If `parent_id` is not provided get all landing page posts. Otherwise, get all posts under + /// `parent_id` post. + pub fn get_posts(&self, parent_id: Option) -> Vec { + let parent_id = parent_id.unwrap_or(ROOT_POST_ID); + let children_ids = self + .post_to_children + .get(&parent_id) + .unwrap_or_else(|| panic!("Parent id {} not found", parent_id)); + children_ids + .into_iter() + .map(|id| { + self.posts + .get(id) + .unwrap_or_else(|| panic!("Post id {} not found. Broken state invariant", id)) + }) + .collect() + } + + pub fn get_post(&self, post_id: PostId) -> VersionedPost { + self.posts.get(post_id).unwrap_or_else(|| panic!("Post id {} not found", post_id)) + } + + pub fn get_all_post_ids(&self) -> Vec { + (0..self.posts.len()).collect() + } + + pub fn get_children_ids(&self, post_id: Option) -> Vec { + let post_id = post_id.unwrap_or(ROOT_POST_ID); + self.post_to_children + .get(&post_id) + .unwrap_or_else(|| panic!("Parent id {} not found", post_id)) + } + + pub fn get_parent_id(&self, post_id: PostId) -> Option { + let res = self + .post_to_parent + .get(&post_id) + .unwrap_or_else(|| panic!("Parent id {} not found", post_id)); + if res == ROOT_POST_ID { + Option::None + } else { + Option::Some(res) + } + } + + pub fn get_proposals(&self, ids: Option>) -> Vec { + if let Some(ids) = ids { + ids.into_iter().filter_map(|id| self.proposals.get(id.into())).collect() + } else { + self.proposals.to_vec() + } + } + + pub fn get_proposal(&self, proposal_id: ProposalId) -> VersionedProposal { + self.proposals + .get(proposal_id.into()) + .unwrap_or_else(|| panic!("Proposal id {} not found", proposal_id)) + } + + pub fn get_all_proposal_ids(&self) -> Vec { + (0..self.proposals.len().try_into().unwrap()).collect() + } + + pub fn get_rfps(&self) -> Vec { + self.rfps.to_vec() + } + + pub fn get_rfp(&self, rfp_id: RFPId) -> VersionedRFP { + self.rfps.get(rfp_id.into()).unwrap_or_else(|| panic!("RFP id {} not found", rfp_id)) + } + + pub fn get_all_rfp_ids(&self) -> Vec { + (0..self.rfps.len().try_into().unwrap()).collect() + } + + #[payable] + pub fn add_like(&mut self, post_id: PostId) -> Promise { + let mut post: Post = self + .posts + .get(post_id) + .unwrap_or_else(|| panic!("Post id {} not found", post_id)) + .into(); + let post_author = post.author_id.clone(); + let like = + Like { author_id: env::predecessor_account_id(), timestamp: env::block_timestamp() }; + post.likes.insert(like); + self.posts.replace(post_id, &post.into()); + notify::notify_like(post_id, post_author) + } + + #[payable] + pub fn add_post( + &mut self, + parent_id: Option, + body: PostBody, + labels: HashSet, + ) -> Promise { + let parent_id = parent_id.unwrap_or(ROOT_POST_ID); + let id = self.posts.len(); + let author_id = env::predecessor_account_id(); + let editor_id = author_id.clone(); + require!( + self.is_allowed_to_use_labels( + Some(editor_id.clone()), + labels.iter().cloned().collect() + ), + "Cannot use these labels" + ); + + for label in &labels { + let mut other_posts = self.label_to_posts.get(label).unwrap_or_default(); + other_posts.insert(id); + self.label_to_posts.insert(label, &other_posts); + } + let post = Post { + id, + author_id: author_id.clone(), + likes: Default::default(), + snapshot: PostSnapshot { editor_id, timestamp: env::block_timestamp(), labels, body }, + snapshot_history: vec![], + }; + self.posts.push(&post.clone().into()); + self.post_to_parent.insert(&id, &parent_id); + + let mut siblings = self + .post_to_children + .get(&parent_id) + .unwrap_or_else(|| panic!("Parent id {} not found", parent_id)); + siblings.push(id); + self.post_to_children.insert(&parent_id, &siblings); + + // Don't forget to add an empty list of your own children. + self.post_to_children.insert(&id, &vec![]); + + let mut author_posts = self.authors.get(&author_id).unwrap_or_default(); + author_posts.insert(post.id); + self.authors.insert(&post.author_id, &author_posts); + + let desc = get_post_description(post.clone()); + + if parent_id != ROOT_POST_ID { + let parent_post: Post = self + .posts + .get(parent_id) + .unwrap_or_else(|| panic!("Parent post with id {} not found", parent_id)) + .into(); + let parent_author = parent_post.author_id; + notify::notify_reply(parent_id, parent_author); + } else { + repost::repost(post); + } + notify::notify_mentions(desc.as_str(), id) + } + + #[payable] + pub fn add_proposal( + &mut self, + body: VersionedProposalBody, + labels: HashSet, + ) -> Promise { + let id: ProposalId = self.proposals.len().try_into().unwrap(); + let author_id = env::predecessor_account_id(); + let editor_id = author_id.clone(); + + let proposal_body = body.clone().latest_version(); + + if proposal_body.linked_rfp.is_some() { + require!(labels.is_empty(), "Cannot add custom labels to this proposal. It inherits labels from the linked RFP. You should not add any labels to this proposal manually"); + } + + let labels = self.update_and_check_rfp_link(id, body.clone(), None, labels); + + require!( + self.is_allowed_to_use_labels( + Some(editor_id.clone()), + labels.iter().cloned().collect() + ), + "Cannot use these labels" + ); + + require!(self.proposal_categories.contains(&proposal_body.category), "Unknown category"); + + require!( + proposal_body.timeline.is_draft() || proposal_body.timeline.is_empty_review(), + "Cannot create proposal which is not in a draft or a review state" + ); + + for label in &labels { + let mut other_proposals = self.label_to_proposals.get(label).unwrap_or_default(); + other_proposals.insert(id); + self.label_to_proposals.insert(label, &other_proposals); + } + + let mut author_proposals = self.author_proposals.get(&author_id).unwrap_or_default(); + author_proposals.insert(id); + self.author_proposals.insert(&author_id, &author_proposals); + + let proposal = Proposal { + id: id, + author_id: author_id.clone(), + social_db_post_block_height: 0u64, + snapshot: ProposalSnapshot { + editor_id, + timestamp: env::block_timestamp(), + labels, + body: body.clone(), + }, + snapshot_history: vec![], + }; + + proposal::repost::publish_to_socialdb_feed( + Self::ext(env::current_account_id()) + .with_static_gas(env::prepaid_gas().saturating_div(4)) + .set_block_height_callback(proposal.clone()), + proposal::repost::proposal_repost_text(proposal.clone()), + ) + .then(notify::notify_proposal_subscribers(&proposal)) + } + + #[payable] + pub fn add_rfp(&mut self, body: VersionedRFPBody, labels: HashSet) -> Promise { + let id: RFPId = self.rfps.len().try_into().unwrap(); + let author_id = env::predecessor_account_id(); + let editor_id = author_id.clone(); + + let rfp_body = body.clone().latest_version(); + + require!( + self.is_allowed_to_write_rfps(editor_id.clone()), + "The account is not allowed to create RFPs" + ); + + require!( + rfp_body.timeline.is_accepting_submissions(), + "Cannot create proposal which is not in a Accepting Submissions state" + ); + + for label in &labels { + require!( + self.global_labels_info.get().get(label).is_some(), + format!("Label {} is not registered", label) + ); + + let mut other_rfps = self.label_to_rfps.get(label).unwrap_or_default(); + other_rfps.insert(id); + self.label_to_rfps.insert(label, &other_rfps); + } + + let rfp = RFP { + id: id, + author_id: env::predecessor_account_id(), + social_db_post_block_height: 0u64, + snapshot: RFPSnapshot { + editor_id: env::predecessor_account_id(), + timestamp: env::block_timestamp(), + block_height: env::block_height(), + labels, + body: body.clone(), + linked_proposals: HashSet::new(), + }, + snapshot_history: vec![], + }; + + proposal::repost::publish_to_socialdb_feed( + Self::ext(env::current_account_id()) + .with_static_gas(env::prepaid_gas().saturating_div(4)) + .set_rfp_block_height_callback(rfp.clone()), + rfp::repost::rfp_repost_text(rfp.clone()), + ) + .then(notify::notify_rfp_subscribers(&rfp, self.get_moderators())) + } + + #[private] + pub fn set_block_height_callback( + &mut self, + #[allow(unused_mut)] mut proposal: Proposal, + #[callback_unwrap] set_result: SetReturnType, + ) -> BlockHeightCallbackRetValue { + proposal.social_db_post_block_height = set_result.block_height.into(); + self.proposals.push(&proposal.clone().into()); + BlockHeightCallbackRetValue { proposal_id: proposal.id } + } + + pub fn set_rfp_block_height_callback( + &mut self, + #[allow(unused_mut)] mut rfp: RFP, + #[callback_unwrap] set_result: SetReturnType, + ) -> BlockHeightCallbackRetValue { + let ret_value = BlockHeightCallbackRetValue { proposal_id: rfp.id }; + rfp.social_db_post_block_height = set_result.block_height.into(); + self.rfps.push(&rfp.into()); + ret_value + } + + pub fn get_posts_by_author(&self, author: AccountId) -> Vec { + self.authors.get(&author).map(|posts| posts.into_iter().collect()).unwrap_or_default() + } + + pub fn get_posts_by_label(&self, label: String) -> Vec { + let mut res: Vec<_> = + self.label_to_posts.get(&label).unwrap_or_default().into_iter().collect(); + res.sort(); + res + } + + pub fn get_proposals_by_author(&self, author: AccountId) -> Vec { + self.author_proposals + .get(&author) + .map(|proposals| proposals.into_iter().collect()) + .unwrap_or_default() + } + + pub fn get_proposals_by_label(&self, label: String) -> Vec { + let mut res: Vec<_> = + self.label_to_proposals.get(&label).unwrap_or_default().into_iter().collect(); + res.sort(); + res + } + + pub fn get_rfps_by_label(&self, label: String) -> Vec { + let mut res: Vec<_> = + self.label_to_rfps.get(&label).unwrap_or_default().into_iter().collect(); + res.sort(); + res + } + + pub fn get_all_labels(&self) -> Vec { + let mut res: Vec<_> = self.label_to_posts.keys().collect(); + res.sort(); + res + } + + pub fn get_all_proposal_labels(&self) -> Vec { + let mut res: Vec<_> = self.label_to_proposals.keys().collect(); + res.sort(); + res + } + + pub fn get_all_authors(&self) -> Vec { + let mut res: Vec<_> = self.authors.keys().collect(); + res.sort(); + res + } + + pub fn get_all_proposal_authors(&self) -> Vec { + let mut res: Vec<_> = self.author_proposals.keys().collect(); + res.sort(); + res + } + + pub fn is_allowed_to_edit_proposal( + &self, + proposal_id: ProposalId, + editor: Option, + ) -> bool { + let proposal: Proposal = self + .proposals + .get(proposal_id.try_into().unwrap()) + .unwrap_or_else(|| panic!("Proposal id {} not found", proposal_id)) + .into(); + let editor = editor.unwrap_or_else(env::predecessor_account_id); + // First check for simple cases. + if editor == env::current_account_id() || editor == proposal.author_id { + return true; + } + + // Then check for complex case. + self.access_control + .members_list + .check_permissions(editor, proposal.snapshot.labels.into_iter().collect::>()) + .contains(&ActionType::EditPost) + } + + pub fn is_allowed_to_write_rfps(&self, editor: AccountId) -> bool { + editor == env::current_account_id() || self.has_moderator(editor) + } + + pub fn is_allowed_to_edit(&self, post_id: PostId, editor: Option) -> bool { + let post: Post = self + .posts + .get(post_id) + .unwrap_or_else(|| panic!("Post id {} not found", post_id)) + .into(); + let editor = editor.unwrap_or_else(env::predecessor_account_id); + // First check for simple cases. + if editor == env::current_account_id() || editor == post.author_id { + return true; + } + + // Then check for complex case. + self.access_control + .members_list + .check_permissions(editor, post.snapshot.labels.into_iter().collect::>()) + .contains(&ActionType::EditPost) + } + + pub fn is_allowed_to_use_labels(&self, editor: Option, labels: Vec) -> bool { + let editor = editor.unwrap_or_else(env::predecessor_account_id); + // First check for simple cases. + if editor == env::current_account_id() { + return true; + } + let restricted_labels = self.access_control.rules_list.find_restricted(&labels); + if restricted_labels.is_empty() { + return true; + } + self.access_control + .members_list + .check_permissions(editor, labels) + .contains(&ActionType::UseLabels) + } + + fn filtered_labels( + &self, + labels_to_t: &UnorderedMap, + editor: &AccountId, + ) -> Vec + where + T: near_sdk::borsh::BorshSerialize + near_sdk::borsh::BorshDeserialize, + { + let filtered: HashSet = labels_to_t + .keys() + .filter(|label| { + self.is_allowed_to_use_labels(Some(editor.clone()), vec![label.clone()]) + }) + .collect(); + let mut res: Vec<_> = filtered.into_iter().collect(); + res.sort(); + res + } + + pub fn get_all_allowed_labels(&self, editor: AccountId) -> Vec { + self.filtered_labels(&self.label_to_posts, &editor) + } + + pub fn get_all_allowed_proposal_labels(&self, editor: AccountId) -> Vec { + self.filtered_labels(&self.label_to_proposals, &editor) + } + + #[payable] + pub fn edit_post(&mut self, id: PostId, body: PostBody, labels: HashSet) -> Promise { + require!( + self.is_allowed_to_edit(id, Option::None), + "The account is not allowed to edit this post" + ); + let editor_id = env::predecessor_account_id(); + let mut post: Post = + self.posts.get(id).unwrap_or_else(|| panic!("Post id {} not found", id)).into(); + + let old_snapshot = post.snapshot.clone(); + let old_labels_set = old_snapshot.labels.clone(); + let new_labels = labels; + let new_snapshot = PostSnapshot { + editor_id: editor_id.clone(), + timestamp: env::block_timestamp(), + labels: new_labels.clone(), + body, + }; + post.snapshot = new_snapshot; + post.snapshot_history.push(old_snapshot); + let post_author = post.author_id.clone(); + self.posts.replace(id, &post.into()); + + // Update labels index. + let new_labels_set = new_labels; + let labels_to_remove = &old_labels_set - &new_labels_set; + let labels_to_add = &new_labels_set - &old_labels_set; + require!( + self.is_allowed_to_use_labels( + Some(editor_id.clone()), + labels_to_remove.iter().cloned().collect() + ), + "Not allowed to remove these labels" + ); + require!( + self.is_allowed_to_use_labels( + Some(editor_id.clone()), + labels_to_add.iter().cloned().collect() + ), + "Not allowed to add these labels" + ); + + for label_to_remove in labels_to_remove { + let mut posts = self.label_to_posts.get(&label_to_remove).unwrap(); + posts.remove(&id); + self.label_to_posts.insert(&label_to_remove, &posts); + } + + for label_to_add in labels_to_add { + let mut posts = self.label_to_posts.get(&label_to_add).unwrap_or_default(); + posts.insert(id); + self.label_to_posts.insert(&label_to_add, &posts); + } + + notify::notify_edit(id, post_author) + } + + #[payable] + pub fn create_community( + &mut self, + #[allow(unused_mut)] mut inputs: CommunityInputs, + ) -> Promise { + require!( + self.get_community(inputs.handle.to_owned()).is_none(), + "Community already exists" + ); + + require!( + env::attached_deposit() >= CREATE_COMMUNITY_BALANCE, + "Require 4 NEAR to create community" + ); + + require!(env::prepaid_gas() >= CREATE_COMMUNITY_GAS, "Require at least 200 Tgas"); + + let mut new_community = Community { + admins: vec![], + handle: inputs.handle.clone(), + name: inputs.name, + tag: inputs.tag, + description: inputs.description, + logo_url: inputs.logo_url, + banner_url: inputs.banner_url, + bio_markdown: inputs.bio_markdown, + github_handle: None, + telegram_handle: None, + twitter_handle: None, + website_url: None, + addons: vec![ + CommunityAddOn { + id: "announcements".to_string(), + addon_id: "announcements".to_string(), + display_name: "Announcements".to_string(), + enabled: true, + parameters: "".to_string(), + }, + CommunityAddOn { + id: "discussions".to_string(), + addon_id: "discussions".to_string(), + display_name: "Discussions".to_string(), + enabled: true, + parameters: "".to_string(), + }, + ], + }; + + new_community.validate(); + new_community.set_default_admin(); + self.communities.insert(&new_community.handle, &new_community); + + ext_devhub_community_factory::ext(get_devhub_community_factory()) + .with_unused_gas_weight(1) + .with_attached_deposit(CREATE_COMMUNITY_BALANCE) + .create_community_account(new_community.handle.clone()) + } + + #[payable] + pub fn edit_proposal( + &mut self, + id: ProposalId, + body: VersionedProposalBody, + labels: HashSet, + ) -> ProposalId { + let proposal_body = body.clone().latest_version(); + if proposal_body.linked_rfp.is_some() { + require!(labels.len() == 0, "Cannot edit labels of a proposal linked to RFP. It inherits labels from the linked RFP."); + } + self.edit_proposal_internal(id, body.clone(), labels) + } + + #[payable] + pub fn edit_proposal_timeline( + &mut self, + id: ProposalId, + timeline: TimelineStatus, + ) -> ProposalId { + let proposal: Proposal = self + .proposals + .get(id.into()) + .unwrap_or_else(|| panic!("Proposal id {} not found", id)) + .into(); + let mut body = proposal.snapshot.body.latest_version(); + body.timeline = timeline; + + self.edit_proposal_internal(id, body.into(), proposal.snapshot.labels) + } + + #[payable] + pub fn edit_proposal_linked_rfp( + &mut self, + id: ProposalId, + rfp_id: Option, + ) -> ProposalId { + let proposal: Proposal = self + .proposals + .get(id.into()) + .unwrap_or_else(|| panic!("Proposal id {} not found", id)) + .into(); + let mut body = proposal.snapshot.body.latest_version(); + body.linked_rfp = rfp_id; + + self.edit_proposal_internal(id, body.into(), proposal.snapshot.labels) + } + + #[payable] + pub fn edit_rfp( + &mut self, + id: RFPId, + body: VersionedRFPBody, + labels: HashSet, + ) -> RFPId { + self.edit_rfp_internal(id, body.clone(), labels) + } + + #[payable] + pub fn cancel_rfp( + &mut self, + id: RFPId, + proposals_to_cancel: Vec, + proposals_to_unlink: Vec, + ) -> RFPId { + for proposal_id in proposals_to_cancel { + let proposal: Proposal = self.get_proposal(proposal_id).into(); + let proposal_timeline = proposal.snapshot.body.latest_version().timeline; + let review_status = proposal_timeline.get_review_status().clone(); + self.edit_proposal_timeline(proposal_id, TimelineStatus::Cancelled(review_status)); + } + + for proposal_id in proposals_to_unlink { + self.edit_proposal_linked_rfp(proposal_id, None); + } + + self.edit_rfp_timeline(id, RFPTimelineStatus::Cancelled) + } + + #[payable] + pub fn edit_rfp_timeline(&mut self, id: RFPId, timeline: RFPTimelineStatus) -> ProposalId { + let rfp: RFP = self.get_rfp(id).into(); + let mut body = rfp.snapshot.body.latest_version(); + body.timeline = timeline; + + self.edit_rfp_internal(id, body.into(), rfp.snapshot.labels) + } + + pub fn get_allowed_categories(&self) -> Vec { + self.proposal_categories.clone() + } + + pub fn get_global_labels(&self) -> Vec { + let mut result: Vec = self + .global_labels_info + .iter() + .map(|(label, label_info)| LabelInfoExtended { + value: label.clone(), + title: label_info.title.clone(), + color: label_info.color.clone(), + }) + .collect(); + result.sort_by(|a, b| a.value.cmp(&b.value)); + result + } + + pub fn get_rfp_linked_proposals(&self, rfp_id: RFPId) -> Vec { + self.get_linked_proposals_in_rfp(rfp_id).into_iter().collect() + } + + #[payable] + pub fn set_global_labels(&mut self, labels: Vec) { + let editor_id = env::predecessor_account_id(); + require!( + self.has_moderator(editor_id.clone()) || editor_id.clone() == env::current_account_id(), + "Only the admin and moderators can set labels" + ); + + self.global_labels_info.clear(); + + for label in labels { + let label_info = LabelInfo { title: label.title, color: label.color }; + (*self.global_labels_info).insert(label.value, label_info); + } + } + + #[payable] + pub fn set_allowed_categories(&mut self, new_categories: Vec) { + let editor_id = env::predecessor_account_id(); + require!( + self.has_moderator(editor_id.clone()) || editor_id.clone() == env::current_account_id(), + "Only the admin and moderators can set categories" + ); + self.proposal_categories = new_categories; + } + + pub fn get_community(&self, handle: CommunityHandle) -> Option { + self.communities.get(&handle) + } + + pub fn get_community_metadata(&self, handle: CommunityHandle) -> Option { + self.communities.get(&handle).map(|community| CommunityMetadata { + admins: community.admins, + handle: community.handle, + name: community.name, + tag: community.tag, + description: community.description, + logo_url: community.logo_url, + banner_url: community.banner_url, + bio_markdown: community.bio_markdown, + }) + } + + pub fn get_account_community_permissions( + &self, + account_id: AccountId, + community_handle: CommunityHandle, + ) -> CommunityPermissions { + let community = self.get_community(community_handle.to_owned()).expect( + format!("Community with handle `{}` does not exist", community_handle).as_str(), + ); + + CommunityPermissions { + can_configure: community.admins.contains(&account_id) + || self.has_moderator(account_id.to_owned()), + + can_delete: self.has_moderator(account_id), + } + } + + pub fn get_all_communities_metadata(&self) -> Vec { + self.communities + .iter() + .map(|(handle, community)| CommunityMetadata { + admins: community.admins, + handle, + name: community.name, + tag: community.tag, + description: community.description, + logo_url: community.logo_url, + banner_url: community.banner_url, + bio_markdown: community.bio_markdown, + }) + .collect() + } + + pub fn get_addon(&self, id: AddOnId) -> Option { + self.available_addons.get(&id) + } + + pub fn get_all_addons(&self) -> Vec { + self.available_addons.iter().map(|(_id, add_on)| add_on).collect() + } + + // Only the contract admin and DevHub moderators + pub fn create_addon(&mut self, addon: AddOn) { + if !self.has_moderator(env::predecessor_account_id()) + && env::predecessor_account_id() != env::current_account_id() + { + panic!("Only the admin and moderators can create new add-ons"); + } + if self.get_addon(addon.id.to_owned()).is_some() { + panic!("Add-on with this id already exists"); + } + addon.validate(); + self.available_addons.insert(&addon.id.clone(), &addon); + } + + // ONLY FOR TESTING + pub fn delete_addon(&mut self, id: AddOnId) { + // Also delete from communities + if !self.has_moderator(env::predecessor_account_id()) + && env::predecessor_account_id() != env::current_account_id() + { + panic!("Only the admin and moderators can delete add-ons"); + } + let addon = self + .get_addon(id.clone()) + .expect(&format!("Add-on with id `{}` does not exist", id)) + .clone(); + + self.available_addons.remove(&addon.id); + } + + pub fn update_addon(&mut self, addon: AddOn) { + if !self.has_moderator(env::predecessor_account_id()) + && env::predecessor_account_id() != env::current_account_id() + { + panic!("Only the admin and moderators can edit add-ons"); + } + self.available_addons.insert(&addon.id.clone(), &addon); + } + + pub fn get_community_addons(&self, handle: CommunityHandle) -> Vec { + let community = self + .get_community(handle.clone()) + .expect(format!("Community not found with handle `{}`", handle).as_str()); + return community.addons; + } + + pub fn set_community_addons( + &mut self, + handle: CommunityHandle, + addons: Vec, + ) -> Promise { + let mut community = self + .get_community(handle.clone()) + .expect(format!("Community not found with handle `{}`", handle).as_str()); + community.addons = addons; + self.update_community(handle, community) + } + + // To add or update parameters set by the configurator widget + pub fn set_community_addon( + &mut self, + handle: CommunityHandle, + community_addon: CommunityAddOn, + ) -> Promise { + let mut community = self + .get_community(handle.clone()) + .expect(format!("Community not found with handle `{}`", handle).as_str()); + if let Some(existing_addon) = + community.addons.iter_mut().find(|current| current.id == community_addon.id) + { + *existing_addon = community_addon; + } else { + community.addons.push(community_addon); + } + self.update_community(handle, community) + } + + fn get_editable_community(&self, handle: &CommunityHandle) -> Option { + if self + .get_account_community_permissions(env::predecessor_account_id(), handle.to_owned()) + .can_configure + { + return self.get_community(handle.to_owned()); + } else { + return None; + }; + } + + pub fn update_community( + &mut self, + handle: CommunityHandle, + #[allow(unused_mut)] mut community: Community, + ) -> Promise { + let _ = self + .get_editable_community(&handle) + .expect("Only community admins and hub moderators can configure communities"); + + community.validate(); + community.set_default_admin(); + + require!(community.handle == handle, "Community handle cannot be changed"); + require!(env::prepaid_gas() >= UPDATE_COMMUNITY_GAS, "Require at least 30 Tgas"); + self.communities.insert(&handle, &community); + let community_page_link = + format!("/devhub.near/widget/app?page=community&handle={}", community.handle); + social_db_contract().with_unused_gas_weight(1).set(json!({ + get_devhub_community_account(&community.handle): { + "profile": { + "name": community.name, + "image": { + "url": community.logo_url, + }, + "linktree": { + "twitter": community.twitter_handle, + "github": community.github_handle, + "telegram": community.telegram_handle, + "website": format!("near.social{community_page_link}"), + }, + "description": format!( + "{}\n\nLearn more about our community [on DevHub]({}).", + community.bio_markdown.as_ref().unwrap_or(&community.description), + community_page_link + ), + "backgroundImage": { + "url": community.banner_url, + }, + "tags": { + "community": "", + "announcements": "", + &community.handle: "", + } + } + }, + get_devhub_discussions_account(&community.handle): { + "profile": { + "name": format!("{} (Community Discussions)", community.name), + "image": { + "url": community.logo_url, + }, + "linktree": { + "twitter": community.twitter_handle, + "github": community.github_handle, + "telegram": community.telegram_handle, + "website": format!("near.social{community_page_link}"), + }, + "description": format!("{}\n\nLearn more about our community [on DevHub]({}).", community.description, community_page_link), + "backgroundImage": { + "url": community.banner_url, + }, + "tags": { + "community": "", + "discussions": "", + &community.handle: "", + } + } + } + })) + } + + pub fn set_community_socialdb(&mut self, handle: CommunityHandle, data: Value) -> Promise { + let _ = self + .get_editable_community(&handle) + .expect("Only community admins and hub moderators can set community Social DB"); + + require!(env::prepaid_gas() >= SET_COMMUNITY_SOCIALDB_GAS, "Require at least 30 Tgas"); + social_db_contract() + .with_unused_gas_weight(1) + .set(json!({ get_devhub_community_account(&handle): data })) + } + + pub fn create_discussion(&mut self, handle: CommunityHandle, block_height: Number) -> Promise { + require!(env::prepaid_gas() >= CREATE_DISCUSSION_GAS, "Require at least 30 Tgas"); + + let post_initiator = env::predecessor_account_id(); + let repost = format!("[{{\"key\":\"main\",\"value\":{{\"type\":\"repost\",\"item\":{{\"type\":\"social\",\"path\":\"{}/post/main\",\"blockHeight\":{}}}}}}},{{\"key\":{{\"type\":\"social\",\"path\":\"{}/post/main\",\"blockHeight\":{}}},\"value\":{{\"type\":\"repost\"}}}}]", post_initiator, block_height, post_initiator, block_height); + let notify = format!("{{\"key\":\"{}\",\"value\":{{\"type\":\"repost\",\"item\":{{\"type\":\"social\",\"path\":\"{}/post/main\",\"blockHeight\":{}}}}}}}", post_initiator, post_initiator, block_height); + social_db_contract().with_unused_gas_weight(1).set( + json!({ get_devhub_discussions_account(&handle): { + "index": { + "repost": repost, + "notify": notify + } + } }), + ) + } + + pub fn delete_community(&mut self, handle: CommunityHandle) -> Promise { + require!( + self.has_moderator(env::predecessor_account_id()), + "Only moderators can delete community" + ); + + let community = self + .get_community(handle.clone()) + .expect(&format!("Community with handle `{}` does not exist", handle)); + + self.communities.remove(&community.handle); + + require!(env::prepaid_gas() >= DELETE_COMMUNITY_GAS, "Require at least 30 Tgas"); + ext_devhub_community::ext(get_devhub_community_account(&community.handle).parse().unwrap()) + .with_unused_gas_weight(1) + .destroy() + } + + pub fn set_featured_communities(&mut self, handles: Vec) { + require!( + self.has_moderator(env::predecessor_account_id()), + "Only moderators can add featured communities" + ); + + // Check if every handle corresponds to an existing community + for handle in &handles { + require!(self.communities.get(&handle).is_some(), "Community does not exist."); + } + + // Replace the existing featured communities with the new ones + self.featured_communities = + handles.into_iter().map(|handle| FeaturedCommunity { handle }).collect(); + } + + pub fn get_featured_communities(&self) -> Vec { + self.featured_communities + .iter() + .filter_map(|fc| self.get_community(fc.handle.clone())) + .collect() + } + + fn get_moderators(&self) -> HashSet { + let mut moderators: HashSet = HashSet::new(); + for m in self.access_control.members_list.get_moderators() { + if let Member::Account(account_id) = m { + moderators.insert(account_id.clone()); + } + } + moderators.insert(env::current_account_id()); + moderators + } + + pub fn has_moderator(&self, account_id: AccountId) -> bool { + let moderators = self.access_control.members_list.get_moderators(); + moderators.contains(&Member::Account(account_id)) + } + + pub fn web4_get(&self, request: Web4Request) -> Web4Response { + web4::handler::web4_get(self, request) + } + + pub fn set_social_db_profile_announcement(&self, announcement: String) -> Promise { + let editor = env::predecessor_account_id(); + require!( + editor == env::current_account_id() || self.has_moderator(editor), + "Permission denied" + ); + social_db_contract() + .with_static_gas(env::prepaid_gas().saturating_div(3)) + .with_attached_deposit(env::attached_deposit()) + .set(json!({ + env::current_account_id(): { + "profile": { + "announcement": announcement + } + } + })) + } +} + +#[near] +pub struct LabelInfo { + title: Option, + color: Option<(u8, u8, u8)>, +} + +#[near(serializers=[borsh, json])] +pub struct LabelInfoExtended { + value: String, + title: Option, + color: Option<(u8, u8, u8)>, +} + +#[derive(Copy, Clone, Serialize, Deserialize, NearSchema)] +#[serde(crate = "near_sdk::serde")] +pub struct BlockHeightCallbackRetValue { + proposal_id: ProposalId, +} + +#[cfg(all(test, not(target_arch = "wasm32")))] +mod tests { + use crate::community::AddOn; + + use crate::{PostBody, ProposalBodyV0, VersionedProposalBody}; + + use near_sdk::test_utils::{get_created_receipts, VMContextBuilder}; + use near_sdk::{testing_env, VMContext}; + use serde_json::json; + use std::collections::HashSet; + use std::convert::TryInto; + + use super::Contract; + + fn get_context(is_view: bool) -> VMContext { + get_context_with_signer(is_view, "bob.near".to_string()) + } + + fn get_context_with_signer(is_view: bool, signer: String) -> VMContext { + VMContextBuilder::new() + .signer_account_id(signer.clone().try_into().unwrap()) + .current_account_id(signer.try_into().unwrap()) + .is_view(is_view) + .build() + } + + fn get_context_with_current(is_view: bool, signer: String) -> VMContext { + VMContextBuilder::new() + .current_account_id(signer.try_into().unwrap()) + .is_view(is_view) + .build() + } + + #[allow(dead_code)] + fn get_context_with_predecessor(is_view: bool, signer: String) -> VMContext { + VMContextBuilder::new() + .predecessor_account_id(signer.try_into().unwrap()) + .is_view(is_view) + .build() + } + + #[test] + pub fn test_add_proposal() { + let context = get_context(false); + testing_env!(context); + let mut contract = Contract::new(); + + let body: ProposalBodyV0 = near_sdk::serde_json::from_value(json!({ + "proposal_body_version": "V0", + "name": "another post", + "description": "Hello to @petersalomonsen.near and @psalomo.near. This is an idea with mentions.", "category": "Marketing", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "payouts": [], + "timeline": {"status": "DRAFT"} + })).unwrap(); + contract.add_proposal(VersionedProposalBody::V0(body), HashSet::new()); + let receipts = get_created_receipts(); + assert_eq!(3, receipts.len()); + + if let near_sdk::mock::MockAction::FunctionCallWeight { method_name, args, .. } = + &receipts[2].actions[0] + { + assert_eq!(method_name, b"set"); + assert_eq!(args, b"{\"data\":{\"bob.near\":{\"index\":{\"notify\":\"[{\\\"key\\\":\\\"petersalomonsen.near\\\",\\\"value\\\":{\\\"type\\\":\\\"proposal/mention\\\",\\\"proposal\\\":0,\\\"widgetAccountId\\\":\\\"bob.near\\\",\\\"notifier\\\":\\\"bob.near\\\"}},{\\\"key\\\":\\\"psalomo.near.\\\",\\\"value\\\":{\\\"type\\\":\\\"proposal/mention\\\",\\\"proposal\\\":0,\\\"widgetAccountId\\\":\\\"bob.near\\\",\\\"notifier\\\":\\\"bob.near\\\"}},{\\\"key\\\":\\\"frol.near\\\",\\\"value\\\":{\\\"type\\\":\\\"proposal/mention\\\",\\\"proposal\\\":0,\\\"widgetAccountId\\\":\\\"bob.near\\\",\\\"notifier\\\":\\\"bob.near\\\"}},{\\\"key\\\":\\\"neardevdao.near\\\",\\\"value\\\":{\\\"type\\\":\\\"proposal/mention\\\",\\\"proposal\\\":0,\\\"widgetAccountId\\\":\\\"bob.near\\\",\\\"notifier\\\":\\\"bob.near\\\"}}]\"}}}}"); + } else { + assert!(false, "Expected a function call ...") + } + } + + #[test] + pub fn test_add_post_with_mention() { + let context = get_context(false); + testing_env!(context); + let mut contract = Contract::new(); + + let body: PostBody = near_sdk::serde_json::from_str(r#" + { + "name": "another post", + "description": "Hello to @petersalomonsen.near and @psalomo.near. This is an idea with mentions.", + "post_type": "Idea", + "idea_version": "V1" + }"#).unwrap(); + contract.add_post(None, body, HashSet::new()); + let receipts = get_created_receipts(); + assert_eq!(2, receipts.len()); + + // Extract the method_name and args values + if let near_sdk::mock::MockAction::FunctionCallWeight { method_name, args, .. } = + &receipts[1].actions[0] + { + assert_eq!(method_name, b"set"); + assert_eq!(args, b"{\"data\":{\"bob.near\":{\"index\":{\"notify\":\"[{\\\"key\\\":\\\"petersalomonsen.near\\\",\\\"value\\\":{\\\"type\\\":\\\"devgovgigs/mention\\\",\\\"post\\\":0}},{\\\"key\\\":\\\"psalomo.near.\\\",\\\"value\\\":{\\\"type\\\":\\\"devgovgigs/mention\\\",\\\"post\\\":0}}]\"}}}}"); + } else { + assert!(false, "Expected a function call ...") + } + } + + #[test] + pub fn test_create_addon() { + let context = get_context_with_current(false, "bob.near".to_string()); + testing_env!(context); + + let mut contract = Contract::new(); + let input = fake_addon("CommunityAddOnId".to_string()); + contract.create_addon(input.to_owned()); + + let addon = contract.get_addon("CommunityAddOnId".to_owned()); + + assert_eq!(addon, Some(input)) + } + + pub fn fake_addon(id: String) -> AddOn { + let input = AddOn { + id: id.to_owned(), + title: "GitHub AddOn".to_owned(), + description: "Current status of NEARCORE repo".to_owned(), + view_widget: "custom-viewer-widget".to_owned(), + configurator_widget: "github-configurator".to_owned(), + icon: "bi bi-github".to_owned(), + }; + return input; + } + + #[test] + pub fn test_get_all_addons() { + let context = get_context_with_current(false, "bob.near".to_string()); + testing_env!(context); + let mut contract = Contract::new(); + let input = fake_addon("CommunityAddOnId".to_string()); + contract.create_addon(input.to_owned()); + + let addons = contract.get_all_addons(); + + assert_eq!(addons[0], input) + } + + #[test] + pub fn test_get_addon() { + let context = get_context_with_current(false, "bob.near".to_string()); + testing_env!(context); + let mut contract = Contract::new(); + let input = fake_addon("CommunityAddOnId".to_string()); + contract.create_addon(input.to_owned()); + + let addon = contract.get_addon("CommunityAddOnId".to_owned()); + + assert_eq!(addon, Some(input)) + } + + #[test] + pub fn test_update_addon() { + let context = get_context(false); + testing_env!(context); + let mut contract = Contract::new(); + let input = fake_addon("test".to_owned()); + contract.create_addon(input.to_owned()); + + contract.update_addon(AddOn { title: "Telegram AddOn".to_owned(), ..input }); + + let addons = contract.get_all_addons(); + + assert_eq!(addons[0].title, "Telegram AddOn".to_owned()); + } +} diff --git a/src/migrations.rs b/src/migrations.rs new file mode 100644 index 0000000..1281d60 --- /dev/null +++ b/src/migrations.rs @@ -0,0 +1,826 @@ +//! Public methods of data model/state migrations between the versions. +//! Should be invocable only by the owner and in most cases should be called only once though the +//! latter is not asserted. + +use crate::*; +use near_sdk::{borsh::to_vec, env, near, NearToken, Promise}; +use near_sdk::store::Lazy; +use std::cmp::min; +use std::collections::{HashSet, HashMap}; + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV1 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, +} + +// From ContractV1 to ContractV2 +impl Contract { + fn unsafe_add_acl() { + let ContractV1 { posts, post_to_parent, post_to_children, label_to_posts } = + env::state_read().unwrap(); + env::state_write(&ContractV2 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control: Default::default(), + }); + } +} + +// // Fake vector purely for the sake of overriding initialization. +// #[derive(BorshSerialize, BorshDeserialize)] +// pub struct FakeVector { +// len: u64, +// prefix: Vec, +// } +// +// impl FakeVector { +// pub fn new(len: u64, prefix: S) -> Self +// where +// S: IntoStorageKey, +// { +// Self { len, prefix: prefix.into_storage_key() } +// } +// } + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV2 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, +} + +// From ContractV2 to ContractV3 +impl Contract { + fn unsafe_add_post_authors() { + let ContractV2 { posts, post_to_parent, post_to_children, label_to_posts, access_control } = + env::state_read().unwrap(); + let authors = UnorderedMap::new(StorageKey::AuthorToAuthorPosts); + + env::state_write(&ContractV3 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + }); + } + + fn unsafe_insert_old_post_authors(start: u64, end: u64) -> StateVersion { + let mut contract: ContractV3 = env::state_read().unwrap(); + let total = contract.posts.len(); + let end = min(total, end); + for i in start..end { + let versioned_post = contract.posts.get(i); + if let Some(versioned_post) = versioned_post { + let post: Post = versioned_post.into(); + let mut author_posts = + contract.authors.get(&post.author_id).unwrap_or_else(|| HashSet::new()); + author_posts.insert(post.id); + contract.authors.insert(&post.author_id, &author_posts); + } + } + env::state_write(&contract); + StateVersion::V3 { done: end == total, migrated_count: end } + } +} + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV3 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, + pub authors: UnorderedMap>, +} + +// From ContractV3 to ContractV4 +impl Contract { + fn unsafe_add_communities() { + let ContractV3 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + } = env::state_read().unwrap(); + env::state_write(&ContractV4 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + communities: UnorderedMap::new(StorageKey::Communities), + }); + } +} + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV4 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, + pub authors: UnorderedMap>, + pub communities: UnorderedMap, +} + +// From ContractV4 to ContractV5 +impl Contract { + fn unsafe_add_featured_communities() { + let ContractV4 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + communities, + } = env::state_read().unwrap(); + env::state_write(&ContractV5 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + communities, + featured_communities: Vec::new(), + }); + } +} + +#[near] +#[derive(Clone)] +pub struct CommunityV1 { + pub handle: CommunityHandle, + pub admins: Vec, + pub name: String, + pub description: String, + pub bio_markdown: Option, + pub logo_url: String, + pub banner_url: String, + pub tag: String, + pub github_handle: Option, + pub telegram_handle: Option, + pub twitter_handle: Option, + pub website_url: Option, + /// JSON string of github board configuration + pub github: Option, + pub sponsorship: Option, + pub wiki1: Option, + pub wiki2: Option, +} + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV5 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, + pub authors: UnorderedMap>, + pub communities: UnorderedMap, + pub featured_communities: Vec, +} + +// From ContractV5 to ContractV6 +impl Contract { + fn unsafe_multiple_telegrams() { + let ContractV5 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + mut communities, + featured_communities, + } = env::state_read().unwrap(); + + let migrated_communities: Vec<(String, CommunityV2)> = communities + .iter() + .map(|(community_handle, community)| { + ( + community_handle, + CommunityV2 { + handle: community.handle, + admins: community.admins, + name: community.name, + description: community.description, + bio_markdown: community.bio_markdown, + logo_url: community.logo_url, + banner_url: community.banner_url, + tag: community.tag, + github_handle: community.github_handle, + telegram_handle: community.telegram_handle.iter().cloned().collect(), + twitter_handle: community.twitter_handle, + website_url: community.website_url, + github: community.github, + sponsorship: community.sponsorship, + wiki1: community.wiki1, + wiki2: community.wiki2, + }, + ) + }) + .collect(); + + communities.clear(); + + let mut communities_new = UnorderedMap::new(StorageKey::Communities); + + for (k, v) in migrated_communities { + communities_new.insert(&k, &v); + } + + env::state_write(&ContractV6 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + communities: communities_new, + featured_communities, + }); + } +} + +#[near] +#[derive(Clone)] +pub struct CommunityV2 { + pub handle: CommunityHandle, + pub admins: Vec, + pub name: String, + pub description: String, + pub bio_markdown: Option, + pub logo_url: String, + pub banner_url: String, + pub tag: String, + pub github_handle: Option, + pub telegram_handle: Vec, + pub twitter_handle: Option, + pub website_url: Option, + /// JSON string of github board configuration + pub github: Option, + pub sponsorship: Option, + pub wiki1: Option, + pub wiki2: Option, +} + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV6 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, + pub authors: UnorderedMap>, + pub communities: UnorderedMap, + pub featured_communities: Vec, +} + +// From ContractV6 to ContractV7 +impl Contract { + fn unsafe_add_board_and_feature_flags() { + let ContractV6 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + mut communities, + featured_communities, + } = env::state_read().unwrap(); + + let migrated_communities: Vec<(String, CommunityV3)> = communities + .iter() + .map(|(community_handle, community)| { + ( + community_handle, + CommunityV3 { + admins: community.admins, + handle: community.handle, + name: community.name, + tag: community.tag, + description: community.description, + logo_url: community.logo_url, + banner_url: community.banner_url, + bio_markdown: community.bio_markdown, + github_handle: community.github_handle, + telegram_handle: community.telegram_handle, + twitter_handle: community.twitter_handle, + website_url: community.website_url, + github: community.github, + board: None, + wiki1: community.wiki1, + wiki2: community.wiki2, + + features: CommunityFeatureFlags { + telegram: true, + github: true, + board: true, + wiki: true, + }, + }, + ) + }) + .collect(); + + communities.clear(); + + let mut communities_new = UnorderedMap::new(StorageKey::Communities); + + for (k, v) in migrated_communities { + communities_new.insert(&k, &v); + } + + env::state_write(&ContractV7 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + communities: communities_new, + featured_communities, + }); + } +} + +#[near] +#[derive(Clone)] +pub struct CommunityV3 { + pub admins: Vec, + pub handle: CommunityHandle, + pub name: String, + pub tag: String, + pub description: String, + pub logo_url: String, + pub banner_url: String, + pub bio_markdown: Option, + pub github_handle: Option, + pub telegram_handle: Vec, + pub twitter_handle: Option, + pub website_url: Option, + /// JSON string of github board configuration + pub github: Option, + /// JSON string of kanban board configuration + pub board: Option, + pub wiki1: Option, + pub wiki2: Option, + pub features: CommunityFeatureFlags, +} + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV7 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, + pub authors: UnorderedMap>, + pub communities: UnorderedMap, + pub featured_communities: Vec, +} + +// From ContractV7 to ContractV8 +impl Contract { + fn unsafe_add_community_addons() { + let ContractV7 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + mut communities, + featured_communities, + } = env::state_read().unwrap(); + + let migrated_communities: Vec<(String, CommunityV4)> = communities + .iter() + .map(|(community_handle, community)| { + ( + community_handle, + CommunityV4 { + admins: community.admins, + handle: community.handle, + name: community.name, + tag: community.tag, + description: community.description, + logo_url: community.logo_url, + banner_url: community.banner_url, + bio_markdown: community.bio_markdown, + github_handle: community.github_handle, + telegram_handle: community.telegram_handle, + twitter_handle: community.twitter_handle, + website_url: community.website_url, + github: community.github, + board: None, + wiki1: community.wiki1, + wiki2: community.wiki2, + features: community.features, + addons: Vec::new(), + }, + ) + }) + .collect(); + + communities.clear(); + + let mut communities_new = UnorderedMap::new(StorageKey::Communities); + + for (k, v) in migrated_communities { + communities_new.insert(&k, &v); + } + + env::state_write(&ContractV8 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + communities: communities_new, + featured_communities, + available_addons: UnorderedMap::new(StorageKey::AddOns), + }); + } +} + +#[near] +pub struct CommunityV4 { + pub admins: Vec, + pub handle: CommunityHandle, + pub name: String, + pub tag: String, + pub description: String, + pub logo_url: String, + pub banner_url: String, + pub bio_markdown: Option, + pub github_handle: Option, + pub telegram_handle: Vec, + pub twitter_handle: Option, + pub website_url: Option, + /// JSON string of github board configuration + pub github: Option, + /// JSON string of kanban board configuration + pub board: Option, + pub wiki1: Option, + pub wiki2: Option, + pub features: CommunityFeatureFlags, + pub addons: Vec, +} + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV8 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, + pub authors: UnorderedMap>, + pub communities: UnorderedMap, + pub featured_communities: Vec, + pub available_addons: UnorderedMap, +} + +// From ContractV8 to ContractV9 +impl Contract { + fn unsafe_clean_up_community() { + let ContractV8 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + mut communities, + featured_communities, + available_addons, + } = env::state_read().unwrap(); + let migrated_communities: Vec<(String, CommunityV5)> = communities + .iter() + .map(|(community_handle, community)| { + ( + community_handle, + CommunityV5 { + admins: community.admins, + handle: community.handle, + name: community.name, + tag: community.tag, + description: community.description, + logo_url: community.logo_url, + banner_url: community.banner_url, + bio_markdown: community.bio_markdown, + github_handle: community.github_handle, + telegram_handle: community.telegram_handle.first().cloned(), + twitter_handle: community.twitter_handle, + website_url: community.website_url, + addons: community.addons, + }, + ) + }) + .collect(); + + communities.clear(); + + let mut communities_new = UnorderedMap::new(StorageKey::Communities); + + for (k, v) in migrated_communities { + communities_new.insert(&k, &v); + } + + env::state_write(&ContractV9 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + communities: communities_new, + featured_communities, + available_addons, + }); + } +} + +#[near] +pub struct CommunityV5 { + pub admins: Vec, + pub handle: CommunityHandle, + pub name: String, + pub tag: String, + pub description: String, + pub logo_url: String, + pub banner_url: String, + pub bio_markdown: Option, + pub github_handle: Option, + pub telegram_handle: Option, + pub twitter_handle: Option, + pub website_url: Option, + pub addons: Vec, +} + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV9 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, + pub authors: UnorderedMap>, + pub communities: UnorderedMap, + pub featured_communities: Vec, + pub available_addons: UnorderedMap, +} + +// From ContractV9 to ContractV10 +impl Contract { + fn unsafe_add_proposals() { + let ContractV9 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + communities, + featured_communities, + available_addons, + } = env::state_read().unwrap(); + + env::state_write(&ContractV10 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + proposals: Vector::new(StorageKey::Proposals), + label_to_proposals: UnorderedMap::new(StorageKey::LabelToProposals), + author_proposals: UnorderedMap::new(StorageKey::AuthorProposals), + proposal_categories: default_categories(), + communities, + featured_communities, + available_addons, + }); + } +} + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV10 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, + pub authors: UnorderedMap>, + pub proposals: Vector, + pub label_to_proposals: UnorderedMap>, + pub author_proposals: UnorderedMap>, + pub proposal_categories: Vec, + pub communities: UnorderedMap, + pub featured_communities: Vec, + pub available_addons: UnorderedMap, +} + +// From ContractV10 to ContractV11 +impl Contract { + fn unsafe_add_rfp() { + let ContractV10 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + proposals, + label_to_proposals, + author_proposals, + proposal_categories, + communities, + featured_communities, + available_addons, + } = env::state_read().unwrap(); + + env::state_write(&ContractV11 { + posts, + post_to_parent, + post_to_children, + label_to_posts, + access_control, + authors, + proposals, + label_to_proposals, + author_proposals, + proposal_categories, + rfps: Vector::new(StorageKey::RFPs), + label_to_rfps: UnorderedMap::new(StorageKey::LabelToRFPs), + global_labels_info: Lazy::new(StorageKey::LabelInfo, HashMap::new()), + communities, + featured_communities, + available_addons, + }); + } +} + +#[near] +#[derive(PanicOnDefault)] +pub struct ContractV11 { + pub posts: Vector, + pub post_to_parent: LookupMap, + pub post_to_children: LookupMap>, + pub label_to_posts: UnorderedMap>, + pub access_control: AccessControl, + pub authors: UnorderedMap>, + pub proposals: Vector, + pub label_to_proposals: UnorderedMap>, + pub author_proposals: UnorderedMap>, + pub proposal_categories: Vec, + pub rfps: Vector, + pub label_to_rfps: UnorderedMap>, + pub global_labels_info: Lazy>, + pub communities: UnorderedMap, + pub featured_communities: Vec, + pub available_addons: UnorderedMap, +} + +#[near] +#[derive(Debug)] +pub(crate) enum StateVersion { + V1, + V2, + V3 { done: bool, migrated_count: u64 }, + V4, + V5, + V6, + V7, + V8, + V9, + V10, + V11, +} + +const VERSION_KEY: &[u8] = b"VERSION"; + +fn state_version_read() -> StateVersion { + env::storage_read(VERSION_KEY) + .map(|data| { + StateVersion::try_from_slice(&data).expect("Cannot deserialize the contract state.") + }) + .unwrap_or(StateVersion::V2) // StateVersion is introduced in production contract with V2 State. +} + +pub(crate) fn state_version_write(version: &StateVersion) { + let data = to_vec(&version).expect("Cannot serialize the contract state."); + env::storage_write(VERSION_KEY, &data); + near_sdk::log!("Migrated to version: {:?}", version); +} + +#[near] +impl Contract { + pub fn unsafe_self_upgrade() { + near_sdk::assert_self(); + + let contract = env::input().expect("No contract code is attached in input"); + Promise::new(env::current_account_id()) + .deploy_contract(contract) + .then(Promise::new(env::current_account_id()).function_call( + "unsafe_migrate".to_string(), + Vec::new(), + NearToken::from_near(0), + env::prepaid_gas().saturating_sub(near_sdk::Gas::from_tgas(100)), + )) + .as_return(); + } + + fn migration_done() { + near_sdk::log!("Migration done."); + env::value_return(b"\"done\""); + } + + fn needs_migration() { + env::value_return(b"\"needs-migration\""); + } + + pub fn unsafe_migrate() { + near_sdk::assert_self(); + let current_version = state_version_read(); + near_sdk::log!("Migrating from version: {:?}", current_version); + match current_version { + StateVersion::V1 => { + Contract::unsafe_add_acl(); + state_version_write(&StateVersion::V2); + } + StateVersion::V2 => { + Contract::unsafe_add_post_authors(); + state_version_write(&StateVersion::V3 { done: false, migrated_count: 0 }) + } + StateVersion::V3 { done: false, migrated_count } => { + let new_version = + Contract::unsafe_insert_old_post_authors(migrated_count, migrated_count + 100); + state_version_write(&new_version); + } + StateVersion::V3 { done: true, migrated_count: _ } => { + Contract::unsafe_add_communities(); + state_version_write(&StateVersion::V4); + } + StateVersion::V4 => { + Contract::unsafe_add_featured_communities(); + state_version_write(&StateVersion::V5); + } + StateVersion::V5 => { + Contract::unsafe_multiple_telegrams(); + state_version_write(&StateVersion::V6); + } + StateVersion::V6 => { + Contract::unsafe_add_board_and_feature_flags(); + state_version_write(&StateVersion::V7); + } + StateVersion::V7 => { + Contract::unsafe_add_community_addons(); + state_version_write(&StateVersion::V8); + } + StateVersion::V8 => { + Contract::unsafe_clean_up_community(); + state_version_write(&StateVersion::V9); + } + StateVersion::V9 => { + Contract::unsafe_add_proposals(); + state_version_write(&StateVersion::V10); + } + StateVersion::V10 => { + Contract::unsafe_add_rfp(); + state_version_write(&StateVersion::V11); + } + _ => { + return Contract::migration_done(); + } + } + Contract::needs_migration(); + } +} diff --git a/src/notify.rs b/src/notify.rs new file mode 100644 index 0000000..8d86d80 --- /dev/null +++ b/src/notify.rs @@ -0,0 +1,208 @@ +use std::collections::HashSet; + +use crate::{ + get_subscribers, rfp::get_subscribers as get_rfp_subscribers, PostId, Proposal, ProposalId, RFP, +}; +use devhub_common::social_db_contract; +use near_sdk::serde_json::json; +use near_sdk::{env, AccountId, Promise}; + +pub fn get_text_mentions(text: &str) -> Vec { + let mut mentions = Vec::new(); + let mut mention = String::new(); + let mut recording = false; + + for ch in text.chars() { + if recording { + if ch.is_alphanumeric() || ch == '.' { + mention.push(ch); + } else { + if !mention.is_empty() { + mentions.push(mention.clone()); + mention.clear(); + } + recording = false; + } + } + + if ch == '@' { + recording = true; + } + } + + // Push the last mention if it wasn't pushed yet + if recording && !mention.is_empty() { + mentions.push(mention); + } + + mentions +} + +pub fn notify_accounts( + notifier: AccountId, + accounts: Vec, + notify_value: serde_json::Value, +) -> Promise { + if !accounts.is_empty() { + let mut notify_values = Vec::new(); + + for account in accounts { + notify_values.push(json!({ + "key": account, + "value": notify_value, + })); + } + + social_db_contract() + .with_static_gas(env::prepaid_gas().saturating_div(4)) + .with_attached_deposit(env::attached_deposit()) + .set(json!({ + notifier : { + "index": { + "notify": json!(notify_values).to_string() + } + } + })) + } else { + Promise::new(env::current_account_id()) + } +} + +pub fn notify_proposal_subscribers(proposal: &Proposal) -> Promise { + let accounts = get_subscribers(&proposal.snapshot.body.clone().latest_version()); + + notify_accounts( + env::current_account_id(), + accounts, + json!({ + "type": "proposal/mention", + "proposal": proposal.id, + "widgetAccountId": env::current_account_id(), + "notifier": env::predecessor_account_id(), + }), + ) +} + +pub fn notify_rfp_subscribers(rfp: &RFP, additional_accounts: HashSet) -> Promise { + let accounts = [ + get_rfp_subscribers(&rfp.snapshot.body.clone().latest_version()), + additional_accounts.iter().map(|x| x.to_string()).collect::>(), + ] + .concat(); + + notify_accounts( + env::current_account_id(), + accounts, + json!({ + "type": "rfp/mention", + "rfp": rfp.id, + "widgetAccountId": env::current_account_id(), + "notifier": env::current_account_id(), + }), + ) +} + +pub fn notify_mentions(text: &str, post_id: PostId) -> Promise { + let mentions = get_text_mentions(text); + + notify_accounts( + env::predecessor_account_id(), + mentions, + json!({ + "type": "devgovgigs/mention", + "post": post_id, + }), + ) +} + +pub fn notify_like(post_id: PostId, post_author: AccountId) -> Promise { + notify(env::predecessor_account_id(), post_author, notify_value(post_id, "like")) +} + +pub fn notify_reply(post_id: PostId, post_author: AccountId) -> Promise { + notify(env::predecessor_account_id(), post_author, notify_value(post_id, "reply")) +} + +pub fn notify_edit(post_id: PostId, post_author: AccountId) -> Promise { + notify(env::predecessor_account_id(), post_author, notify_value(post_id, "edit")) +} + +pub fn notify_edit_proposal(proposal_id: ProposalId, post_author: AccountId) -> Promise { + notify( + env::current_account_id(), + post_author, + json!({ + "type": "proposal/edit", + "proposal": proposal_id, + "widgetAccountId": env::current_account_id(), + "notifier": env::predecessor_account_id(), + }), + ) +} + +fn notify_value(post_id: PostId, action: &str) -> serde_json::Value { + json!({ + "type": format!("devgovgigs/{}", action), + "post": post_id, + }) +} + +fn notify(notifier: AccountId, post_author: AccountId, notify_value: serde_json::Value) -> Promise { + social_db_contract() + .with_static_gas(env::prepaid_gas().saturating_div(4)) + .with_attached_deposit(env::attached_deposit()) + .set(json!({ + notifier : { + "index": { + "notify": json!({ + "key": post_author, + "value": notify_value, + }).to_string() + } + } + })) +} + +#[cfg(all(test, not(target_arch = "wasm32")))] +mod tests { + use super::notify_mentions; + + use near_sdk::test_utils::{get_created_receipts, VMContextBuilder}; + use near_sdk::{testing_env, VMContext}; + + fn get_context(is_view: bool) -> VMContext { + VMContextBuilder::new() + .signer_account_id("bob.near".parse().unwrap()) + .is_view(is_view) + .build() + } + + #[test] + pub fn test_notify_mentions() { + let context = get_context(false); + testing_env!(context); + let text = "Mentioning @a.near and @bcdefg.near"; + notify_mentions(text, 2); + let receipts = get_created_receipts(); + assert_eq!(1, receipts.len()); + + if let near_sdk::mock::MockAction::FunctionCallWeight { method_name, args, .. } = + &receipts[0].actions[0] + { + assert_eq!(method_name, b"set"); + assert_eq!(args, b"{\"data\":{\"bob.near\":{\"index\":{\"notify\":\"[{\\\"key\\\":\\\"a.near\\\",\\\"value\\\":{\\\"type\\\":\\\"devgovgigs/mention\\\",\\\"post\\\":2}},{\\\"key\\\":\\\"bcdefg.near\\\",\\\"value\\\":{\\\"type\\\":\\\"devgovgigs/mention\\\",\\\"post\\\":2}}]\"}}}}"); + } else { + assert!(false, "Expected a function call ...") + } + } + + #[test] + pub fn test_no_mentions() { + let context = get_context(false); + testing_env!(context); + let text = "Not mentioning anyone"; + notify_mentions(text, 2); + assert_eq!(1, get_created_receipts().len()); + assert_eq!(0, get_created_receipts()[0].actions.len()); + } +} diff --git a/src/post/attestation.rs b/src/post/attestation.rs new file mode 100644 index 0000000..3f37803 --- /dev/null +++ b/src/post/attestation.rs @@ -0,0 +1,80 @@ +use super::{Like, PostStatus}; +use crate::str_serializers::*; +use crate::{AttestationId, CommentId, SolutionId}; +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::{AccountId, NearSchema, Timestamp}; +use std::collections::HashSet; + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct Attestation { + // Common fields + pub id: AttestationId, + pub name: String, + pub description: String, + pub author_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub timestamp: Timestamp, + pub status: PostStatus, + pub likes: HashSet, + pub comments: Vec, + + //Specific fields + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub submission_id: SolutionId, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct AttestationV1 { + pub name: String, + pub description: String, +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[serde(tag = "attestation_version")] +#[borsh(crate = "near_sdk::borsh")] +pub enum VersionedAttestation { + V0(Attestation), + V1(AttestationV1), +} + +impl VersionedAttestation { + pub fn latest_version(self) -> AttestationV1 { + self.into() + } +} + +impl From for Attestation { + fn from(va: VersionedAttestation) -> Self { + match va { + VersionedAttestation::V0(v0) => v0, + VersionedAttestation::V1(_) => unimplemented!(), + } + } +} + +impl From for AttestationV1 { + fn from(va: VersionedAttestation) -> Self { + match va { + VersionedAttestation::V0(_) => unimplemented!(), + VersionedAttestation::V1(v1) => v1, + } + } +} + +impl From for VersionedAttestation { + fn from(a: Attestation) -> Self { + VersionedAttestation::V0(a) + } +} diff --git a/src/post/comment.rs b/src/post/comment.rs new file mode 100644 index 0000000..071288c --- /dev/null +++ b/src/post/comment.rs @@ -0,0 +1,93 @@ +use super::Like; +use crate::str_serializers::*; +use crate::CommentId; +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::{AccountId, NearSchema, Timestamp}; +use std::collections::HashSet; + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct CommentV0 { + pub author_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub timestamp: Timestamp, + pub description: String, + pub likes: HashSet, + pub comments: Vec, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct Comment { + pub id: CommentId, + pub author_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub timestamp: Timestamp, + pub description: String, + pub likes: HashSet, + pub comments: Vec, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct CommentV2 { + pub description: String, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[serde(tag = "comment_version")] +#[borsh(crate = "near_sdk::borsh")] +pub enum VersionedComment { + V0(CommentV0), + V1(Comment), + V2(CommentV2), +} + +impl VersionedComment { + pub fn latest_version(self) -> CommentV2 { + self.into() + } +} + +impl From for Comment { + fn from(vc: VersionedComment) -> Self { + match vc { + VersionedComment::V0(v0) => Comment { + id: 0, + author_id: v0.author_id, + timestamp: v0.timestamp, + description: v0.description, + likes: v0.likes, + comments: v0.comments, + }, + VersionedComment::V1(v1) => v1, + VersionedComment::V2(_) => unimplemented!(), + } + } +} + +impl From for CommentV2 { + fn from(vc: VersionedComment) -> Self { + match vc { + VersionedComment::V2(v2) => v2, + _ => unimplemented!(), + } + } +} + +impl From for VersionedComment { + fn from(c: Comment) -> Self { + VersionedComment::V1(c) + } +} diff --git a/src/post/github.rs b/src/post/github.rs new file mode 100644 index 0000000..f3f91b3 --- /dev/null +++ b/src/post/github.rs @@ -0,0 +1,33 @@ +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct GithubV0 { + pub github_link: String, + pub name: String, + pub description: String, +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone)] +#[serde(crate = "near_sdk::serde")] +#[serde(tag = "github_version")] +#[borsh(crate = "near_sdk::borsh")] +pub enum VersionedGithub { + V0(GithubV0), +} + +impl From for VersionedGithub { + fn from(v0: GithubV0) -> Self { + VersionedGithub::V0(v0) + } +} + +impl From for GithubV0 { + fn from(vg: VersionedGithub) -> Self { + match vg { + VersionedGithub::V0(v0) => v0, + } + } +} diff --git a/src/post/idea.rs b/src/post/idea.rs new file mode 100644 index 0000000..39c23de --- /dev/null +++ b/src/post/idea.rs @@ -0,0 +1,71 @@ +use super::{Like, PostStatus}; +use crate::{CommentId, IdeaId, SolutionId}; +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::{AccountId, NearSchema, Timestamp}; +use std::collections::HashSet; + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct Idea { + // Common Fields + pub id: IdeaId, + pub name: String, + pub description: String, + pub author_id: AccountId, + pub timestamp: Timestamp, + pub status: PostStatus, + pub likes: HashSet, + pub comments: Vec, + + // Specific fields + pub solutions: Vec, +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct IdeaV1 { + pub name: String, + pub description: String, +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[serde(tag = "idea_version")] +#[borsh(crate = "near_sdk::borsh")] +pub enum VersionedIdea { + V0(Idea), + V1(IdeaV1), +} + +impl VersionedIdea { + pub fn latest_version(self) -> IdeaV1 { + self.into() + } +} + +impl From for Idea { + fn from(vi: VersionedIdea) -> Self { + match vi { + VersionedIdea::V0(v0) => v0, + VersionedIdea::V1(_) => unimplemented!(), + } + } +} + +impl From for IdeaV1 { + fn from(vi: VersionedIdea) -> Self { + match vi { + VersionedIdea::V1(v1) => v1, + _ => unimplemented!(), + } + } +} + +impl From for VersionedIdea { + fn from(idea: Idea) -> Self { + VersionedIdea::V0(idea) + } +} diff --git a/src/post/like.rs b/src/post/like.rs new file mode 100644 index 0000000..73b2d82 --- /dev/null +++ b/src/post/like.rs @@ -0,0 +1,38 @@ +use crate::str_serializers::*; +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::{AccountId, NearSchema, Timestamp}; +use std::cmp::Ordering; +use std::hash::{Hash, Hasher}; + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, Ord, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct Like { + pub author_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub timestamp: Timestamp, +} + +impl Hash for Like { + fn hash(&self, state: &mut H) { + self.author_id.hash(state) + } +} + +impl PartialEq for Like { + fn eq(&self, other: &Self) -> bool { + self.author_id.eq(&other.author_id) + } +} + +impl PartialOrd for Like { + fn partial_cmp(&self, other: &Self) -> Option { + self.author_id.partial_cmp(&other.author_id) + } +} + +impl Eq for Like {} diff --git a/src/post/mod.rs b/src/post/mod.rs new file mode 100644 index 0000000..bbbfa0e --- /dev/null +++ b/src/post/mod.rs @@ -0,0 +1,142 @@ +mod attestation; +mod comment; +mod github; +mod idea; +mod like; +mod solution; +mod sponsorship; + +use crate::str_serializers::*; +pub use attestation::*; +pub use comment::*; +pub use idea::*; +pub use like::*; +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::{AccountId, BorshStorageKey, CryptoHash, NearSchema, Timestamp}; +pub use solution::*; +pub use sponsorship::*; +use std::collections::HashSet; + +pub type Balance = u128; +pub type PostId = u64; + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub enum PostType { + Comment, + Idea, + Solution, + Attestation, + Sponsorship, + Github, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub enum PostStatus { + Open, + Closed { reason: String }, +} + +#[derive(BorshSerialize, BorshStorageKey)] +#[borsh(crate = "near_sdk::borsh")] +pub enum StorageKey { + Ideas, + Solutions, + Attestations, + Sponsorships, + Comments, + Posts, + PostToParent, + PostToChildren, + /// Deprecated due to damaged storage state. + LabelToPosts, + LabelToPostsV2, + AuthorToAuthorPosts, + AuthorPosts(CryptoHash), + Communities, + AddOns, + Proposals, + LabelToProposals, + AuthorProposals, + RFPs, + LabelToRFPs, + RFPLinkedProposals, + LabelInfo, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[serde(tag = "post_version")] +#[borsh(crate = "near_sdk::borsh")] +pub enum VersionedPost { + V0(Post), +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct Post { + pub id: PostId, + pub author_id: AccountId, + pub likes: HashSet, + pub snapshot: PostSnapshot, + // Excludes the current snapshot itself. + pub snapshot_history: Vec, +} + +type PostTag = String; + +impl From for Post { + fn from(vp: VersionedPost) -> Self { + match vp { + VersionedPost::V0(v0) => v0, + } + } +} + +impl From for VersionedPost { + fn from(p: Post) -> Self { + VersionedPost::V0(p) + } +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct PostSnapshot { + pub editor_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub timestamp: Timestamp, + pub labels: HashSet, + #[serde(flatten)] + pub body: PostBody, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[serde(tag = "post_type")] +#[borsh(crate = "near_sdk::borsh")] +pub enum PostBody { + Comment(VersionedComment), + Idea(VersionedIdea), + Solution(VersionedSolution), + Attestation(VersionedAttestation), + Sponsorship(VersionedSponsorship), +} + +pub fn get_post_description(post: Post) -> String { + return match post.snapshot.body.clone() { + PostBody::Comment(comment) => comment.latest_version().description, + PostBody::Idea(idea) => idea.latest_version().description, + PostBody::Solution(solution) => solution.latest_version().description, + PostBody::Attestation(attestation) => attestation.latest_version().description, + PostBody::Sponsorship(sponsorship) => sponsorship.latest_version().description, + }; +} diff --git a/src/post/solution.rs b/src/post/solution.rs new file mode 100644 index 0000000..c335071 --- /dev/null +++ b/src/post/solution.rs @@ -0,0 +1,133 @@ +use super::{Like, PostStatus, SponsorshipToken}; +use crate::str_serializers::*; +use crate::{AttestationId, Balance, CommentId, SolutionId, SponsorshipId}; +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::{AccountId, NearSchema, Timestamp}; +use std::collections::HashSet; + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct SolutionV0 { + // Common fields + pub id: SolutionId, + pub name: String, + pub description: String, + pub author_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub timestamp: Timestamp, + pub status: PostStatus, + pub likes: HashSet, + pub comments: Vec, + + // Specific fields + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub idea_id: u64, + pub attestations: Vec, + pub sponsorships: Vec, +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct SolutionV1 { + pub name: String, + pub description: String, +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct SolutionV2 { + pub name: String, + pub description: String, + pub requested_sponsor: Option, + #[serde( + serialize_with = "u128_dec_format::serialize", + deserialize_with = "u128_dec_format::deserialize" + )] + pub requested_sponsorship_amount: Balance, + pub requested_sponsorship_token: Option, +} + +#[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[serde(tag = "solution_version")] +#[borsh(crate = "near_sdk::borsh")] +pub enum VersionedSolution { + V0(SolutionV0), + V1(SolutionV1), + V2(SolutionV2), +} + +impl VersionedSolution { + pub fn latest_version(self) -> SolutionV2 { + self.into() + } + + pub fn validate(&self) { + match self { + VersionedSolution::V2(solution) => { + if solution.requested_sponsorship_amount > 0 + && (solution.requested_sponsorship_token.is_none() + || solution.requested_sponsor.is_none()) + { + panic!( + "Solution that requires funding must specify sponsorship token and sponsor" + ) + } + } + + _ => unimplemented!(), + } + } +} + +impl From for SolutionV0 { + fn from(solution: VersionedSolution) -> Self { + match solution { + VersionedSolution::V0(v0) => v0, + _ => unimplemented!(), + } + } +} + +impl From for SolutionV1 { + fn from(solution: VersionedSolution) -> Self { + match solution { + VersionedSolution::V1(v1) => v1, + _ => unimplemented!(), + } + } +} + +impl From for VersionedSolution { + fn from(solution: SolutionV0) -> Self { + VersionedSolution::V0(solution) + } +} + +impl From for SolutionV2 { + fn from(solution: VersionedSolution) -> Self { + match solution { + VersionedSolution::V2(v2) => v2, + + VersionedSolution::V1(v1) => SolutionV2 { + name: v1.name, + description: v1.description, + requested_sponsor: None, + requested_sponsorship_amount: 0, + requested_sponsorship_token: None, + }, + + _ => unimplemented!(), + } + } +} diff --git a/src/post/sponsorship.rs b/src/post/sponsorship.rs new file mode 100644 index 0000000..d9e2408 --- /dev/null +++ b/src/post/sponsorship.rs @@ -0,0 +1,102 @@ +use super::{Like, PostStatus}; +use crate::{str_serializers::*, Balance, CommentId, SolutionId, SponsorshipId}; +use near_sdk::borsh::{BorshDeserialize, BorshSerialize}; +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::{AccountId, NearSchema, Timestamp}; +use std::collections::HashSet; + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub enum SponsorshipToken { + NEAR, + NEP141 { address: AccountId }, + USD, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct Sponsorship { + // Common fields + pub id: SponsorshipId, + pub name: String, + pub description: String, + pub author_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub timestamp: Timestamp, + pub status: PostStatus, + pub likes: HashSet, + pub comments: Vec, + + // Specific fields + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub submission_id: SolutionId, + pub sponsorship_token: SponsorshipToken, + #[serde( + serialize_with = "u128_dec_format::serialize", + deserialize_with = "u128_dec_format::deserialize" + )] + pub amount: Balance, + pub supervisor: AccountId, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[borsh(crate = "near_sdk::borsh")] +pub struct SponsorshipV1 { + pub name: String, + pub description: String, + pub sponsorship_token: SponsorshipToken, + #[serde( + serialize_with = "u128_dec_format::serialize", + deserialize_with = "u128_dec_format::deserialize" + )] + pub amount: Balance, + pub supervisor: AccountId, +} + +#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone, NearSchema)] +#[serde(crate = "near_sdk::serde")] +#[serde(tag = "sponsorship_version")] +#[borsh(crate = "near_sdk::borsh")] +pub enum VersionedSponsorship { + V0(Sponsorship), + V1(SponsorshipV1), +} + +impl VersionedSponsorship { + pub fn latest_version(self) -> SponsorshipV1 { + self.into() + } +} + +impl From for Sponsorship { + fn from(vs: VersionedSponsorship) -> Self { + match vs { + VersionedSponsorship::V0(v0) => v0, + VersionedSponsorship::V1(_) => unimplemented!(), + } + } +} + +impl From for SponsorshipV1 { + fn from(vs: VersionedSponsorship) -> Self { + match vs { + VersionedSponsorship::V1(v1) => v1, + _ => unimplemented!(), + } + } +} + +impl From for VersionedSponsorship { + fn from(s: Sponsorship) -> Self { + VersionedSponsorship::V0(s) + } +} diff --git a/src/proposal/mod.rs b/src/proposal/mod.rs new file mode 100644 index 0000000..9ab218a --- /dev/null +++ b/src/proposal/mod.rs @@ -0,0 +1,306 @@ +pub mod repost; +pub mod timeline; + +use std::collections::HashSet; + +use self::timeline::TimelineStatus; + +use crate::Contract; +use crate::str_serializers::*; +use crate::{notify::get_text_mentions, rfp::RFPId}; + +use near_sdk::{env, near, require, AccountId, BlockHeight, Timestamp}; + +pub type ProposalId = u32; + +type PostTag = String; + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +#[serde(tag = "proposal_version")] +pub enum VersionedProposal { + V0(Proposal), +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct Proposal { + pub id: ProposalId, + pub author_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub social_db_post_block_height: BlockHeight, + pub snapshot: ProposalSnapshot, + // // Excludes the current snapshot itself. + pub snapshot_history: Vec, +} + +impl From for Proposal { + fn from(vp: VersionedProposal) -> Self { + match vp { + VersionedProposal::V0(v0) => v0, + } + } +} + +impl From for VersionedProposal { + fn from(p: Proposal) -> Self { + VersionedProposal::V0(p) + } +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct ProposalSnapshot { + pub editor_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub timestamp: Timestamp, + pub labels: HashSet, + #[serde(flatten)] + pub body: VersionedProposalBody, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct ProposalBodyV0 { + pub name: String, + pub category: String, + pub summary: String, + pub description: String, + pub linked_proposals: Vec, + #[serde( + serialize_with = "u32_dec_format::serialize", + deserialize_with = "u32_dec_format::deserialize" + )] + pub requested_sponsorship_usd_amount: u32, + pub requested_sponsorship_paid_in_currency: ProposalFundingCurrency, + pub receiver_account: AccountId, + pub requested_sponsor: AccountId, + pub supervisor: Option, + pub timeline: TimelineStatus, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct ProposalBodyV1 { + pub name: String, + pub category: String, + pub summary: String, + pub description: String, + pub linked_proposals: Vec, + #[serde( + serialize_with = "u32_dec_format::serialize", + deserialize_with = "u32_dec_format::deserialize" + )] + pub requested_sponsorship_usd_amount: u32, + pub requested_sponsorship_paid_in_currency: ProposalFundingCurrency, + pub receiver_account: AccountId, + pub requested_sponsor: AccountId, + pub supervisor: Option, + pub timeline: TimelineStatus, + pub linked_rfp: Option, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +#[serde(tag = "proposal_body_version")] +pub enum VersionedProposalBody { + V0(ProposalBodyV0), + V1(ProposalBodyV1), +} + +impl From for ProposalBodyV1 { + fn from(v0: ProposalBodyV0) -> Self { + ProposalBodyV1 { + name: v0.name, + category: v0.category, + summary: v0.summary, + description: v0.description, + linked_proposals: v0.linked_proposals, + requested_sponsorship_usd_amount: v0.requested_sponsorship_usd_amount, + requested_sponsorship_paid_in_currency: v0.requested_sponsorship_paid_in_currency, + receiver_account: v0.receiver_account, + requested_sponsor: v0.requested_sponsor, + supervisor: v0.supervisor, + timeline: v0.timeline, + linked_rfp: None, + } + } +} + +impl From for ProposalBodyV0 { + fn from(solution: VersionedProposalBody) -> Self { + match solution { + VersionedProposalBody::V0(v0) => v0, + _ => unimplemented!(), + } + } +} + +impl From for ProposalBodyV1 { + fn from(solution: VersionedProposalBody) -> Self { + match solution { + VersionedProposalBody::V0(v0) => v0.into(), + VersionedProposalBody::V1(v1) => v1, + } + } +} + +impl From for VersionedProposalBody { + fn from(p: ProposalBodyV0) -> Self { + VersionedProposalBody::V0(p) + } +} + +impl From for VersionedProposalBody { + fn from(p: ProposalBodyV1) -> Self { + VersionedProposalBody::V1(p) + } +} + +impl VersionedProposalBody { + pub fn latest_version(self) -> ProposalBodyV1 { + self.into() + } +} + +pub fn get_subscribers(proposal_body: &ProposalBodyV1) -> Vec { + let mut result = [ + get_text_mentions(proposal_body.description.as_str()), + get_text_mentions(proposal_body.summary.as_str()), + ] + .concat(); + if let Some(supervisor) = proposal_body.supervisor.clone() { + result.push(supervisor.to_string()); + } + result.push(proposal_body.requested_sponsor.to_string()); + result +} + +pub fn default_categories() -> Vec { + vec![ + String::from("DevDAO Operations"), + String::from("Decentralized DevRel"), + String::from("NEAR Campus"), + String::from("Marketing"), + String::from("Events"), + String::from("Tooling & Infrastructures"), + String::from("Other"), + ] +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub enum ProposalFundingCurrency { + NEAR, + USDT, + USDC, + OTHER, +} + +impl Contract { + pub(crate) fn update_proposal_labels(&mut self, proposal_id: ProposalId, new_labels: HashSet) -> ProposalId { + let proposal: Proposal = self + .proposals + .get(proposal_id.into()) + .unwrap_or_else(|| panic!("Proposal id {} not found", proposal_id)) + .into(); + + self.edit_proposal_internal(proposal_id, proposal.snapshot.body, new_labels) + } + + pub(crate) fn edit_proposal_internal( + &mut self, + id: ProposalId, + body: VersionedProposalBody, + labels: HashSet, + ) -> ProposalId { + require!( + self.is_allowed_to_edit_proposal(id, Option::None), + "The account is not allowed to edit this proposal" + ); + let editor_id = env::predecessor_account_id(); + let mut proposal: Proposal = self + .proposals + .get(id.into()) + .unwrap_or_else(|| panic!("Proposal id {} not found", id)) + .into(); + + let proposal_body = body.clone().latest_version(); + + let old_body = proposal.snapshot.body.clone(); + let labels = self.update_and_check_rfp_link(id, body.clone(), Some(old_body.clone()), labels); + + let current_timeline = old_body.latest_version().timeline; + + require!( + self.has_moderator(editor_id.clone()) + || editor_id.clone() == env::current_account_id() + || current_timeline.is_draft() + && (proposal_body.timeline.is_empty_review() + || proposal_body.timeline.is_draft()) + || current_timeline.can_be_cancelled() && proposal_body.timeline.is_cancelled(), + "This account is only allowed to change proposal status from DRAFT to REVIEW" + ); + + require!( + proposal_body.timeline.is_draft() || proposal_body.timeline.is_review() || proposal_body.timeline.is_cancelled() || proposal_body.supervisor.is_some(), + "You can't change the timeline of the proposal to this status without adding a supervisor" + ); + + require!(self.proposal_categories.contains(&proposal_body.category), "Unknown category"); + + let old_snapshot = proposal.snapshot.clone(); + let old_labels_set = old_snapshot.labels.clone(); + let new_labels = labels; + let new_snapshot = ProposalSnapshot { + editor_id: editor_id.clone(), + timestamp: env::block_timestamp(), + labels: new_labels.clone(), + body: body, + }; + proposal.snapshot = new_snapshot; + proposal.snapshot_history.push(old_snapshot); + let proposal_author = proposal.author_id.clone(); + self.proposals.replace(id.try_into().unwrap(), &proposal.into()); + + // Update labels index. + let new_labels_set = new_labels; + let labels_to_remove = &old_labels_set - &new_labels_set; + let labels_to_add = &new_labels_set - &old_labels_set; + require!( + self.is_allowed_to_use_labels( + Some(editor_id.clone()), + labels_to_remove.iter().cloned().collect() + ), + "Not allowed to remove these labels" + ); + require!( + self.is_allowed_to_use_labels( + Some(editor_id.clone()), + labels_to_add.iter().cloned().collect() + ), + "Not allowed to add these labels" + ); + + for label_to_remove in labels_to_remove { + let mut proposals = self.label_to_proposals.get(&label_to_remove).unwrap(); + proposals.remove(&id); + self.label_to_proposals.insert(&label_to_remove, &proposals); + } + + for label_to_add in labels_to_add { + let mut proposals = self.label_to_proposals.get(&label_to_add).unwrap_or_default(); + proposals.insert(id); + self.label_to_proposals.insert(&label_to_add, &proposals); + } + + crate::notify::notify_edit_proposal(id, proposal_author); + id + } +} \ No newline at end of file diff --git a/src/proposal/repost.rs b/src/proposal/repost.rs new file mode 100644 index 0000000..480a59b --- /dev/null +++ b/src/proposal/repost.rs @@ -0,0 +1,52 @@ +use near_sdk::serde_json::json; +use near_sdk::{env, AccountId, Promise}; + +use devhub_common::social_db_contract; + +use crate::Proposal; + +pub fn proposal_repost_text(proposal: Proposal) -> String { + let proposal_link = + format!("/bos.forum.potlock.near/widget/app?page=proposal&id={}", proposal.id); + + let title = proposal.snapshot.body.clone().latest_version().name; + let summary = proposal.snapshot.body.clone().latest_version().summary; + let category = proposal.snapshot.body.clone().latest_version().category; + + let text = format!( + "We have just received a new *{category}* proposal.\n\n———\n\n**By**: @{author}\n\n**Title**: “{title}“\n\n**Summary**:\n\n{summary}\n\n———\n\nRead the full proposal and share your feedback on [AI PGF]({proposal_link})", + author = proposal.author_id, + proposal_link = proposal_link, + title = title, + summary = summary, + category = category + ); + + text +} + +fn repost_internal(text: String, contract_address: AccountId) -> near_sdk::serde_json::Value { + let main_value = json!({ + "type": "md", + "text": text + }); + + json!({ + contract_address: { + "post": { + "main": main_value.to_string(), + }, + "index": { + "post": "{\"key\":\"main\",\"value\":{\"type\":\"md\"}}", + } + } + }) +} + +pub fn publish_to_socialdb_feed(callback: Promise, text: String) -> Promise { + social_db_contract() + .with_static_gas(env::prepaid_gas().saturating_div(3)) + .with_attached_deposit(env::attached_deposit()) + .set(repost_internal(text, env::current_account_id())) + .then(callback) +} diff --git a/src/proposal/timeline.rs b/src/proposal/timeline.rs new file mode 100644 index 0000000..a677a05 --- /dev/null +++ b/src/proposal/timeline.rs @@ -0,0 +1,104 @@ +use near_sdk::near; + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] +pub enum TimelineStatus { + Draft, + Review(ReviewStatus), + Approved(ReviewStatus), + Rejected(ReviewStatus), + ApprovedConditionally(ReviewStatus), + PaymentProcessing(PaymentProcessingStatus), + Funded(FundedStatus), + Cancelled(ReviewStatus), +} + +impl TimelineStatus { + pub fn is_draft(&self) -> bool { + matches!(self, TimelineStatus::Draft) + } + + pub fn is_empty_review(&self) -> bool { + match self { + TimelineStatus::Review(review_status) => { + !review_status.sponsor_requested_review + && !review_status.reviewer_completed_attestation + } + _ => false, + } + } + + pub fn is_review(&self) -> bool { + matches!(self, TimelineStatus::Review(..)) + } + + pub fn is_cancelled(&self) -> bool { + matches!(self, TimelineStatus::Cancelled(..)) + } + + pub fn can_be_cancelled(&self) -> bool { + match self { + TimelineStatus::Draft => true, + TimelineStatus::Review(..) => true, + _ => false, + } + } + + pub fn was_approved(&self) -> bool { + match self { + TimelineStatus::Approved(..) => true, + TimelineStatus::ApprovedConditionally(..) => true, + TimelineStatus::PaymentProcessing(..) => true, + TimelineStatus::Funded(..) => true, + _ => false, + + } + } + + pub fn get_review_status(&self) -> &ReviewStatus { + match self { + TimelineStatus::Review(review_status) + | TimelineStatus::Approved(review_status) + | TimelineStatus::Rejected(review_status) + | TimelineStatus::ApprovedConditionally(review_status) + | TimelineStatus::Cancelled(review_status) => review_status, + TimelineStatus::PaymentProcessing(payment_processing_status) => { + &payment_processing_status.review_status + }, + TimelineStatus::Funded(funded_status) => { + &funded_status.payment_processing_status.review_status + }, + TimelineStatus::Draft => &ReviewStatus { + sponsor_requested_review: false, + reviewer_completed_attestation: false, + }, + } + } +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct ReviewStatus { + sponsor_requested_review: bool, + reviewer_completed_attestation: bool, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct PaymentProcessingStatus { + #[serde(flatten)] + review_status: ReviewStatus, + kyc_verified: bool, + test_transaction_sent: bool, + request_for_trustees_created: bool, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct FundedStatus { + #[serde(flatten)] + payment_processing_status: PaymentProcessingStatus, + trustees_released_payment: bool, + payouts: Vec, +} diff --git a/src/repost.rs b/src/repost.rs new file mode 100644 index 0000000..31d6d85 --- /dev/null +++ b/src/repost.rs @@ -0,0 +1,90 @@ +use crate::post::{get_post_description, Post, PostBody}; +use devhub_common::social_db_contract; +use near_sdk::serde_json::json; +use near_sdk::{env, AccountId, Promise}; + +fn repost_internal(post: Post, contract_address: AccountId) -> near_sdk::serde_json::Value { + let post_link = format!("/devhub.near/widget/app?page=post&id={}", post.id); + let title = match post.snapshot.body.clone() { + PostBody::Idea(idea) => format!("## Idea: {}\n", idea.latest_version().name), + PostBody::Solution(solution) => { + format!("## Solution: {}\n", solution.latest_version().name) + } + PostBody::Attestation(attestation) => { + format!("## Attestation: {}\n", attestation.latest_version().name) + } + PostBody::Sponsorship(sponsorship) => { + format!("## Sponsorship: {}\n", sponsorship.latest_version().name) + } + _ => Default::default(), + }; + + let desc = get_post_description(post.clone()); + + let text = format!( + "@{author} [Posted on DevHub]({post_link})\n{title}{desc}", + author = post.author_id, + post_link = post_link, + title = title, + desc = desc + ); + + let main_value = json!({ + "type": "md", + "text": text + }); + + json!({ + contract_address: { + "post": { + "main": main_value.to_string(), + }, + "index": { + "post": "{\"key\":\"main\",\"value\":{\"type\":\"md\"}}", + } + } + }) +} + +pub fn repost(post: Post) -> Promise { + social_db_contract() + .with_static_gas(env::prepaid_gas().saturating_div(4)) + .with_attached_deposit(env::attached_deposit()) + .set(repost_internal(post, env::current_account_id())) +} + +#[cfg(test)] +mod tests { + use crate::post::{IdeaV1, Post, PostBody, PostSnapshot, VersionedIdea}; + use crate::repost::repost_internal; + use near_sdk::serde_json::json; + + #[test] + pub fn check_formatting() { + let post = Post { + id: 0, + author_id: "neardevgov.near".parse().unwrap(), + likes: Default::default(), + snapshot: PostSnapshot { + editor_id: "neardevgov.near".parse().unwrap(), + timestamp: 0, + labels: Default::default(), + body: PostBody::Idea(VersionedIdea::V1(IdeaV1 { name: "A call for Zero Knowledge Work Group members!".to_string(), description: "We are excited to create a more formal Zero Knowledge Work Group (WG) to oversee official decisions on Zero Knowledge proposals. We’re looking for 3-7 experts to participate. Reply to the post if you’re interested in becoming a work group member.".to_string() })), + }, + snapshot_history: vec![], + }; + + let call_args = repost_internal(post, "devhub.near".parse().unwrap()); + let expected = json!({ + "devhub.near": { + "post": { + "main": "{\"type\":\"md\",\"text\":\"@neardevgov.near [Posted on DevHub](/devhub.near/widget/app?page=post&id=0)\\n## Idea: A call for Zero Knowledge Work Group members!\\nWe are excited to create a more formal Zero Knowledge Work Group (WG) to oversee official decisions on Zero Knowledge proposals. We’re looking for 3-7 experts to participate. Reply to the post if you’re interested in becoming a work group member.\"}" + }, + "index": { + "post": "{\"key\":\"main\",\"value\":{\"type\":\"md\"}}" + } + } + }); + assert_eq!(call_args, expected); + } +} diff --git a/src/rfp/mod.rs b/src/rfp/mod.rs new file mode 100644 index 0000000..7e94e34 --- /dev/null +++ b/src/rfp/mod.rs @@ -0,0 +1,285 @@ +pub mod repost; +pub mod timeline; + +use std::collections::HashSet; + +pub use self::timeline::TimelineStatus; + +use crate::Contract; +use crate::proposal::{Proposal, ProposalId, VersionedProposalBody}; +use crate::notify::get_text_mentions; +use crate::str_serializers::*; + +use near_sdk::{env, require, near, AccountId, BlockHeight, Timestamp}; + +pub type RFPId = u32; + +type PostTag = String; + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +#[serde(tag = "rfp_version")] +pub enum VersionedRFP { + V0(RFP), +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct RFP { + pub id: RFPId, + pub author_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub social_db_post_block_height: BlockHeight, + pub snapshot: RFPSnapshot, + // Excludes the current snapshot itself. + // Contains the block height when the RFP was added or edited. + pub snapshot_history: Vec, +} + +impl From for RFP { + fn from(vp: VersionedRFP) -> Self { + match vp { + VersionedRFP::V0(v0) => v0, + } + } +} + +impl From for VersionedRFP { + fn from(p: RFP) -> Self { + VersionedRFP::V0(p) + } +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct RFPSnapshot { + pub editor_id: AccountId, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub timestamp: Timestamp, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub block_height: BlockHeight, + pub labels: HashSet, + #[serde(flatten)] + pub body: VersionedRFPBody, + pub linked_proposals: HashSet, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +pub struct RFPBodyV0 { + pub name: String, + pub summary: String, + pub description: String, + pub timeline: TimelineStatus, + #[serde( + serialize_with = "u64_dec_format::serialize", + deserialize_with = "u64_dec_format::deserialize" + )] + pub submission_deadline: Timestamp, +} + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +#[serde(tag = "rfp_body_version")] +pub enum VersionedRFPBody { + V0(RFPBodyV0), +} + +impl From for RFPBodyV0 { + fn from(solution: VersionedRFPBody) -> Self { + match solution { + VersionedRFPBody::V0(v0) => v0, + } + } +} + +impl From for VersionedRFPBody { + fn from(p: RFPBodyV0) -> Self { + VersionedRFPBody::V0(p) + } +} + +impl VersionedRFPBody { + pub fn latest_version(self) -> RFPBodyV0 { + self.into() + } +} + +pub fn get_subscribers(proposal_body: &RFPBodyV0) -> Vec { + let result = [ + get_text_mentions(proposal_body.description.as_str()), + get_text_mentions(proposal_body.summary.as_str()), + ] + .concat(); + result +} + +enum LinkedProposalChangeOperation { + Add, + Remove, +} + + +impl Contract { + fn assert_can_link_unlink_rfp(&self, rfp_id: Option) { + if let Some(rfp_id) = rfp_id { + let rfp: RFP = self + .rfps + .get(rfp_id.into()) + .unwrap_or_else(|| panic!("RFP id {} not found", rfp_id)) + .into(); + require!( + rfp.snapshot.body.latest_version().timeline.is_accepting_submissions() || self.is_allowed_to_write_rfps(env::predecessor_account_id()), + format!("The RFP {} is not in the Accepting Submissions state, so you can't link or unlink to this RFP", rfp_id) + ); + } + } + + fn get_rfp_labels(&self, rfp_id: RFPId) -> HashSet { + let rfp: RFP = self + .rfps + .get(rfp_id.into()) + .unwrap_or_else(|| panic!("RFP id {} not found", rfp_id)) + .into(); + rfp.snapshot.labels + } + + pub(crate) fn get_linked_proposals_in_rfp(&self, rfp_id: RFPId) -> HashSet { + let rfp: RFP = self.get_rfp(rfp_id).into(); + rfp.snapshot.linked_proposals + } + + fn change_linked_proposal_in_rfp(&mut self, rfp_id: RFPId, proposal_id: ProposalId, operation: LinkedProposalChangeOperation) { + let mut rfp: RFP = self.get_rfp(rfp_id).into(); + let mut linked_proposals = rfp.snapshot.linked_proposals.clone(); + match operation { + LinkedProposalChangeOperation::Add => { + linked_proposals.insert(proposal_id); + } + LinkedProposalChangeOperation::Remove => { + linked_proposals.remove(&proposal_id); + } + } + rfp.snapshot_history.push(rfp.snapshot.block_height); + let new_snapshot = RFPSnapshot { + editor_id: env::predecessor_account_id(), + timestamp: env::block_timestamp(), + block_height: env::block_height(), + labels: rfp.snapshot.labels, + body: rfp.snapshot.body, + linked_proposals: linked_proposals, + }; + rfp.snapshot = new_snapshot; + self.rfps.replace(rfp_id.try_into().unwrap(), &rfp.clone().into()); + } + + fn add_linked_proposal_in_rfp(&mut self, rfp_id: RFPId, proposal_id: ProposalId) { + self.change_linked_proposal_in_rfp(rfp_id, proposal_id, LinkedProposalChangeOperation::Add); + } + + fn remove_linked_proposal_in_rfp(&mut self, rfp_id: RFPId, proposal_id: ProposalId) { + self.change_linked_proposal_in_rfp(rfp_id, proposal_id, LinkedProposalChangeOperation::Remove); + } + + pub(crate) fn update_and_check_rfp_link( + &mut self, + proposal_id: ProposalId, + new_proposal_body: VersionedProposalBody, + old_proposal_body: Option, + labels: HashSet, + ) -> HashSet { + let mut labels = labels; + let new_body = new_proposal_body.clone().latest_version(); + let old_rfp_id = + old_proposal_body.clone().map(|old| old.latest_version().linked_rfp).flatten(); + if new_body.linked_rfp != old_rfp_id { + self.assert_can_link_unlink_rfp(new_body.linked_rfp); + self.assert_can_link_unlink_rfp(old_rfp_id); + if let Some(old_rfp_id) = old_rfp_id { + self.remove_linked_proposal_in_rfp(old_rfp_id, proposal_id); + } + if let Some(new_rfp_id) = new_body.linked_rfp { + self.add_linked_proposal_in_rfp(new_rfp_id, proposal_id); + } + } + if let Some(new_rfp_id) = new_body.linked_rfp { + labels = self.get_rfp_labels(new_rfp_id); + } + labels + } + + pub(crate) fn edit_rfp_internal( + &mut self, + id: RFPId, + body: VersionedRFPBody, + labels: HashSet, + ) -> RFPId { + let editor_id: AccountId = env::predecessor_account_id(); + require!( + self.is_allowed_to_write_rfps(editor_id.clone()), + "The account is not allowed to edit RFPs" + ); + + let mut rfp: RFP = self.get_rfp(id).into(); + + let rfp_body = body.clone().latest_version(); + + if rfp_body.timeline.is_proposal_selected() { + let has_approved_proposal = self.get_rfp_linked_proposals(id) + .into_iter() + .filter_map(|proposal_id| self.proposals.get(proposal_id.into())) + .any(|proposal| Into::::into(proposal).snapshot.body.latest_version().timeline.was_approved()); + require!(has_approved_proposal, "Cannot change RFP status to Proposal Selected without an approved proposal linked to this RFP"); + } + + let old_snapshot = rfp.snapshot.clone(); + let old_labels_set = old_snapshot.labels.clone(); + let new_labels = labels; + rfp.snapshot_history.push(rfp.snapshot.block_height); + let new_snapshot = RFPSnapshot { + editor_id: env::predecessor_account_id(), + timestamp: env::block_timestamp(), + block_height: env::block_height(), + labels: new_labels.clone(), + body: body, + linked_proposals: old_snapshot.linked_proposals.clone(), + }; + rfp.snapshot = new_snapshot; + self.rfps.replace(id.try_into().unwrap(), &rfp.clone().into()); + + // Update labels index. + let new_labels_set = new_labels; + + if old_labels_set != new_labels_set { + for proposal_id in self.get_rfp_linked_proposals(id) { + self.update_proposal_labels(proposal_id, new_labels_set.clone()); + } + } + + let labels_to_remove = &old_labels_set - &new_labels_set; + let labels_to_add: HashSet = &new_labels_set - &old_labels_set; + for label_to_remove in labels_to_remove { + let mut rfps = self.label_to_rfps.get(&label_to_remove).unwrap(); + rfps.remove(&id); + self.label_to_rfps.insert(&label_to_remove, &rfps); + } + + for label_to_add in labels_to_add { + let mut rfps = self.label_to_rfps.get(&label_to_add).unwrap_or_default(); + rfps.insert(id); + self.label_to_rfps.insert(&label_to_add, &rfps); + } + + crate::notify::notify_rfp_subscribers(&rfp, self.get_moderators()); + id + } +} \ No newline at end of file diff --git a/src/rfp/repost.rs b/src/rfp/repost.rs new file mode 100644 index 0000000..11377e7 --- /dev/null +++ b/src/rfp/repost.rs @@ -0,0 +1,19 @@ +use crate::RFP; + +pub fn rfp_repost_text(rfp: RFP) -> String { + let rfp_link = format!("/bos.forum.potlock.near/widget/app?page=rfp&id={}", rfp.id); + + let body = rfp.snapshot.body.latest_version(); + + let title = body.name; + let summary = body.summary; + + let text = format!( + "A new Request for Proposals is published.\n\n———\n\n**Title**: “{title}“\n\n**Summary**:\n\n{summary}\n\n———\n\nRead the full RFP and participate [here]({rfp_link})", + rfp_link = rfp_link, + title = title, + summary = summary, + ); + + text +} diff --git a/src/rfp/timeline.rs b/src/rfp/timeline.rs new file mode 100644 index 0000000..dcbb623 --- /dev/null +++ b/src/rfp/timeline.rs @@ -0,0 +1,25 @@ +use near_sdk::near; + +#[near(serializers=[borsh, json])] +#[derive(Clone)] +#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")] +pub enum TimelineStatus { + AcceptingSubmissions, + Evaluation, + ProposalSelected, + Cancelled, +} + +impl TimelineStatus { + pub fn is_accepting_submissions(&self) -> bool { + matches!(self, TimelineStatus::AcceptingSubmissions) + } + + pub fn is_cancelled(&self) -> bool { + matches!(self, TimelineStatus::Cancelled) + } + + pub fn is_proposal_selected(&self) -> bool { + matches!(self, TimelineStatus::ProposalSelected) + } +} diff --git a/src/stats.rs b/src/stats.rs new file mode 100644 index 0000000..56cf213 --- /dev/null +++ b/src/stats.rs @@ -0,0 +1,17 @@ +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::{near, NearSchema}; + +use crate::*; + +#[derive(Serialize, Deserialize, NearSchema)] +#[serde(crate = "near_sdk::serde")] +pub struct Stats { + pub num_posts: u64, +} + +#[near] +impl Contract { + pub fn get_stats(&self) -> Stats { + Stats { num_posts: self.posts.len() } + } +} diff --git a/src/str_serializers.rs b/src/str_serializers.rs new file mode 100644 index 0000000..ebff306 --- /dev/null +++ b/src/str_serializers.rs @@ -0,0 +1,56 @@ +pub mod u128_dec_format { + use near_sdk::serde::de; + use near_sdk::serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(num: &u128, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&num.to_string()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer)?.parse().map_err(de::Error::custom) + } +} + +pub mod u64_dec_format { + use near_sdk::serde::de; + use near_sdk::serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(num: &u64, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&num.to_string()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer)?.parse().map_err(de::Error::custom) + } +} + +pub mod u32_dec_format { + use near_sdk::serde::de; + use near_sdk::serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(num: &u32, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&num.to_string()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + String::deserialize(deserializer)?.parse().map_err(de::Error::custom) + } +} diff --git a/src/web4/handler.rs b/src/web4/handler.rs new file mode 100644 index 0000000..7763fcb --- /dev/null +++ b/src/web4/handler.rs @@ -0,0 +1,694 @@ +use near_sdk::{ + base64::{ + alphabet, + engine::{self, general_purpose}, + Engine, + }, + env, +}; +use serde_json::json; + +use crate::{ + web4::types::{Web4Request, Web4Response}, + Contract, Proposal, +}; + +pub const BASE64_ENGINE: engine::GeneralPurpose = + engine::GeneralPurpose::new(&alphabet::URL_SAFE, general_purpose::PAD); + +pub fn web4_get(contract: &Contract, request: Web4Request) -> Web4Response { + let current_account_id = env::current_account_id().to_string(); + let path_parts: Vec<&str> = request.path.split('/').collect(); + + // A valid path provided by a legit web4 gateway always has '/', so there + // are always [0] and [1] elements, and [0] is always empty. + let page = path_parts[1]; + + let metadata_preload_url = format!( + "/web4/contract/social.near/get?keys.json=%5B%22{}/widget/app/metadata/**%22%5D", + ¤t_account_id + ); + + let mut app_name = String::from("near/dev/hub"); + let mut title = String::new(); + let mut description = String::from("The decentralized home base for NEAR builders"); + + let Some(preloads) = request.preloads else { + return Web4Response::PreloadUrls { preload_urls: [metadata_preload_url.clone()].to_vec() }; + }; + + if let Some(Web4Response::Body { content_type: _, body }) = preloads.get(&metadata_preload_url) { + if let Ok(body_value) = serde_json::from_slice::( + &BASE64_ENGINE.decode(body).unwrap() + ) { + if let Some(app_name_str) = body_value[¤t_account_id] + ["widget"]["app"]["metadata"]["name"] + .as_str() + { + app_name = app_name_str.to_string(); + } + + if let Some(description_str) = body_value + [¤t_account_id]["widget"]["app"]["metadata"] + ["description"] + .as_str() + { + description = description_str.to_string(); + } + } + } + + let mut image = format!( + "https://i.near.social/magic/large/https://near.social/magic/img/account/{}", + ¤t_account_id + ); + let redirect_path; + let initial_props_json; + + match (page, path_parts.get(2)) { + ("community", Some(handle)) => { + if let Some(community) = contract.get_community(handle.to_string()) { + title = format!(" - Community - {}", community.name); + description = community.description; + image = community.logo_url; + } else { + title = format!(" - Community - {}", handle); + } + redirect_path = + format!("{}/widget/app?page={}&handle={}", ¤t_account_id, page, handle); + initial_props_json = json!({"page": page, "handle": handle}); + } + ("proposal", Some(id)) => { + if let Ok(id) = id.parse::() { + if let Some(versioned_proposal) = contract.proposals.get(id.into()) { + let proposal_body = + Proposal::from(versioned_proposal).snapshot.body.latest_version(); + title = format!(" - Proposal #{} - {}", id, proposal_body.name); + description = proposal_body.summary; + } else { + title = format!(" - Proposal #{}", id); + } + } else { + title = " - Proposals".to_string(); + } + redirect_path = + format!("{}/widget/app?page={}&id={}", ¤t_account_id, page, id); + initial_props_json = json!({"page": page, "id": id}); + } + _ => { + redirect_path = format!("{}/widget/app", ¤t_account_id); + initial_props_json = json!({"page": page}); + } + } + + let app_name = html_escape::encode_text(&app_name).to_string(); + let title = html_escape::encode_text(&title).to_string(); + let description = html_escape::encode_text(&description).to_string(); + + let body = format!( + r#" + + + {title} + + + + + + + + + + + + + + + + + + + + +"#, + url = redirect_path + ); + + Web4Response::Body { + content_type: "text/html; charset=UTF-8".to_owned(), + body: BASE64_ENGINE.encode(body), + } +} + +#[cfg(all(test, not(target_arch = "wasm32")))] +mod tests { + use std::collections::HashSet; + + use super::web4_get; + use crate::{ + web4::{handler::BASE64_ENGINE, types::Web4Response}, + CommunityInputs, Contract, Proposal, ProposalBodyV0, ProposalSnapshot, + VersionedProposalBody, + }; + use near_sdk::{ + base64::Engine, serde_json::json, test_utils::VMContextBuilder, testing_env, NearToken, + }; + + const PRELOAD_URL: &str = "/web4/contract/social.near/get?keys.json=%5B%22not-only-devhub.near/widget/app/metadata/**%22%5D"; + + fn create_preload_result(title: String, description: String) -> serde_json::Value { + let body_string = serde_json::json!({"not-only-devhub.near":{"widget":{"app":{"metadata":{ + "description":description, + "image":{"ipfs_cid":"bafkreido4srg4aj7l7yg2tz22nbu3ytdidjczdvottfr5ek6gqorwg6v74"}, + "name":title, + "tags": {"devhub":"","communities":"","developer-governance":"","app":""}}}}}}) + .to_string(); + + let body_base64 = BASE64_ENGINE.encode(body_string); + return serde_json::json!({ + String::from(PRELOAD_URL): { + "contentType": "application/json", + "body": body_base64 + } + }); + } + + fn view_test_env() { + let contract: String = "not-only-devhub.near".to_string(); + let context = + VMContextBuilder::new().current_account_id(contract.try_into().unwrap()).build(); + + testing_env!(context); + } + + #[test] + pub fn test_preload_url_response() { + view_test_env(); + let contract = Contract::new(); + + let response_before_preload = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/" + })) + .unwrap(), + ); + match response_before_preload { + Web4Response::PreloadUrls { preload_urls } => { + assert_eq!(PRELOAD_URL, preload_urls.get(0).unwrap()) + } + _ => { + panic!("Should return Web4Response::PreloadUrls"); + } + } + } + + #[test] + pub fn test_response_with_preload_content() { + view_test_env(); + let contract = Contract::new(); + + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/", + "preloads": create_preload_result(String::from("NotOnlyDevHub"),String::from("A description of any devhub portal instance, not just devhub itself")), + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + + assert!(body_string.contains( + "" + )); + assert!(body_string + .contains("")); + } + _ => { + panic!("Should return Web4Response::Body"); + } + } + } + + #[test] + pub fn test_response_with_empty_preload_content() { + view_test_env(); + let contract = Contract::new(); + + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/", + "preloads": { + String::from(PRELOAD_URL): { + "contentType": "application/json", + "body": "" + } + }, + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + + assert!(body_string.contains( + "" + )); + assert!(body_string + .contains("")); + } + _ => { + panic!("Should return Web4Response::Body"); + } + } + } + + #[test] + pub fn test_logo() { + view_test_env(); + let contract = Contract::new(); + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/proposal/1", + "preloads": create_preload_result(String::from("title"), String::from("description")), + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + assert!(body_string.contains("")); + assert!(body_string.contains("")); + assert!(body_string.contains("")); + let expected_initial_props_string = + json!({"page": "proposal", "id": "1"}).to_string(); + assert!(body_string.contains(&expected_initial_props_string)); + } + _ => { + panic!("Should return Web4Response::Body"); + } + } + } + + #[test] + pub fn test_community_path() { + let signer = "bob.near".to_string(); + let contract: String = "not-only-devhub.near".to_string(); + + let context = VMContextBuilder::new() + .signer_account_id(signer.clone().try_into().unwrap()) + .current_account_id(contract.try_into().unwrap()) + .attached_deposit(NearToken::from_near(4)) + .build(); + + testing_env!(context); + let mut contract = Contract::new(); + + contract.create_community(CommunityInputs { + handle: String::from("webassemblymusic"), + name: String::from("WebAssembly Music"), + description: String::from("Music stored forever in the NEAR blockchain"),tag: String::from("wasm"), + logo_url: String::from("https://ipfs.near.social/ipfs/bafybeiesrsf4fpdmlfgcnxpuxiqlgw2lk3bietdt25mvumrjk5yhf2c54e"), + banner_url: String::from("https://ipfs.near.social/ipfs/bafybeihsid3qgrb2dd4adsd4kuwe3pondtjr3u27ru6e2mbvabvm4rocru"), + bio_markdown: Some(String::from("Music stored forever in the NEAR blockchain")) + }); + + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/community/webassemblymusic", + "preloads": create_preload_result(String::from("title"), String::from("description")), + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + + assert!(body_string.contains("")); + assert!(body_string + .contains("")); + assert!(body_string.contains("https://near.social/not-only-devhub.near/widget/app?page=community&handle=webassemblymusic")); + let expected_initial_props_string = + json!({"page": "community", "handle": "webassemblymusic"}).to_string(); + assert!(body_string.contains(&expected_initial_props_string)); + } + _ => { + panic!("Should return Web4Response::Body"); + } + } + } + + #[test] + pub fn test_web4_unknown_path() { + view_test_env(); + let contract = Contract::new(); + for unknown_path in &["/", "/unknown", "/unknown/path"] { + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": unknown_path, + "preloads": create_preload_result(String::from("near/dev/hub"), String::from("The decentralized home base for NEAR builders")), + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = + String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + + assert!(body_string.contains("")); + assert!(body_string + .contains("")); + assert!( + body_string.contains("https://near.social/not-only-devhub.near/widget/app") + ); + } + _ => { + panic!("Should return Web4Response::Body for '{}' path", unknown_path); + } + } + } + } + + #[test] + pub fn test_web4_unknown_community() { + view_test_env(); + let contract = Contract::new(); + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/community/blablablablabla", + "preloads": create_preload_result(String::from("near/dev/hub"), String::from("The decentralized home base for NEAR builders")), + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + + assert!(body_string.contains("")); + assert!( + body_string.contains("") + ); + assert!(body_string.contains("https://near.social/not-only-devhub.near/widget/app")); + assert!(body_string.contains("https://near.org/not-only-devhub.near/widget/app")); + let expected_initial_props_string = + json!({"page": "community", "handle": "blablablablabla"}).to_string(); + assert!(body_string.contains(&expected_initial_props_string)); + } + _ => { + panic!("Should return Web4Response::Body"); + } + } + } + + #[test] + pub fn test_web4_community_missing_handle() { + view_test_env(); + let contract = Contract::new(); + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/community", + "preloads": create_preload_result(String::from("near/dev/hub"), String::from("The decentralized home base for NEAR builders")), + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + + assert!(body_string.contains("")); + assert!( + body_string.contains("") + ); + assert!(body_string.contains("https://near.social/not-only-devhub.near/widget/app")); + assert!(body_string.contains("https://near.org/not-only-devhub.near/widget/app")); + let expected_initial_props_string = json!({"page": "community"}).to_string(); + assert!(body_string.contains(&expected_initial_props_string)); + } + _ => { + panic!("Should return Web4Response::Body"); + } + } + } + + #[test] + pub fn test_proposal_path() { + let signer = "bob.near".to_string(); + let contract = "not-only-devhub.near".to_string(); + let context = VMContextBuilder::new() + .signer_account_id(signer.clone().try_into().unwrap()) + .current_account_id(contract.try_into().unwrap()) + .build(); + + testing_env!(context); + let mut contract = Contract::new(); + + let proposal_body: ProposalBodyV0 = near_sdk::serde_json::from_value(json!({ + "proposal_body_version": "V0", + "name": "The best proposal ever", + "description": "You should just understand why this is the best proposal", + "category": "Marketing", + "summary": "It is obvious why this proposal is so great", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "payouts": [], + "timeline": {"status": "DRAFT"} + })) + .unwrap(); + let proposal = Proposal { + id: 0, + author_id: "bob.near".parse().unwrap(), + social_db_post_block_height: 0u64, + snapshot: ProposalSnapshot { + editor_id: "bob.near".parse().unwrap(), + timestamp: 0, + labels: HashSet::new(), + body: VersionedProposalBody::V0(proposal_body), + }, + snapshot_history: vec![], + }; + + contract.proposals.push(&proposal.clone().into()); + + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/proposal/0", + "preloads": create_preload_result(String::from("near/dev/hub"), String::from("The decentralized home base for NEAR builders")), + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + + assert!(body_string.contains("")); + assert!(body_string + .contains("")); + assert!(body_string.contains( + "https://near.social/not-only-devhub.near/widget/app?page=proposal&id=0" + )); + assert!(body_string.contains( + "https://near.org/not-only-devhub.near/widget/app?page=proposal&id=0" + )); + let expected_initial_props_string = + json!({"page": "proposal", "id": "0"}).to_string(); + assert!(body_string.contains(&expected_initial_props_string)); + } + _ => { + panic!("Should return Web4Response::Body"); + } + } + } + + #[test] + pub fn test_proposal_with_html_tag_in_summary() { + let signer = "bob.near".to_string(); + let contract = "not-only-devhub.near".to_string(); + let context = VMContextBuilder::new() + .signer_account_id(signer.clone().try_into().unwrap()) + .current_account_id(contract.try_into().unwrap()) + .build(); + + testing_env!(context); + let mut contract = Contract::new(); + + let proposal_body: ProposalBodyV0 = near_sdk::serde_json::from_value(json!({ + "proposal_body_version": "V0", + "name": "The best proposal ever", + "description": "You should just understand why this is the best proposal", + "category": "Marketing", + "summary": "It is obvious why this proposal is so great", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "payouts": [], + "timeline": {"status": "DRAFT"} + })) + .unwrap(); + let proposal = Proposal { + id: 0, + author_id: "bob.near".parse().unwrap(), + social_db_post_block_height: 0u64, + snapshot: ProposalSnapshot { + editor_id: "bob.near".parse().unwrap(), + timestamp: 0, + labels: HashSet::new(), + body: VersionedProposalBody::V0(proposal_body), + }, + snapshot_history: vec![], + }; + + contract.proposals.push(&proposal.clone().into()); + + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/proposal/0", + "preloads": create_preload_result(String::from("near/dev/hub"), String::from("The decentralized home base for NEAR builders")), + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + + assert!(body_string.contains("")); + assert!(body_string + .contains("")); + assert!(body_string.contains( + "https://near.social/not-only-devhub.near/widget/app?page=proposal&id=0" + )); + assert!(body_string.contains( + "https://near.org/not-only-devhub.near/widget/app?page=proposal&id=0" + )); + let expected_initial_props_string = + json!({"page": "proposal", "id": "0"}).to_string(); + assert!(body_string.contains(&expected_initial_props_string)); + } + _ => { + panic!("Should return Web4Response::Body"); + } + } + } + + #[test] + pub fn test_proposal_path_unknown() { + view_test_env(); + let contract = Contract::new(); + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/proposal/1", + "preloads": create_preload_result(String::from("near/dev/hub"), String::from("The decentralized home base for NEAR builders")), + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + + assert!(body_string.contains("")); + assert!( + body_string.contains("") + ); + assert!(body_string.contains("https://near.social/not-only-devhub.near/widget/app")); + let expected_initial_props_string = + json!({"page": "proposal", "id": "1"}).to_string(); + assert!(body_string.contains(&expected_initial_props_string)); + } + _ => { + panic!("Should return Web4Response::Body"); + } + } + } + + #[test] + pub fn test_proposal_path_incomplete() { + view_test_env(); + let contract = Contract::new(); + let response = web4_get( + &contract, + serde_json::from_value(serde_json::json!({ + "path": "/proposal", + "preloads": create_preload_result(String::from("near/dev/hub"), String::from("The decentralized home base for NEAR builders")), + })) + .unwrap(), + ); + match response { + Web4Response::Body { content_type, body } => { + assert_eq!("text/html; charset=UTF-8", content_type); + + let body_string = String::from_utf8(BASE64_ENGINE.decode(body).unwrap()).unwrap(); + + assert!(body_string.contains("")); + assert!( + body_string.contains("") + ); + assert!(body_string.contains("https://near.social/not-only-devhub.near/widget/app")); + let expected_initial_props_string = json!({"page": "proposal"}).to_string(); + assert!(body_string.contains(&expected_initial_props_string)); + } + _ => { + panic!("Should return Web4Response::Body"); + } + } + } +} diff --git a/src/web4/mod.rs b/src/web4/mod.rs new file mode 100644 index 0000000..c2ce1d6 --- /dev/null +++ b/src/web4/mod.rs @@ -0,0 +1,2 @@ +pub mod handler; +pub mod types; diff --git a/src/web4/types.rs b/src/web4/types.rs new file mode 100644 index 0000000..6b4c8b1 --- /dev/null +++ b/src/web4/types.rs @@ -0,0 +1,33 @@ +use near_sdk::serde::{Deserialize, Serialize}; +use near_sdk::NearSchema; + +#[derive(Debug, Serialize, Deserialize, NearSchema)] +#[serde(crate = "near_sdk::serde")] +pub struct Web4Request { + #[serde(rename = "accountId")] + pub account_id: Option, + pub path: String, + #[serde(default)] + pub params: std::collections::HashMap, + #[serde(default)] + pub query: std::collections::HashMap>, + pub preloads: Option>, +} + +#[derive(Debug, Serialize, Deserialize, NearSchema, Clone)] +#[serde(crate = "near_sdk::serde", untagged)] +pub enum Web4Response { + Body { + #[serde(rename = "contentType")] + content_type: String, + body: String, + }, + BodyUrl { + #[serde(rename = "bodyUrl")] + body_url: String, + }, + PreloadUrls { + #[serde(rename = "preloadUrls")] + preload_urls: Vec, + }, +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..94b5c9a --- /dev/null +++ b/test.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +contract=i.zxcvn.testnet + +# near create-account $contract --masterAccount zxcvn.testnet --initialBalance 10 +# near deploy $contract res/devgovgigs.wasm --initFunction new --initArgs '{}' + +# for i in $(seq 1 2) +# do +# near call $contract add_post --accountId zxcvn.testnet --deposit 0.01 --args '{"parent_id":null,"body":{"post_type": "Idea","idea_version":"V1","name":"a'$i'","description":"aaa"},"labels":[]}' +# near call $contract add_post --accountId zxcvn.testnet --deposit 0.01 --args '{"parent_id":null,"body":{"post_type": "Idea","idea_version":"V1","name":"b'$i'","description":"bbb"},"labels":[]}' +# near call $contract add_post --accountId a.zxcvn.testnet --deposit 0.01 --args '{"parent_id":null,"body":{"post_type": "Idea","idea_version":"V1","name":"c'$i'","description":"ccc"},"labels":[]}' +# near call $contract add_post --accountId zxcvn.testnet --deposit 0.01 --args '{"parent_id":null,"body":{"post_type": "Idea","idea_version":"V1","name":"d'$i'","description":"ddd"},"labels":[]}' +# done + +# near deploy $contract res/devgovgigs.wasm + +# near call $contract unsafe_self_upgrade --accountId $contract --args $(base64 < res/devgovgigs.wasm ) --base64 --gas 300000000000000 + +#near call contract.devhubopen.testnet unsafe_self_upgrade --accountId contract.devhubopen.testnet --args $(base64 < res/devgovgigs.wasm ) --base64 --gas 300000000000000 + +# near call $contract unsafe_migrate --accountId $contract --gas 300000000000000 + +# cargo near deploy forum.potlock.near with-init-call new json-args {} prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' network-config mainnet sign-with-plaintext-private-key --signer-public-key ed25519:H15VWKYXKsEG7dfeb2xWqHEjb4FsmZTuMPY7NjUoX6XT --signer-private-key ed25519:47JqUN9iC95vKWqvySupvfnBmnvnnreaLmWEtVUwKhw6grRLhhCNp6MeC5sonZ27Zvwk25Ymg7hggkSUjDaCXqFX send + +# near contract call-function \ +# as-transaction forum.potlock.near set_allowed_categories json-args '{"new_categories":["AI PGF"]}' \ +# prepaid-gas '300 Tgas' \ +# attached-deposit '0 NEAR' \ +# sign-as forum.potlock.near \ +# network-config mainnet \ +# sign-with-plaintext-private-key --signer-public-key ed25519:H15VWKYXKsEG7dfeb2xWqHEjb4FsmZTuMPY7NjUoX6XT --signer-private-key ed25519:47JqUN9iC95vKWqvySupvfnBmnvnnreaLmWEtVUwKhw6grRLhhCNp6MeC5sonZ27Zvwk25Ymg7hggkSUjDaCXqFX \ +# send + + +# near contract call-function \ +# as-transaction forum.potlock.near set_global_labels json-args '{"labels":[{"title":"Bounty","value":"Bounty","color":[124,102,220]},{"title":"Quick Start","value":"Quick Start","color":[220,194,102]},{"title":"A small build","value":"A small build","color":[4,164,110]}]}' \ +# prepaid-gas '300 Tgas' \ +# attached-deposit '0 NEAR' \ +# sign-as forum.potlock.near \ +# network-config mainnet \ +# sign-with-plaintext-private-key --signer-public-key ed25519:H15VWKYXKsEG7dfeb2xWqHEjb4FsmZTuMPY7NjUoX6XT --signer-private-key ed25519:47JqUN9iC95vKWqvySupvfnBmnvnnreaLmWEtVUwKhw6grRLhhCNp6MeC5sonZ27Zvwk25Ymg7hggkSUjDaCXqFX \ +# send + + +# near contract \ +# call-function \ +# as-transaction forum.potlock.near edit_member json-args '{"member":"team:moderators","metadata":{"member_metadata_version":"V0","description":"The moderator group has permissions to create and edit RFPs, edit and manage proposals, and manage admins.","permissions":{"*":["use-labels","edit-post"]},"children":["megha19.near"],"parents":[]}}' \ +# prepaid-gas '300 Tgas' \ +# attached-deposit '0 NEAR' \ +# sign-as forum.potlock.near \ +# network-config mainnet \ +# sign-with-plaintext-private-key --signer-public-key ed25519:H15VWKYXKsEG7dfeb2xWqHEjb4FsmZTuMPY7NjUoX6XT --signer-private-key ed25519:47JqUN9iC95vKWqvySupvfnBmnvnnreaLmWEtVUwKhw6grRLhhCNp6MeC5sonZ27Zvwk25Ymg7hggkSUjDaCXqFX \ +# send + +near contract \ + # call-function \ + # as-transaction forum.potlock.near remove_member json-args '{"member": "team:moderators"}' \ + # prepaid-gas '300 Tgas' \ + # attached-deposit '0 NEAR' \ + # sign-as forum.potlock.near \ + # network-config mainnet \ + # sign-with-plaintext-private-key --signer-public-key ed25519:H15VWKYXKsEG7dfeb2xWqHEjb4FsmZTuMPY7NjUoX6XT --signer-private-key ed25519:47JqUN9iC95vKWqvySupvfnBmnvnnreaLmWEtVUwKhw6grRLhhCNp6MeC5sonZ27Zvwk25Ymg7hggkSUjDaCXqFX \ + # send + + +# near contract \ +# call-function \ +# as-transaction forum.potlock.near add_member json-args '{"member":"megha19.near","metadata":{"member_metadata_version":"V0","description":"The moderator group has permissions to create and edit RFPs, edit and manage proposals, and manage admins.","permissions":{"*":["use-labels","edit-post"]},"children":[],"parents":["team:moderators"]}}' \ +# prepaid-gas '300 Tgas' \ +# attached-deposit '0 NEAR' \ +# sign-as forum.potlock.near \ +# network-config mainnet \ +# sign-with-plaintext-private-key --signer-public-key ed25519:H15VWKYXKsEG7dfeb2xWqHEjb4FsmZTuMPY7NjUoX6XT --signer-private-key ed25519:47JqUN9iC95vKWqvySupvfnBmnvnnreaLmWEtVUwKhw6grRLhhCNp6MeC5sonZ27Zvwk25Ymg7hggkSUjDaCXqFX \ +# send + + +# cd discussions +# cargo near build +# cd ../community +# cargo near build +# cd ../community-factory +# cargo near build +# cd .. +# cargo near build + +# cargo near deploy forum.potlock.near without-init-call network-config mainnet sign-with-plaintext-private-key --signer-public-key ed25519:H15VWKYXKsEG7dfeb2xWqHEjb4FsmZTuMPY7NjUoX6XT --signer-private-key ed25519:47JqUN9iC95vKWqvySupvfnBmnvnnreaLmWEtVUwKhw6grRLhhCNp6MeC5sonZ27Zvwk25Ymg7hggkSUjDaCXqFX send \ No newline at end of file diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..15a5993 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,18 @@ +# Integration Test using near-workspaces-rs + +This codebase is designed to deploy and test the Devhub contract on a Sandbox node. + +## Dependencies + +- Rust: For writing the test suite. +- `near-workspaces`: A custom library for handling contract calls. +- `near_units`: For handling NEAR unit conversions. +- `serde_json`: For JSON serialization and deserialization. + +### How to Run + +To run the test, use the following command: + +```bash +cargo test +``` diff --git a/tests/communities.rs b/tests/communities.rs new file mode 100644 index 0000000..ddb0499 --- /dev/null +++ b/tests/communities.rs @@ -0,0 +1,344 @@ +mod test_env; + +use near_sdk::NearToken; +use {crate::test_env::*, serde_json::json}; + +#[tokio::test] +async fn test_community_addon() -> anyhow::Result<()> { + // Initialize the devhub and near social contract on chain, + // contract is devhub contract instance. + let (contract, _, _) = init_contracts_from_res().await?; + + let deposit_amount = NearToken::from_near(4); + + // Add a community + let _create_community = contract + .call("create_community") + .args_json(json!({ + "inputs": { + "handle": "gotham", + "name": "Gotham", + "tag": "some", + "description": "This is a test community.", + "bio_markdown": "This is a sample text about your community.\nYou can change it on the community configuration page.", + "logo_url": "https://ipfs.near.social/ipfs/bafkreibysr2mkwhb4j36h2t7mqwhynqdy4vzjfygfkfg65kuspd2bawauu", + "banner_url": "https://ipfs.near.social/ipfs/bafkreic4xgorjt6ha5z4s5e3hscjqrowe5ahd7hlfc5p4hb6kdfp6prgy4" + } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + // Create add-on + let _ = contract + .call("create_addon") + .args_json(json!({"addon": { + "id": "CommunityAddOnId", + "title": "GitHub AddOn", + "description": "Current status of NEARCORE repo", + "view_widget": "custom-viewer-widget", + "configurator_widget": "github-configurator", + "icon": "bi bi-github", + }})) + .max_gas() + .transact() + .await?; + + let _ = contract + .call("set_community_addons") + .args_json(json!({ + "handle": "gotham", + "addons": [{ + "id": "unique", + "addon_id": "CommunityAddOnId", + "display_name": "GitHub", + "enabled": true, + "parameters": "" + }] + })) + .max_gas() + .transact() + .await?; + + let get_community: serde_json::Value = contract + .call("get_community") + .args_json(json!({ + "handle" : "gotham" + })) + .view() + .await? + .json()?; + + assert_eq!(get_community["addons"][0]["display_name"].as_str(), Some("GitHub")); + + Ok(()) +} + +#[tokio::test] +async fn test_update_community() -> anyhow::Result<()> { + // Initialize the devhub and near social contract on chain, + // contract is devhub contract instance. + let (contract, _, _) = init_contracts_from_res().await?; + + let deposit_amount = NearToken::from_near(4); + + // Add a community + let _create_community = contract + .call("create_community") + .args_json(json!({ + "inputs": { + "handle": "gotham", + "name": "Gotham", + "tag": "some", + "description": "This is a test community.", + "bio_markdown": "This is a sample text about your community.\nYou can change it on the community configuration page.", + "logo_url": "https://ipfs.near.social/ipfs/bafkreibysr2mkwhb4j36h2t7mqwhynqdy4vzjfygfkfg65kuspd2bawauu", + "banner_url": "https://ipfs.near.social/ipfs/bafkreic4xgorjt6ha5z4s5e3hscjqrowe5ahd7hlfc5p4hb6kdfp6prgy4" + } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let _update_community = contract + .call("update_community") + .args_json(json!({ + "handle": "gotham", + "community": { + "admins": [], + "handle": "gotham", + "name": "Gotham2", + "tag": "other", + "description": "This is a test community.", + "bio_markdown": "This is a sample text about your community.\nYou can change it on the community configuration page.", + "logo_url": "https://ipfs.near.social/ipfs/bafkreibysr2mkwhb4j36h2t7mqwhynqdy4vzjfygfkfg65kuspd2bawauu", + "banner_url": "https://ipfs.near.social/ipfs/bafkreic4xgorjt6ha5z4s5e3hscjqrowe5ahd7hlfc5p4hb6kdfp6prgy4", + "addons": [] + } + })) + .max_gas() + .transact() + .await?; + + let get_community: serde_json::Value = contract + .call("get_community") + .args_json(json!({ + "handle" : "gotham" + })) + .view() + .await? + .json()?; + + assert_eq!(get_community["tag"].as_str(), Some("other")); + assert_eq!(get_community["name"].as_str(), Some("Gotham2")); + + Ok(()) +} + +#[tokio::test] +async fn test_announcement() -> anyhow::Result<()> { + // Initialize the devhub and near social contract on chain, + // contract is devhub contract instance. + let (contract, worker, _) = init_contracts_from_res().await?; + + let deposit_amount = NearToken::from_near(4); + + // Add a community + let _create_community = contract + .call("create_community") + .args_json(json!({ + "inputs": { + "handle": "gotham", + "name": "Gotham", + "tag": "some", + "description": "This is a test community.", + "bio_markdown": "This is a sample text about your community.\nYou can change it on the community configuration page.", + "logo_url": "https://ipfs.near.social/ipfs/bafkreibysr2mkwhb4j36h2t7mqwhynqdy4vzjfygfkfg65kuspd2bawauu", + "banner_url": "https://ipfs.near.social/ipfs/bafkreic4xgorjt6ha5z4s5e3hscjqrowe5ahd7hlfc5p4hb6kdfp6prgy4" + } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + println!("{:?}", _create_community); + + let community_account = "gotham.community.devhub.near".parse()?; + + // assert community account exists + let _ = worker.view_account(&community_account).await?; + + // create announcement + let create_announcement = contract + .call("set_community_socialdb") + .args_json(json!({ + "handle": "gotham", + "data": { + "post": { + "main": "{\"type\":\"md\",\"text\":\"what's up\"}" + }, + "index": { + "post": "{\"key\":\"main\",\"value\":{\"type\":\"md\"}}" + } + } + })) + .max_gas() + .transact() + .await?; + + assert!(create_announcement.is_success()); + + let near_social_account = "social.near".parse()?; + let data: serde_json::Value = worker + .view(&near_social_account, "get") + .args_json(json!({"keys": ["gotham.community.devhub.near/**"]})) + .await? + .json()?; + + assert_eq!( + data["gotham.community.devhub.near"]["post"]["main"].as_str(), + Some("{\"type\":\"md\",\"text\":\"what's up\"}") + ); + + // update community, intend to change name and logo + let _update_community = contract + .call("update_community") + .args_json(json!({ + "handle": "gotham", + "community": { + "admins": [], + "handle": "gotham", + "name": "Gotham2", + "tag": "some", + "description": "This is a test community.", + "bio_markdown": "This is a sample text about your community.\nYou can change it on the community configuration page.", + "logo_url": "https://example.com/image.png", + "banner_url": "https://ipfs.near.social/ipfs/bafkreic4xgorjt6ha5z4s5e3hscjqrowe5ahd7hlfc5p4hb6kdfp6prgy4", + "addons": [] + } + })) + .max_gas() + .transact() + .await?; + + let near_social_account = "social.near".parse()?; + let data: serde_json::Value = worker + .view(&near_social_account, "get") + .args_json(json!({"keys": ["gotham.community.devhub.near/**"]})) + .await? + .json()?; + + assert_eq!(data["gotham.community.devhub.near"]["profile"]["name"].as_str(), Some("Gotham2")); + assert_eq!( + data["gotham.community.devhub.near"]["profile"]["image"]["url"].as_str(), + Some("https://example.com/image.png") + ); + + Ok(()) +} + +#[tokio::test] +async fn test_discussions() -> anyhow::Result<()> { + // Initialize the devhub and near social contract on chain, + // contract is devhub contract instance. + let (contract, worker, near_social) = init_contracts_from_res().await?; + + let deposit_amount = NearToken::from_near(6); + + // Add a community + let _ = contract + .call("create_community") + .args_json(json!({ + "inputs": { + "handle": "gotham", + "name": "Gotham", + "tag": "some", + "description": "This is a test community.", + "bio_markdown": "This is a sample text about your community.\nYou can change it on the community configuration page.", + "logo_url": "https://ipfs.near.social/ipfs/bafkreibysr2mkwhb4j36h2t7mqwhynqdy4vzjfygfkfg65kuspd2bawauu", + "banner_url": "https://ipfs.near.social/ipfs/bafkreic4xgorjt6ha5z4s5e3hscjqrowe5ahd7hlfc5p4hb6kdfp6prgy4" + } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let community_account = "gotham.community.devhub.near".parse()?; + let discussions_account = "discussions.gotham.community.devhub.near".parse()?; + + // assert community account exists + let _ = worker.view_account(&community_account).await?; + // assert discussions account exists + let _ = worker.view_account(&discussions_account).await?; + + // grant write permission to discussions account from a user + let user = worker.dev_create_account().await?; + + let deposit_amount = NearToken::from_near(1); + + // user make a post on social.near + let create_post = user + .call(near_social.id(), "set") + .args_json(json!({ + "data": { + user.id().to_string(): { + "post": { + "main": "{\"type\":\"md\",\"text\":\"what's up\"}" + }, + "index": { + "post": "{\"key\":\"main\",\"value\":{\"type\":\"md\"}}" + } + } + } + })) + .deposit(deposit_amount) + .max_gas() + .transact() + .await?; + + assert!(create_post.is_success()); + + let get_block_height: serde_json::Value = worker + .view(&near_social.id(), "get") + .args_json(json!({"keys": [format!("{}/**", user.id())], "options": { + "with_block_height": true, + }})) + .await? + .json()?; + + let block_height = get_block_height[user.id().as_str()]["post"][":block"].clone(); + assert!(block_height.is_number()); + + // create discussion as user + let repost_discussion = user + .call(contract.id(), "create_discussion") + .args_json(json!({ + "handle": "gotham", + "block_height": block_height, + })) + .max_gas() + .transact() + .await?; + + assert!(repost_discussion.is_success()); + + let discussion_data: serde_json::Value = worker + .view(&near_social.id(), "get") + .args_json(json!({"keys": ["discussions.gotham.community.devhub.near/index/**"]})) + .await? + .json()?; + + let post_initiator = user.id().to_string(); + let repost = format!("[{{\"key\":\"main\",\"value\":{{\"type\":\"repost\",\"item\":{{\"type\":\"social\",\"path\":\"{}/post/main\",\"blockHeight\":{}}}}}}},{{\"key\":{{\"type\":\"social\",\"path\":\"{}/post/main\",\"blockHeight\":{}}},\"value\":{{\"type\":\"repost\"}}}}]", post_initiator, block_height, post_initiator, block_height); + + assert_eq!( + discussion_data["discussions.gotham.community.devhub.near"]["index"]["repost"].as_str(), + Some(repost.as_str()) + ); + + Ok(()) +} diff --git a/tests/migration.rs b/tests/migration.rs new file mode 100644 index 0000000..fc33e24 --- /dev/null +++ b/tests/migration.rs @@ -0,0 +1,409 @@ +mod test_env; + +use near_sdk::NearToken; +use {crate::test_env::*, serde_json::json}; + +#[tokio::test] +async fn test_deploy_contract_self_upgrade() -> anyhow::Result<()> { + // Test Flow: + // 1. Deploy devhub and near social contract on sandbox + // 2. Add all kinds of posts and add a community. + // 3. Upgrade the contract. + // 4. Get all the posts and community and check if migration was successful. + + // Initialize the devhub and near social contract on chain, + // contract is devhub contract instance. + let contract = init_contracts_from_mainnet().await?; + + let deposit_amount = NearToken::from_millinear(100); + + // Add Posts + let add_idea_post = contract + .call("add_post") + .args_json(json!({ + "parent_id": null, + "labels": [], + "body": { + "name": "This is a test idea.", + "description": "This is a test description.", + "post_type": "Idea", + "idea_version": "V1" + } + })) + .deposit(deposit_amount) + .transact() + .await?; + + assert!(add_idea_post.is_success()); + + let add_solution_v2_post = contract + .call("add_post") + .args_json(json!({ + "parent_id": null, + "labels": [], + "body": { + "name": "Solution Test", + "description": "This is a test solution post.", + "post_type": "Solution", + "requested_sponsor": "neardevgov.near", + "requested_sponsorship_amount": "1000", + "requested_sponsorship_token": "NEAR", + "solution_version": "V2" + } + })) + .deposit(deposit_amount) + .max_gas() + .transact() + .await?; + + assert!(add_solution_v2_post.is_success()); + + let add_comment_post = contract + .call("add_post") + .args_json(json!({ + "parent_id": 0, + "labels": [], + "body": { + "description": "This is test Comment.", + "comment_version": "V2", + "post_type": "Comment" + } + })) + .deposit(deposit_amount) + .max_gas() + .transact() + .await?; + + assert!(add_comment_post.is_success()); + + let add_attestation_post = contract + .call("add_post") + .args_json(json!({ + "parent_id": 1, + "labels": [], + "body": { + "name": "Attestation", + "description": "Description", + "attestation_version": "V1", + "post_type": "Attestation" + } + })) + .deposit(deposit_amount) + .max_gas() + .transact() + .await?; + + assert!(add_attestation_post.is_success()); + + let add_sponsorship_post_with_near = contract + .call("add_post") + .args_json(json!({ + "parent_id": 1, + "labels": [], + "body": { + "name": "Contributor fellowship", + "description": "Funding approved", + "amount": "1000", + "sponsorship_token": "NEAR", + "supervisor": "john.near", + "sponsorship_version": "V1", + "post_type": "Sponsorship" + } + })) + .deposit(deposit_amount) + .max_gas() + .transact() + .await?; + + assert!(add_sponsorship_post_with_near.is_success()); + + let add_sponsorship_post_with_usd = contract + .call("add_post") + .args_json(json!({ + "parent_id": 1, + "labels": [], + "body": { + "name": "Contributor fellowship", + "description": "Funding approved", + "amount": "1000", + "sponsorship_token": "USD", + "supervisor": "john.near", + "sponsorship_version": "V1", + "post_type": "Sponsorship" + } + })) + .deposit(deposit_amount) + .max_gas() + .transact() + .await?; + + assert!(add_sponsorship_post_with_usd.is_success()); + + let add_sponsorship_post_with_nep141 = contract + .call("add_post") + .args_json(json!({ + "parent_id": 1, + "labels": [], + "body": { + "name": "Contributor fellowship", + "description": "Funding approved", + "amount": "1000", + "sponsorship_token": { + "NEP141": { + "address": "usdt.tether-token.near" + } + }, + "supervisor": "john.near", + "sponsorship_version": "V1", + "post_type": "Sponsorship" + } + })) + .deposit(deposit_amount) + .max_gas() + .transact() + .await?; + + assert!(add_sponsorship_post_with_nep141.is_success()); + + // Add a community + let create_community = contract + .call("create_community") + .args_json(json!({ + "inputs": { + "handle": "gotham", + "name": "Gotham", + "tag": "some", + "description": "This is a test community.", + "bio_markdown": "This is a sample text about your community.\nYou can change it on the community configuration page.", + "logo_url": "https://ipfs.near.social/ipfs/bafkreibysr2mkwhb4j36h2t7mqwhynqdy4vzjfygfkfg65kuspd2bawauu", + "banner_url": "https://ipfs.near.social/ipfs/bafkreic4xgorjt6ha5z4s5e3hscjqrowe5ahd7hlfc5p4hb6kdfp6prgy4" + } + })) + .deposit(NearToken::from_near(4)) + .max_gas() + .transact() + .await?; + + assert!(dbg!(create_community).is_success()); + + let _add_proposal = contract + .call("add_proposal") + .args_json(json!({ + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "Marketing", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "DRAFT"} + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let _edit_proposal_timeline_payment = contract + .call("edit_proposal_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "PAYMENT_PROCESSING", "kyc_verified": false, "test_transaction_sent": false, "request_for_trustees_created": false, "sponsor_requested_review": true, "reviewer_completed_attestation": false } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + // Call self upgrade with current branch code + // compile the current code + let wasm = near_workspaces::compile_project("./").await?; + + let mut contract_upgrade_result = + contract.call("unsafe_self_upgrade").args(wasm).max_gas().transact().await?; + + while contract_upgrade_result.json::()? == "needs-migration" { + contract_upgrade_result = + contract.call("unsafe_migrate").args_json(json!({})).max_gas().transact().await?; + } + + let get_idea_post: serde_json::Value = contract + .call("get_post") + .args_json(json!({ + "post_id" : 0 + })) + .view() + .await? + .json()?; + + insta::assert_json_snapshot!(get_idea_post, {".snapshot.timestamp" => "[timestamp]"}); + + let get_solution_v2_post: serde_json::Value = contract + .call("get_post") + .args_json(json!({ + "post_id" : 1 + })) + .view() + .await? + .json()?; + + insta::assert_json_snapshot!(get_solution_v2_post, {".snapshot.timestamp" => "[timestamp]"}); + + let get_comment_posts: serde_json::Value = contract + .call("get_posts") + .args_json(json!({ + "parent_id" : 0 + })) + .view() + .await? + .json()?; + + insta::assert_json_snapshot!(get_comment_posts, {"[].snapshot.timestamp" => "[timestamp]"}); + + let get_attestation_sponsorship_posts: serde_json::Value = contract + .call("get_posts") + .args_json(json!({ + "parent_id" : 1 + })) + .view() + .await? + .json()?; + + insta::assert_json_snapshot!(get_attestation_sponsorship_posts, {"[].snapshot.timestamp" => "[timestamp]"}); + + let get_sponsorship_post_with_near: serde_json::Value = contract + .call("get_post") + .args_json(json!({ + "post_id" : 4 + })) + .view() + .await? + .json()?; + + insta::assert_json_snapshot!(get_sponsorship_post_with_near, {".snapshot.timestamp" => "[timestamp]"}); + + let get_sponsorship_post_with_usd: serde_json::Value = contract + .call("get_post") + .args_json(json!({ + "post_id" : 5 + })) + .view() + .await? + .json()?; + + insta::assert_json_snapshot!(get_sponsorship_post_with_usd, {".snapshot.timestamp" => "[timestamp]"}); + + let get_sponsorship_post_with_nep141: serde_json::Value = contract + .call("get_post") + .args_json(json!({ + "post_id" : 6 + })) + .view() + .await? + .json()?; + + insta::assert_json_snapshot!(get_sponsorship_post_with_nep141, {".snapshot.timestamp" => "[timestamp]"}); + + let get_community: serde_json::Value = contract + .call("get_community") + .args_json(json!({ + "handle" : "gotham" + })) + .view() + .await? + .json()?; + + insta::assert_json_snapshot!(get_community); + + let get_proposal: serde_json::Value = contract + .call("get_proposal") + .args_json(json!({ + "proposal_id" : 0 + })) + .view() + .await? + .json()?; + + insta::assert_json_snapshot!(get_proposal, {".snapshot.timestamp" => "[timestamp]", ".social_db_post_block_height" => "91", ".snapshot_history[0].timestamp" => "[timestamp]"}); + + let _set_global_labels = contract + .call("set_global_labels") + .args_json(json!({ + "labels": [ + { + "value": "test1", + "title": "test1 description", + "color": [255, 0, 0] + }, + { + "value": "test2", + "title": "test2 description", + "color": [0, 255, 0] + }, + { + "value": "test3", + "title": "test3 description", + "color": [0, 0, 255] + } + ] + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let _add_rfp = contract + .call("add_rfp") + .args_json(json!({ + "body": { + "rfp_body_version": "V0", + "name": "Some RFP", + "description": "some description", + "summary": "sum", + "timeline": {"status": "ACCEPTING_SUBMISSIONS"}, + "submission_deadline": "1707821848175250170" + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + println!("_add_rfp: {:?}", _add_rfp); + + let _add_proposal = contract + .call("add_proposal") + .args_json(json!({ + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "Marketing", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "DRAFT"} + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_add_rfp.is_success()); + assert!(_add_proposal.is_success()); + + Ok(()) +} diff --git a/tests/proposals.rs b/tests/proposals.rs new file mode 100644 index 0000000..2349203 --- /dev/null +++ b/tests/proposals.rs @@ -0,0 +1,663 @@ +mod test_env; + +use near_sdk::NearToken; +use serde_json::Value; +use {crate::test_env::*, serde_json::json}; + +#[tokio::test] +async fn test_proposal() -> anyhow::Result<()> { + // Initialize the devhub and near social contract on chain, + // contract is devhub contract instance. + let (contract, worker, near_social) = init_contracts_from_res().await?; + + let deposit_amount = NearToken::from_near(2); + + let add_idea_post = contract + .call("add_post") + .args_json(json!({ + "parent_id": null, + "labels": [], + "body": { + "name": "This is a test idea.", + "description": "This is a test description.", + "post_type": "Idea", + "idea_version": "V1" + } + })) + .deposit(deposit_amount) + .transact() + .await?; + + println!("add idea post: {:?}", add_idea_post); + assert!(add_idea_post.is_success()); + + + let _set_categories = contract + .call("set_allowed_categories") + .args_json(json!({"new_categories": ["Marketing", "Events"]})) + .max_gas() + .deposit(NearToken::from_near(1)) + .transact() + .await?; + + let _add_proposal = contract + .call("add_proposal") + .args_json(json!({ + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "Marketing", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "DRAFT"} + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let get_proposal: serde_json::Value = contract + .call("get_proposal") + .args_json(json!({ + "proposal_id" : 0 + })) + .view() + .await? + .json()?; + + assert_eq!(get_proposal["snapshot"]["category"], "Marketing"); + + let social_db_post_block_height: u64 = + get_proposal["social_db_post_block_height"].as_str().unwrap().parse::()?; + assert!(social_db_post_block_height > 0); + + let first_proposal_social_post = String::from_utf8( + near_social + .call("get") + .args_json(json!({"keys": [format!("{}/post/main", contract.id())]})) + .view() + .await? + .result, + ) + .unwrap(); + + assert_eq!(first_proposal_social_post, "{\"devhub.near\":{\"post\":{\"main\":\"{\\\"type\\\":\\\"md\\\",\\\"text\\\":\\\"We have just received a new *Marketing* proposal.\\\\n\\\\n———\\\\n\\\\n**By**: @devhub.near\\\\n\\\\n**Title**: “another post“\\\\n\\\\n**Summary**:\\\\n\\\\nsum\\\\n\\\\n———\\\\n\\\\nRead the full proposal and share your feedback on [DevHub](/devhub.near/widget/app?page=proposal&id=0)\\\"}\"}}}"); + let _edit_proposal_category = contract + .call("edit_proposal") + .args_json(json!({ + "id": 0, + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "Events", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "REVIEW", "sponsor_requested_review": true, "reviewer_completed_attestation": false } + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let get_proposal_with_new_category: serde_json::Value = contract + .call("get_proposal") + .args_json(json!({ + "proposal_id" : 0 + })) + .view() + .await? + .json()?; + + assert_eq!(get_proposal_with_new_category["snapshot"]["category"], "Events"); + + let _add_second_proposal = contract + .call("add_proposal") + .args_json(json!({ + "body": { + "proposal_body_version": "V0", + "name": "One more", + "description": "some description", + "category": "Events", + "summary": "sum", + "linked_proposals": [], + "requested_sponsorship_usd_amount": "200", + "requested_sponsorship_paid_in_currency": "NEAR", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "DRAFT"} + }, + "labels": ["test3"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let get_proposals = + contract.call("get_proposals").args_json(json!({})).view().await?.json::()?; + + let proposals_array = get_proposals.as_array().unwrap(); + + assert_eq!(proposals_array.len(), 2); + assert_eq!(proposals_array.get(1).unwrap()["snapshot"]["name"], "One more"); + + let get_proposal_ids = + contract.call("get_all_proposal_ids").args_json(json!({})).view().await?.json::()?; + + let proposal_ids = get_proposal_ids + .as_array() + .unwrap() + .iter() + .map(|x| x.clone().as_u64().unwrap()) + .collect::>(); + + let expected_ids: Vec = [0u64, 1u64].to_vec(); + + assert_eq!(proposal_ids, expected_ids); + + let second_account = worker + .root_account()? + .create_subaccount("second") + .initial_balance(NearToken::from_near(20)) + .transact() + .await? + .into_result()?; + + let _second_author_add_proposal = second_account + .call(contract.id(), "add_proposal") + .args_json(json!({ + "body": { + "proposal_body_version": "V0", + "name": "another author", + "description": "some description", + "category": "Events", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "DRAFT"} + }, + "labels": ["test2", "test3"], + })) + .max_gas() + .deposit(NearToken::from_near(1)) + .transact() + .await?; + + let second_proposal_social_post = String::from_utf8( + near_social + .call("get") + .args_json(json!({"keys": [format!("{}/post/main", contract.id())]})) + .view() + .await? + .result, + ) + .unwrap(); + + assert_eq!(second_proposal_social_post, "{\"devhub.near\":{\"post\":{\"main\":\"{\\\"type\\\":\\\"md\\\",\\\"text\\\":\\\"We have just received a new *Events* proposal.\\\\n\\\\n———\\\\n\\\\n**By**: @second.test.near\\\\n\\\\n**Title**: “another author“\\\\n\\\\n**Summary**:\\\\n\\\\nsum\\\\n\\\\n———\\\\n\\\\nRead the full proposal and share your feedback on [DevHub](/devhub.near/widget/app?page=proposal&id=2)\\\"}\"}}}"); + + let get_second_author_proposal: serde_json::Value = contract + .call("get_proposal") + .args_json(json!({ + "proposal_id" : 2 + })) + .view() + .await? + .json()?; + + assert_eq!(get_second_author_proposal["author_id"], "second.test.near"); + + let get_proposals_by_author = contract + .call("get_proposals_by_author") + .args_json(json!({ + "author": "devhub.near" + })) + .view() + .await? + .json::()?; + + let proposals_array = get_proposals_by_author.as_array().unwrap(); + + assert_eq!(proposals_array.len(), 2); + + let get_proposals_by_label = contract + .call("get_proposals_by_label") + .args_json(json!({ + "label": "test2" + })) + .view() + .await? + .json::()?; + + let proposal_ids_by_label = get_proposals_by_label + .as_array() + .unwrap() + .iter() + .map(|x| x.as_u64().unwrap()) + .collect::>(); + + let expected_ids: Vec = [0u64, 2u64].to_vec(); + assert_eq!(proposal_ids_by_label, expected_ids); + + let get_all_proposal_labels = contract + .call("get_all_proposal_labels") + .args_json(json!({})) + .view() + .await? + .json::()?; + + let proposal_labels = get_all_proposal_labels + .as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap()) + .collect::>(); + + let expected_labels: Vec<&str> = ["test1", "test2", "test3"].to_vec(); + assert_eq!(proposal_labels, expected_labels); + + let get_all_proposal_authors = contract + .call("get_all_proposal_authors") + .args_json(json!({})) + .view() + .await? + .json::()?; + + let proposal_authors = get_all_proposal_authors + .as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap()) + .collect::>(); + + let expected_authors: Vec<&str> = ["devhub.near", "second.test.near"].to_vec(); + assert_eq!(proposal_authors, expected_authors); + + let is_allowed_to_edit_proposal_false = contract + .call("is_allowed_to_edit_proposal") + .args_json(json!({ + "proposal_id": 0, + "editor": "second.test.near" + })) + .view() + .await? + .json::()?; + + assert!(!is_allowed_to_edit_proposal_false.as_bool().unwrap()); + + let is_allowed_to_edit_proposal_true = contract + .call("is_allowed_to_edit_proposal") + .args_json(json!({ + "proposal_id": 0, + "editor": "devhub.near" + })) + .view() + .await? + .json::()?; + + assert!(is_allowed_to_edit_proposal_true.as_bool().unwrap()); + + let get_all_allowed_proposal_labels = contract + .call("get_all_allowed_proposal_labels") + .args_json(json!({ + "editor": "devhub.near" + })) + .view() + .await? + .json::()?; + + let allowed_proposal_labels = get_all_allowed_proposal_labels + .as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap()) + .collect::>(); + + let expected_labels: Vec<&str> = ["test1", "test2", "test3"].to_vec(); + assert_eq!(allowed_proposal_labels, expected_labels); + + let add_proposal_incorrect_timeline_status = contract + .call("add_proposal") + .args_json(json!({ + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "Events", + "summary": "sum", + "linked_proposals": [], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "REVIEW", "sponsor_requested_review": true, "reviewer_completed_attestation": false } + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + assert!(add_proposal_incorrect_timeline_status.is_failure()); + + let edit_proposal_incorrect_timeline_status = second_account.call(contract.id(), "edit_proposal") + .args_json(json!({ + "id": 2, + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "Events", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "REVIEW", "sponsor_requested_review": true, "reviewer_completed_attestation": false } + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(edit_proposal_incorrect_timeline_status.is_failure()); + + let edit_proposal_to_review = second_account.call(contract.id(), "edit_proposal") + .args_json(json!({ + "id": 2, + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "Events", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "REVIEW", "sponsor_requested_review": false, "reviewer_completed_attestation": false } + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(edit_proposal_to_review.is_success()); + + let edit_proposal_to_cancelled: near_workspaces::result::ExecutionFinalResult = second_account.call(contract.id(), "edit_proposal") + .args_json(json!({ + "id": 2, + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "Events", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "CANCELLED", "sponsor_requested_review": false, "reviewer_completed_attestation": false } + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(edit_proposal_to_cancelled.is_success()); + + let set_categories_not_allowed = second_account + .call(contract.id(), "set_allowed_categories") + .args_json(json!({"new_categories": ["One", "Two"]})) + .max_gas() + .deposit(NearToken::from_near(1)) + .transact() + .await?; + + assert!(set_categories_not_allowed.is_failure()); + + let _set_categories = contract + .call("set_allowed_categories") + .args_json(json!({"new_categories": ["Two", "Three"]})) + .max_gas() + .deposit(NearToken::from_near(1)) + .transact() + .await?; + + let get_categories: serde_json::Value = + contract.call("get_allowed_categories").args_json(json!({})).view().await?.json()?; + + let categories: Vec = get_categories + .as_array() + .unwrap() + .iter() + .map(|x| String::from(x.clone().as_str().unwrap())) + .collect::>(); + + assert_eq!(categories, vec!["Two", "Three"]); + + let edit_proposal_incorrect_category = contract + .call("edit_proposal") + .args_json(json!({ + "id": 0, + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "bad cat", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "REVIEW", "sponsor_requested_review": true, "reviewer_completed_attestation": false } + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(edit_proposal_incorrect_category.is_failure()); + + let _edit_proposal_timeline_approved = contract + .call("edit_proposal") + .args_json(json!({ + "id": 0, + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "Three", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "APPROVED", "sponsor_requested_review": true, "reviewer_completed_attestation": false } + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_proposal_timeline_approved.is_success()); + + let edit_proposal_to_cancelled_incorrect: near_workspaces::result::ExecutionFinalResult = second_account.call(contract.id(), "edit_proposal") + .args_json(json!({ + "id": 2, + "body": { + "proposal_body_version": "V0", + "name": "another post", + "description": "some description", + "category": "Events", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "CANCELLED", "sponsor_requested_review": false, "reviewer_completed_attestation": false } + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(edit_proposal_to_cancelled_incorrect.is_failure()); + + let _edit_proposal_timeline_rejected = contract + .call("edit_proposal_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "REJECTED", "sponsor_requested_review": true, "reviewer_completed_attestation": false } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_proposal_timeline_rejected.is_success()); + + let _edit_proposal_timeline_conditionally = contract + .call("edit_proposal_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "APPROVED_CONDITIONALLY", "sponsor_requested_review": true, "reviewer_completed_attestation": false } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_proposal_timeline_conditionally.is_success()); + + let _edit_proposal_timeline_payment = contract + .call("edit_proposal_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "PAYMENT_PROCESSING", "kyc_verified": false, "test_transaction_sent": false, "request_for_trustees_created": false, "sponsor_requested_review": true, "reviewer_completed_attestation": false } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_proposal_timeline_payment.is_success()); + + let _edit_proposal_timeline_funded = contract + .call("edit_proposal_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "FUNDED", "trustees_released_payment": false, "kyc_verified": false, "test_transaction_sent": false, "request_for_trustees_created": false, "sponsor_requested_review": true, "reviewer_completed_attestation": false, "payouts": [ "https://nearblocks.io/txns/6UwrzrYqBhA3ft2mDHXtvpzEFwkWhvCauJS1FGKjG37p" ] } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_proposal_timeline_funded.is_success()); + + let get_proposal: serde_json::Value = contract + .call("get_proposal") + .args_json(json!({ + "proposal_id" : 0 + })) + .view() + .await? + .json()?; + + assert_eq!(get_proposal["snapshot"]["timeline"]["status"], "FUNDED"); + + let _add_team = contract + .call("add_member") + .args_json(json!({ + "member": "team:moderators", + "metadata": { + "member_metadata_version": "V0", + "children": [], + "description": "moderators", + "parents": [], + "permissions": { + "*": ["use-labels", "edit-post"] + } + } + })) + .max_gas() + .deposit(NearToken::from_near(0)) + .transact() + .await?; + + let _add_member = contract + .call("add_member") + .args_json(json!({ + "member": "second.test.near", + "metadata": { + "member_metadata_version": "V0", + "children": [], + "description": "One of the moderators", + "parents": ["team:moderators"], + "permissions": {} + } + })) + .max_gas() + .deposit(NearToken::from_near(0)) + .transact() + .await?; + + let is_allowed_to_edit_proposal_again = contract + .call("is_allowed_to_edit_proposal") + .args_json(json!({ + "proposal_id": 0, + "editor": "second.test.near" + })) + .view() + .await? + .json::()?; + + assert!(is_allowed_to_edit_proposal_again.as_bool().unwrap()); + + Ok(()) +} diff --git a/tests/rfps.rs b/tests/rfps.rs new file mode 100644 index 0000000..a6435ad --- /dev/null +++ b/tests/rfps.rs @@ -0,0 +1,515 @@ +mod test_env; + +use near_sdk::NearToken; +use serde_json::Value; +use {crate::test_env::*, serde_json::json}; + +#[tokio::test] +async fn test_rfp() -> anyhow::Result<()> { + // Initialize the devhub and near social contract on chain, + // contract is devhub contract instance. + let (contract, worker, _) = init_contracts_from_res().await?; + + let deposit_amount = NearToken::from_near(2); + + let _set_global_labels = contract + .call("set_global_labels") + .args_json(json!({ + "labels": [ + { + "value": "test1", + "title": "test1 description", + "color": [255, 0, 0] + }, + { + "value": "test2", + "title": "test2 description", + "color": [0, 255, 0] + }, + { + "value": "test3", + "title": "test3 description", + "color": [0, 0, 255] + } + ] + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let _add_rfp = contract + .call("add_rfp") + .args_json(json!({ + "body": { + "rfp_body_version": "V0", + "name": "Some RFP", + "description": "some description", + "summary": "sum", + "timeline": {"status": "ACCEPTING_SUBMISSIONS"}, + "submission_deadline": "1707821848175250170" + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let get_rfp: serde_json::Value = contract + .call("get_rfp") + .args_json(json!({ + "rfp_id" : 0 + })) + .view() + .await? + .json()?; + + assert_eq!(get_rfp["snapshot"]["summary"], "sum"); + + let social_db_post_block_height: u64 = + get_rfp["social_db_post_block_height"].as_str().unwrap().parse::()?; + assert!(social_db_post_block_height > 0); + + let _edit_rfp = contract + .call("edit_rfp") + .args_json(json!({ + "id": 0, + "body": { + "rfp_body_version": "V0", + "name": "Some RFP", + "description": "another description", + "category": "Events", + "summary": "sum", + "timeline": {"status": "ACCEPTING_SUBMISSIONS"}, + "submission_deadline": "1707821848175250170" + }, + "labels": ["test1", "test2"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let get_edited_rfp: serde_json::Value = contract + .call("get_rfp") + .args_json(json!({ + "rfp_id" : 0 + })) + .view() + .await? + .json()?; + + assert_eq!(get_edited_rfp["snapshot"]["description"], "another description"); + + let _add_second_rfp = contract + .call("add_rfp") + .args_json(json!({ + "body": { + "rfp_body_version": "V0", + "name": "Another RFP", + "description": "another description", + "category": "Events", + "summary": "sum", + "timeline": {"status": "ACCEPTING_SUBMISSIONS"}, + "submission_deadline": "1707821848175250170" + }, + "labels": ["test3"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let get_rfps = contract.call("get_rfps").args_json(json!({})).view().await?.json::()?; + + let rfps_array = get_rfps.as_array().unwrap(); + + assert_eq!(rfps_array.len(), 2); + assert_eq!(rfps_array.get(1).unwrap()["snapshot"]["name"], "Another RFP"); + + let get_rfp_ids = + contract.call("get_all_rfp_ids").args_json(json!({})).view().await?.json::()?; + + let rfp_ids = get_rfp_ids + .as_array() + .unwrap() + .iter() + .map(|x| x.clone().as_u64().unwrap()) + .collect::>(); + + let expected_ids: Vec = [0u64, 1u64].to_vec(); + + assert_eq!(rfp_ids, expected_ids); + + let second_account = worker + .root_account()? + .create_subaccount("second") + .initial_balance(NearToken::from_near(20)) + .transact() + .await? + .into_result()?; + + let _second_author_add_rfp = second_account + .call(contract.id(), "add_rfp") + .args_json(json!({ + "body": { + "rfp_body_version": "V0", + "name": "Another Author", + "description": "another description", + "category": "Events", + "summary": "sum", + "timeline": {"status": "ACCEPTING_SUBMISSIONS"}, + "submission_deadline": "1707821848175250170" + }, + "labels": ["test2", "test3"], + })) + .max_gas() + .deposit(NearToken::from_near(1)) + .transact() + .await?; + + assert!(_second_author_add_rfp.is_failure()); + + let get_rfps_by_label = contract + .call("get_rfps_by_label") + .args_json(json!({ + "label": "test2" + })) + .view() + .await? + .json::()?; + + let rfp_ids_by_label = get_rfps_by_label + .as_array() + .unwrap() + .iter() + .map(|x| x.as_u64().unwrap()) + .collect::>(); + + let expected_ids: Vec = [0u64].to_vec(); + assert_eq!(rfp_ids_by_label, expected_ids); + + let is_allowed_to_edit_rfp_false = contract + .call("is_allowed_to_write_rfps") + .args_json(json!({ + "rfp_id": 0, + "editor": "second.test.near" + })) + .view() + .await? + .json::()?; + + assert!(!is_allowed_to_edit_rfp_false.as_bool().unwrap()); + + let is_allowed_to_edit_rfp_true = contract + .call("is_allowed_to_write_rfps") + .args_json(json!({ + "rfp_id": 0, + "editor": "devhub.near" + })) + .view() + .await? + .json::()?; + + assert!(is_allowed_to_edit_rfp_true.as_bool().unwrap()); + + let _add_rfp_incorrect_label = contract + .call("add_rfp") + .args_json(json!({ + "body": { + "rfp_body_version": "V0", + "name": "Some RFP", + "description": "some description", + "summary": "sum", + "timeline": {"status": "ACCEPTING_SUBMISSIONS"}, + "submission_deadline": "1707821848175250170" + }, + "labels": ["test4"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_add_rfp_incorrect_label.is_failure()); + + let _add_rfp_proposal = second_account + .call(contract.id(), "add_proposal") + .args_json(json!({ + "body": { + "proposal_body_version": "V0", + "name": "RFP-proposal", + "description": "some description", + "category": "Marketing", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "DRAFT"}, + }, + "labels": ["test1"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let _edit_proposal_linked_rfp_incorrect = contract + .call("edit_proposal_linked_rfp") + .args_json(json!({ + "id": 0, + "rfp_id": 2, + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_proposal_linked_rfp_incorrect.is_failure()); + + let _edit_proposal_linked_rfp = second_account + .call(contract.id(), "edit_proposal_linked_rfp") + .args_json(json!({ + "id": 0, + "rfp_id": 0, + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let get_proposal: serde_json::Value = contract + .call("get_proposal") + .args_json(json!({ + "proposal_id" : 0 + })) + .view() + .await? + .json()?; + + let proposal_labels = get_proposal["snapshot"]["labels"] + .as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap()) + .collect::>(); + + let expected_labels: Vec<&str> = ["test1", "test2"].to_vec(); + assert_eq!(proposal_labels, expected_labels); + + assert_eq!(get_proposal["snapshot"]["linked_rfp"], 0); + + let _edit_rfp_labels = contract + .call("edit_rfp") + .args_json(json!({ + "id": 0, + "body": { + "rfp_body_version": "V0", + "name": "Some RFP", + "description": "some description", + "summary": "sum", + "timeline": {"status": "ACCEPTING_SUBMISSIONS"}, + "submission_deadline": "1707821848175250170" + }, + "labels": ["test2", "test3"], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let get_proposal: serde_json::Value = contract + .call("get_proposal") + .args_json(json!({ + "proposal_id" : 0 + })) + .view() + .await? + .json()?; + + let proposal_labels = get_proposal["snapshot"]["labels"] + .as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap()) + .collect::>(); + + let expected_labels: Vec<&str> = ["test3", "test2"].to_vec(); + assert_eq!(proposal_labels, expected_labels); + + let edit_proposal = contract + .call("edit_proposal") + .args_json(json!({ + "id": 0, + "body": { + "proposal_body_version": "V1", + "name": "RFP-proposal", + "description": "some description", + "category": "Marketing", + "summary": "sum", + "linked_proposals": [1, 3], + "requested_sponsorship_usd_amount": "1000000000", + "requested_sponsorship_paid_in_currency": "USDT", + "receiver_account": "polyprogrammist.near", + "supervisor": "frol.near", + "requested_sponsor": "neardevdao.near", + "timeline": {"status": "DRAFT"}, + "linked_rfp": 0, + }, + "labels": [], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + println!("edit_proposal: {:?}", edit_proposal); + + assert!(edit_proposal.is_success()); + + let get_proposal: serde_json::Value = contract + .call("get_proposal") + .args_json(json!({ + "proposal_id" : 0 + })) + .view() + .await? + .json()?; + + let proposal_labels = get_proposal["snapshot"]["labels"] + .as_array() + .unwrap() + .iter() + .map(|x| x.as_str().unwrap()) + .collect::>(); + + let expected_labels: Vec<&str> = ["test3", "test2"].to_vec(); + assert_eq!(proposal_labels, expected_labels); + + let _edit_rfp_timeline_evaluation = contract + .call("edit_rfp_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "EVALUATION" } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_rfp_timeline_evaluation.is_success()); + + let _edit_rfp_timeline_proposal_selected = contract + .call("edit_rfp_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "PROPOSAL_SELECTED" } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_rfp_timeline_proposal_selected.is_failure()); + + let _approve_proposal = contract + .call("edit_proposal_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "APPROVED", "sponsor_requested_review": true, "reviewer_completed_attestation": false } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let _edit_rfp_timeline_proposal_selected = contract + .call("edit_rfp_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "PROPOSAL_SELECTED" } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_rfp_timeline_proposal_selected.is_success()); + + let _edit_rfp_timeline_accepting_submissions = contract + .call("edit_rfp_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "ACCEPTING_SUBMISSIONS" } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let _edit_proposal_unlink_rfp = contract + .call("edit_proposal_linked_rfp") + .args_json(json!({ + "id": 0, + "rfp_id": 1, + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + let _edit_rfp_timeline_cancelled = contract + .call("edit_rfp_timeline") + .args_json(json!({ + "id": 0, + "timeline": {"status": "CANCELLED" } + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_rfp_timeline_cancelled.is_success()); + + let _edit_proposal_linked_rfp_incorrect_unlink = second_account + .call(contract.id(), "edit_proposal_linked_rfp") + .args_json(json!({ + "id": 0, + "rfp_id": 1, + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + assert!(_edit_proposal_linked_rfp_incorrect_unlink.is_failure()); + + let get_global_labels = + contract.call("get_global_labels").args_json(json!({})).view().await?.json::()?; + + let labels_array = get_global_labels.as_array().unwrap(); + + assert_eq!(labels_array.len(), 3); + + let _cancel_rfp = contract + .call("cancel_rfp") + .args_json(json!({ + "id": 0, + "proposals_to_cancel": [0], + "proposals_to_unlink": [], + })) + .max_gas() + .deposit(deposit_amount) + .transact() + .await?; + + println!("_cancel_rfp: {:?}", _cancel_rfp); + + assert!(_cancel_rfp.is_success()); + + Ok(()) +} diff --git a/tests/test_env.rs b/tests/test_env.rs new file mode 100644 index 0000000..2d19c68 --- /dev/null +++ b/tests/test_env.rs @@ -0,0 +1,112 @@ +use near_sdk::{AccountIdRef, NearToken}; +use near_workspaces::network::Sandbox; +use near_workspaces::types::{AccessKey, KeyType, SecretKey}; +use near_workspaces::{Account, Worker}; + +use serde_json::json; + +const DEVHUB_CONTRACT_PREFIX: &str = "devhub"; +const DEVHUB_CONTRACT: &AccountIdRef = AccountIdRef::new_or_panic("devhub.near"); +const DEVHUB_COMMUNITY_CONTRACT: &AccountIdRef = + AccountIdRef::new_or_panic("community.devhub.near"); +const COMMUNITY_FACTORY_PREFIX: &str = "community"; +const NEAR_SOCIAL: &AccountIdRef = AccountIdRef::new_or_panic("social.near"); +const _TEST_NEAR_SOCIAL: &AccountIdRef = AccountIdRef::new_or_panic("v1.social08.testnet"); +const TEST_SEED: &str = "testificate"; +const DEVHUB_CONTRACT_PATH: &str = "./target/near/devhub.wasm"; +const COMMUNITY_FACTORY_CONTRACT_PATH: &str = + "./community-factory/target/near/devhub_community_factory.wasm"; + +#[allow(dead_code)] +pub async fn init_contracts_from_mainnet() -> anyhow::Result { + let worker = near_workspaces::sandbox().await?; + let mainnet = near_workspaces::mainnet_archival().await?; + + // NEAR social deployment + let near_social = worker + .import_contract(&NEAR_SOCIAL.to_owned(), &mainnet) + .initial_balance(NearToken::from_near(10000)) + .transact() + .await?; + near_social.call("new").transact().await?.into_result()?; + near_social + .call("set_status") + .args_json(json!({ + "status": "Live" + })) + .transact() + .await? + .into_result()?; + + // Devhub contract deployment + let devhub_contract = worker + .import_contract(&DEVHUB_CONTRACT.to_owned(), &mainnet) + .initial_balance(NearToken::from_near(1000)) + .transact() + .await?; + let outcome = devhub_contract.call("new").args_json(json!({})).transact().await?; + assert!(outcome.is_success()); + assert!(format!("{:?}", outcome).contains("Migrated to version:")); + + // Devhub Community contract deployment + worker + .import_contract(&DEVHUB_COMMUNITY_CONTRACT.to_owned(), &mainnet) + .initial_balance(NearToken::from_near(10)) + .transact() + .await?; + + Ok(devhub_contract) +} + +#[allow(dead_code)] +pub async fn init_contracts_from_res( +) -> anyhow::Result<(near_workspaces::Contract, Worker, near_workspaces::Contract)> { + let worker: Worker = near_workspaces::sandbox().await?; + let mainnet = near_workspaces::mainnet_archival().await?; + + // NEAR social deployment + let near_social = worker + .import_contract(&NEAR_SOCIAL.to_owned(), &mainnet) + .initial_balance(NearToken::from_near(10000)) + .transact() + .await?; + near_social.call("new").transact().await?.into_result()?; + near_social + .call("set_status") + .args_json(json!({ + "status": "Live" + })) + .transact() + .await? + .into_result()?; + + let contract_wasm = std::fs::read(DEVHUB_CONTRACT_PATH)?; + let sk = SecretKey::from_seed(KeyType::ED25519, TEST_SEED); + + let _test_near = worker.root_account()?; + let tla_near = Account::from_secret_key("near".parse()?, sk.clone(), &worker); + worker + .patch(tla_near.id()) + .access_key(sk.public_key(), AccessKey::full_access()) + .transact() + .await?; + let contract_account = tla_near + .create_subaccount(DEVHUB_CONTRACT_PREFIX) + .initial_balance(NearToken::from_near(100)) + .transact() + .await? + .into_result()?; + let contract = contract_account.deploy(&contract_wasm).await?.into_result()?; + let _outcome = contract.call("new").args_json(json!({})).transact().await?; + + let community_factory_account = contract_account + .create_subaccount(COMMUNITY_FACTORY_PREFIX) + .initial_balance(NearToken::from_near(10)) + .transact() + .await? + .into_result()?; + let community_factory_wasm = std::fs::read(COMMUNITY_FACTORY_CONTRACT_PATH)?; + let _community_factory = + community_factory_account.deploy(&community_factory_wasm).await?.into_result()?; + Ok((contract, worker, near_social)) +}