Переглянути джерело

Working groups initial mappings and tests (openings, applications)

Leszek Wiesner 3 роки тому
батько
коміт
7ce0483e64

+ 2 - 2
query-node/codegen/package.json

@@ -5,7 +5,7 @@
   "author": "",
   "license": "ISC",
   "dependencies": {
-    "@dzlzv/hydra-cli": "2.0.1-beta.17",
-    "@dzlzv/hydra-typegen": "2.0.1-beta.17"
+    "@dzlzv/hydra-cli": "2.1.0-beta.7",
+    "@dzlzv/hydra-typegen": "2.1.0-beta.7"
   }
 }

+ 318 - 227
query-node/codegen/yarn.lock

@@ -69,17 +69,17 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/runtime@^7.12.5":
+"@babel/runtime@^7.13.10", "@babel/runtime@^7.13.9":
   version "7.13.10"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
   integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
   dependencies:
     regenerator-runtime "^0.13.4"
 
-"@dzlzv/hydra-cli@2.0.1-beta.17":
-  version "2.0.1-beta.17"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-cli/-/hydra-cli-2.0.1-beta.17.tgz#9e79fde559d7c90a429030fb906e2fbc352b2b2e"
-  integrity sha512-qRNr6R3i/ju4Uqk+gAXuJ0OWbDENkW1ghD84m3wRlFJ3DSDDPlXGmmc3+EfsgzAbSYHCOAnfsrWWsRdgrh8RtQ==
+"@dzlzv/hydra-cli@2.1.0-beta.7":
+  version "2.1.0-beta.7"
+  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-cli/-/hydra-cli-2.1.0-beta.7.tgz#87942475ab897e1ac9d8637cabe69339c9f6107e"
+  integrity sha512-l8fQQP3w3zY3K73kQUiecz8gpBbrncnG6KMQQjOGACNAGrVRho5Hhzz60nLu6IJugg/wtuqLhQcSIPdvTVkmfw==
   dependencies:
     "@inquirer/input" "^0.0.13-alpha.0"
     "@inquirer/password" "^0.0.12-alpha.0"
@@ -90,6 +90,7 @@
     "@oclif/plugin-help" "^2"
     "@oclif/plugin-plugins" "^1.9.4"
     "@types/chalk" "^2.2.0"
+    "@types/copyfiles" "^2.4.0"
     "@types/fs-extra" "^8.1.0"
     "@types/graphql" "^14.5.0"
     "@types/listr" "^0.14.2"
@@ -97,6 +98,7 @@
     "@types/node" "^12.12.30"
     chalk "^4.1.0"
     cli-ux "^5.4.9"
+    copyfiles "^2.4.1"
     execa "^4.0.3"
     fs-extra "^9.0.0"
     glob "^7.1.6"
@@ -109,15 +111,15 @@
     tslib "1.11.2"
     warthog "https://github.com/metmirr/warthog/releases/download/v2.23.0/warthog-v2.23.0.tgz"
 
-"@dzlzv/hydra-typegen@2.0.1-beta.17":
-  version "2.0.1-beta.17"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-typegen/-/hydra-typegen-2.0.1-beta.17.tgz#4fafa31254acb65a3e62ce1c114aae77d6e030cb"
-  integrity sha512-NJH8orCtOTvXf58AohyXwCbvhrkCqETAZbvRFqRmNslY3t5kRHfBfrhonN4NzbHURhufXX9z9j8+rt/9doxkPQ==
+"@dzlzv/hydra-typegen@2.1.0-beta.7":
+  version "2.1.0-beta.7"
+  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-typegen/-/hydra-typegen-2.1.0-beta.7.tgz#d08fa39b031c564eded7d7d644601468ebbf2f40"
+  integrity sha512-PwxWmLVvmET/58zZh0VnM7S+HH1/5ozXLnE/l7HrvlBYH1sQms5hGYi7bnRGGU/RKCeXBojBNLsX6YIg7b7yGQ==
   dependencies:
     "@oclif/command" "^1.8.0"
     "@oclif/config" "^1"
     "@oclif/errors" "^1.3.3"
-    "@polkadot/api" "^2.10.1"
+    "@polkadot/api" "4.2.1"
     debug "^4.3.1"
     handlebars "^4.7.6"
     lodash "^4.17.20"
@@ -300,196 +302,236 @@
   resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-1.0.4.tgz#b740f68609dfae8aa71c3a6cab15d816407ba493"
   integrity sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw==
 
-"@polkadot/api-derive@2.10.1":
-  version "2.10.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-2.10.1.tgz#6dc6c0030e036e8a38d44b7e06fd884e9c1b32fb"
-  integrity sha512-cMbXrOyHWJ/uLxNiAjmRa6a8WM/FEDMansWbQGJtN7ebHrJD3t1SE53aM4zgD+AgaEJgPAUfI5RuOrEzxDDTdw==
-  dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@polkadot/api" "2.10.1"
-    "@polkadot/rpc-core" "2.10.1"
-    "@polkadot/types" "2.10.1"
-    "@polkadot/util" "^4.2.1"
-    "@polkadot/util-crypto" "^4.2.1"
+"@polkadot/api-derive@4.2.1":
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-4.2.1.tgz#848a2a9ef947f08660af2571f72ca2b06969f2e3"
+  integrity sha512-TQqhK356IEk7ksMDE/tA3ZKqFEI8O8virZItd/w+RFaBs/HfbDNP8p+xPM5+6Rif3kuBzdubMv3Bdq/OIAJc6g==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/api" "4.2.1"
+    "@polkadot/rpc-core" "4.2.1"
+    "@polkadot/types" "4.2.1"
+    "@polkadot/util" "^6.0.5"
+    "@polkadot/util-crypto" "^6.0.5"
+    "@polkadot/x-rxjs" "^6.0.5"
     bn.js "^4.11.9"
-    memoizee "^0.4.14"
-    rxjs "^6.6.3"
-
-"@polkadot/api@2.10.1", "@polkadot/api@^2.10.1":
-  version "2.10.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-2.10.1.tgz#750987bccbf8e607c3690a7bdfed818bfc2c7571"
-  integrity sha512-C/vd5eGK3SDpPBWfs6tbNJM6uKpThE9GiTs5Lb5yR83J2ssvnZnn4qGOoEZnpPH+2iW7hVS4GR5sE9YcZxUXTg==
-  dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@polkadot/api-derive" "2.10.1"
-    "@polkadot/keyring" "^4.2.1"
-    "@polkadot/metadata" "2.10.1"
-    "@polkadot/rpc-core" "2.10.1"
-    "@polkadot/rpc-provider" "2.10.1"
-    "@polkadot/types" "2.10.1"
-    "@polkadot/types-known" "2.10.1"
-    "@polkadot/util" "^4.2.1"
-    "@polkadot/util-crypto" "^4.2.1"
+
+"@polkadot/api@4.2.1":
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-4.2.1.tgz#8eb0997dc148a34a4aca3a0dcaabad9954565909"
+  integrity sha512-PbXwcLnZr5V5LfKsovMS0TRG+rfJp8lJxluCyOSABDpaz2h1B5R8rdYEZCmXI3qSrT0yu2C6Pp8AjTQHRd7SAA==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/api-derive" "4.2.1"
+    "@polkadot/keyring" "^6.0.5"
+    "@polkadot/metadata" "4.2.1"
+    "@polkadot/rpc-core" "4.2.1"
+    "@polkadot/rpc-provider" "4.2.1"
+    "@polkadot/types" "4.2.1"
+    "@polkadot/types-known" "4.2.1"
+    "@polkadot/util" "^6.0.5"
+    "@polkadot/util-crypto" "^6.0.5"
+    "@polkadot/x-rxjs" "^6.0.5"
     bn.js "^4.11.9"
     eventemitter3 "^4.0.7"
-    rxjs "^6.6.3"
 
-"@polkadot/keyring@^4.2.1":
+"@polkadot/keyring@^6.0.5":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-6.1.1.tgz#937103e3c98bb92942f91502577fcc3b6bcd4aef"
+  integrity sha512-gpOJ8L7MyuFe9/bYOJ+0qIXZIqob1IMl1xrsULTlF03ijENv7XvMeUUf6hN8hbxkgEWugow060M7SUnLQ4zTTA==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/util" "6.1.1"
+    "@polkadot/util-crypto" "6.1.1"
+
+"@polkadot/metadata@4.2.1":
   version "4.2.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-4.2.1.tgz#34bf18ae8cb5822f2ea522c8db62dd0086725ffa"
-  integrity sha512-8kH8jXSIA3I2Gn96o7KjGoLBa7fmc2iB/VKOmEEcMCgJR32HyE8YbeXwc/85OQCheQjG4rJA3RxPQ4CsTsjO7w==
-  dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@polkadot/util" "4.2.1"
-    "@polkadot/util-crypto" "4.2.1"
-
-"@polkadot/metadata@2.10.1":
-  version "2.10.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/metadata/-/metadata-2.10.1.tgz#bea4696c8773af4214c071ab5017bef215d978c1"
-  integrity sha512-ilB81k4ZDFVLHYo8mhxs9VFpL7Vi/Q0tqTSuQ+ziD3U7fYh0QV5si+1nqo5EBzvIKws6hsC7B4bTPQLJHHTC9w==
-  dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@polkadot/types" "2.10.1"
-    "@polkadot/types-known" "2.10.1"
-    "@polkadot/util" "^4.2.1"
-    "@polkadot/util-crypto" "^4.2.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/metadata/-/metadata-4.2.1.tgz#7bff99e44992708469e7b2aa70d0865637d8febe"
+  integrity sha512-oXuKOrKTU0wys5pedKd1OVUDWK8/NoBRCrUYN8fxq3Qq/J9Sz6lF4ZbgX3w22C75l1z2+acsebiZBwlpWgKeqw==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/types" "4.2.1"
+    "@polkadot/types-known" "4.2.1"
+    "@polkadot/util" "^6.0.5"
+    "@polkadot/util-crypto" "^6.0.5"
     bn.js "^4.11.9"
 
-"@polkadot/networks@4.2.1":
+"@polkadot/networks@6.1.1", "@polkadot/networks@^6.0.5":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-6.1.1.tgz#7725b75a421e80ad6bc152c6ba0e0b5f82153939"
+  integrity sha512-QJynYW/S00UT9R59w+RPPhUG7zCQURqCDwyJQWKc+yWxnVacBFKvtokrCbYZIViE1lMY4FvdA6blUT3eYmABYg==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+
+"@polkadot/rpc-core@4.2.1":
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-4.2.1.tgz#249f2e8f359450e365b0784d494814c7348e9a3e"
+  integrity sha512-A67Rt7lFpdauj7O7fRGn9yhII0SpCRJ/NkHWKo/whj8RwIAuOdxLnekGC9Qr26FPi0mAqN5DBQ8vYSDUiLFXxA==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/metadata" "4.2.1"
+    "@polkadot/rpc-provider" "4.2.1"
+    "@polkadot/types" "4.2.1"
+    "@polkadot/util" "^6.0.5"
+    "@polkadot/x-rxjs" "^6.0.5"
+
+"@polkadot/rpc-provider@4.2.1":
   version "4.2.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-4.2.1.tgz#b0ca69807ed60189f1c958bb27cfeb3cb1c6b12b"
-  integrity sha512-T1tg0V0uG09Vdce2O4KfEcWO3/fZh4VYt0bmJ6iPwC+x6yv939X2BKvuFTDDVNT3fqBpGzWQlwiTXYQ15o9bGA==
-  dependencies:
-    "@babel/runtime" "^7.12.5"
-
-"@polkadot/rpc-core@2.10.1":
-  version "2.10.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-2.10.1.tgz#6d9cca349dc03324dbf9c3bfe2a9db555808a664"
-  integrity sha512-oyEEhSwlKW3FNO5v7MJYSoiF5kIxcJKMKVJSIpLHp6G2oHhgKRZtsGlX4n6QJYxIBWb0EueewpkuEMCGAv3R7g==
-  dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@polkadot/metadata" "2.10.1"
-    "@polkadot/rpc-provider" "2.10.1"
-    "@polkadot/types" "2.10.1"
-    "@polkadot/util" "^4.2.1"
-    memoizee "^0.4.14"
-    rxjs "^6.6.3"
-
-"@polkadot/rpc-provider@2.10.1":
-  version "2.10.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-2.10.1.tgz#7929b5aa8899033ba127984b4411baef92a1232d"
-  integrity sha512-VvrFedxIbPrcm3CadZLdVwm3eWyyaZV1Sh0BSGZ2u9Pi2JkONshWrg7mf32SbKhckXWt/BNwUnpCQfIUjnKaDw==
-  dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@polkadot/types" "2.10.1"
-    "@polkadot/util" "^4.2.1"
-    "@polkadot/util-crypto" "^4.2.1"
-    "@polkadot/x-fetch" "^4.2.1"
-    "@polkadot/x-ws" "^4.2.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-4.2.1.tgz#2dbb217773a57fde2a70fc15628e2682e3ac81f8"
+  integrity sha512-Gwfs6JAD4Sp+Uz1kEtBSt1P6C3Lwn9xZ64CupU1/6w3qj9QzTFOKHKoznnekiH5HXSh53qVz2c2OSXptSrwL0Q==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/types" "4.2.1"
+    "@polkadot/util" "^6.0.5"
+    "@polkadot/util-crypto" "^6.0.5"
+    "@polkadot/x-fetch" "^6.0.5"
+    "@polkadot/x-global" "^6.0.5"
+    "@polkadot/x-ws" "^6.0.5"
     bn.js "^4.11.9"
     eventemitter3 "^4.0.7"
 
-"@polkadot/types-known@2.10.1":
-  version "2.10.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-2.10.1.tgz#37bc032aae7db12e9a4480caf5aa65f619cffac9"
-  integrity sha512-RmnRPMoypxodfXRRqO+t4ogeaHTEC1S968+Djo8SYeSSmeUrlo9LdoJ5DZBXd0dTOUJbo0wXl9DOjL5qVnRy6A==
+"@polkadot/types-known@4.2.1":
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-4.2.1.tgz#0f1ccef363359de0370cd5b3cc3e6dfe51a18f38"
+  integrity sha512-/zbvzcCiv6yLhnikVWrN03uJk/3Vuer+sbK8G/pVtLOUhRYdDLOet7VPmRnjH9CGsEGJDQebu0zqW77npg5V2Q==
   dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@polkadot/types" "2.10.1"
-    "@polkadot/util" "^4.2.1"
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/networks" "^6.0.5"
+    "@polkadot/types" "4.2.1"
+    "@polkadot/util" "^6.0.5"
     bn.js "^4.11.9"
 
-"@polkadot/types@2.10.1":
-  version "2.10.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-2.10.1.tgz#84189d508c28d375ec562a049aaf58aa34256a74"
-  integrity sha512-wRs9X7uiSRNQBFxcuCDv++FU+HgFml55U73zsqxDgBb7+bor4QGLPpki8rV+xQOpqhfPjKHN1gosK99sFcC3Aw==
-  dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@polkadot/metadata" "2.10.1"
-    "@polkadot/util" "^4.2.1"
-    "@polkadot/util-crypto" "^4.2.1"
+"@polkadot/types@4.2.1":
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-4.2.1.tgz#7be97d3abda4bb3f9031b43602062ed464596696"
+  integrity sha512-xl8QnbXiJmSm6MUZH/U/ov3ZSXMN+KgNjsTCCzfz2xR5B3eK9ClYcstYYkNSyF12K90Gut9bnNSGZvaCfT2hNQ==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/metadata" "4.2.1"
+    "@polkadot/util" "^6.0.5"
+    "@polkadot/util-crypto" "^6.0.5"
+    "@polkadot/x-rxjs" "^6.0.5"
     "@types/bn.js" "^4.11.6"
     bn.js "^4.11.9"
-    memoizee "^0.4.14"
-    rxjs "^6.6.3"
 
-"@polkadot/util-crypto@4.2.1", "@polkadot/util-crypto@^4.2.1":
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-4.2.1.tgz#a342cd6b400c69ed61cd929917030ed2f43c59d1"
-  integrity sha512-U1rCdzBQxVTA854HRpt2d4InDnPCfHD15JiWAwIzjBvq7i59EcTbVSqV02fcwet/KpmT3XYa25xoiff+alzCBA==
-  dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@polkadot/networks" "4.2.1"
-    "@polkadot/util" "4.2.1"
-    "@polkadot/wasm-crypto" "^2.0.1"
-    "@polkadot/x-randomvalues" "4.2.1"
+"@polkadot/util-crypto@6.1.1", "@polkadot/util-crypto@^6.0.5":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-6.1.1.tgz#dc9ee86656bbaf59b41c5a1cf40fa025b44aaf27"
+  integrity sha512-xKDqudvMCirQZ4df2PiWEdlNntNn5gUx/2gTNId7MoE4j4y0edLTwiQ6B2EgRCyLxCaIY+sw6Z5NL5ik1NHdcw==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/networks" "6.1.1"
+    "@polkadot/util" "6.1.1"
+    "@polkadot/wasm-crypto" "^4.0.2"
+    "@polkadot/x-randomvalues" "6.1.1"
     base-x "^3.0.8"
+    base64-js "^1.5.1"
     blakejs "^1.1.0"
     bn.js "^4.11.9"
     create-hash "^1.2.0"
-    elliptic "^6.5.3"
+    elliptic "^6.5.4"
     hash.js "^1.1.7"
     js-sha3 "^0.8.0"
     scryptsy "^2.1.0"
     tweetnacl "^1.0.3"
     xxhashjs "^0.2.2"
 
-"@polkadot/util@4.2.1", "@polkadot/util@^4.2.1":
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-4.2.1.tgz#1845d03be7e418a14ec2ef929d6288f326f2145d"
-  integrity sha512-eO/IFbSDjqVPPWPnARDFydy2Kt992Th+8ByleTkCRqWk0aNYaseO1pGKNdwrYbLfUR3JlyWqvJ60lITeS+qAfQ==
+"@polkadot/util@6.1.1", "@polkadot/util@^6.0.5":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-6.1.1.tgz#d4ab0bf8f0d38f60b93124e7efb4339c16d0b1a1"
+  integrity sha512-xdm2UF6SIjW50jnIyzT1+m1sLBjulExDAo+7JnrTZe4OQfUL8JyQzJ3jdy9hFNBHjXkLRENc94DR4HSJ+n3Uyg==
   dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@polkadot/x-textdecoder" "4.2.1"
-    "@polkadot/x-textencoder" "4.2.1"
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/x-textdecoder" "6.1.1"
+    "@polkadot/x-textencoder" "6.1.1"
     "@types/bn.js" "^4.11.6"
     bn.js "^4.11.9"
     camelcase "^5.3.1"
-    ip-regex "^4.2.0"
+    ip-regex "^4.3.0"
 
-"@polkadot/wasm-crypto@^2.0.1":
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto/-/wasm-crypto-2.0.1.tgz#cf7384385f832f6389520cc00e52a87fda6f29b6"
-  integrity sha512-Vb0q4NToCRHXYJwhLWc4NTy77+n1dtJmkiE1tt8j1pmY4IJ4UL25yBxaS8NCS1LGqofdUYK1wwgrHiq5A78PFA==
+"@polkadot/wasm-crypto-asmjs@^4.0.2":
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-4.0.2.tgz#f42c353a64e1243841daf90e4bd54eff01a4e3cf"
+  integrity sha512-hlebqtGvfjg2ZNm4scwBGVHwOwfUhy2yw5RBHmPwkccUif3sIy4SAzstpcVBIVMdAEvo746bPWEInA8zJRcgJA==
+  dependencies:
+    "@babel/runtime" "^7.13.9"
 
-"@polkadot/x-fetch@^4.2.1":
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-4.2.1.tgz#6cd157da6f98f97395c3f01849ccdd3de23ee44f"
-  integrity sha512-dfVYvCQQXo2AgoWPi4jQp47eIMjAi6glQQ8Y1OsK4sCqmX7BSkNl9ONUKQuH27oi0BkJ/BL7fwDg55JeB5QrKg==
+"@polkadot/wasm-crypto-wasm@^4.0.2":
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-4.0.2.tgz#89f9e0a1e4d076784d4a42bea37fc8b06bdd8bb6"
+  integrity sha512-de/AfNPZ0uDKFWzOZ1rJCtaUbakGN29ks6IRYu6HZTRg7+RtqvE1rIkxabBvYgQVHIesmNwvEA9DlIkS6hYRFQ==
+  dependencies:
+    "@babel/runtime" "^7.13.9"
+
+"@polkadot/wasm-crypto@^4.0.2":
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto/-/wasm-crypto-4.0.2.tgz#9649057adee8383cc86433d107ba526b718c5a3b"
+  integrity sha512-2h9FuQFkBc+B3TwSapt6LtyPvgtd0Hq9QsHW8g8FrmKBFRiiFKYRpfJKHCk0aCZzuRf9h95bQl/X6IXAIWF2ng==
+  dependencies:
+    "@babel/runtime" "^7.13.9"
+    "@polkadot/wasm-crypto-asmjs" "^4.0.2"
+    "@polkadot/wasm-crypto-wasm" "^4.0.2"
+
+"@polkadot/x-fetch@^6.0.5":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-6.1.1.tgz#433300cd6c9ca98c8e610e34bf30922f480879d9"
+  integrity sha512-9idoVhFjEZYTRhufqqVXSParVebRwTCGYhZx2yYsF5R9+BKS/noTSBBtOmhdICtCT9evl3xgUdZkiiGVfD6sJw==
   dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@types/node-fetch" "^2.5.7"
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/x-global" "6.1.1"
+    "@types/node-fetch" "^2.5.10"
     node-fetch "^2.6.1"
 
-"@polkadot/x-randomvalues@4.2.1":
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-4.2.1.tgz#91fd272f8bb79a59b20055a4514f944888a6ee76"
-  integrity sha512-eOfz/KnHYFVl9l0zlhlwomKMzFASgolaQV6uXSN38np+99/+F38wlbOSXFbfZ5H3vmMCt4y/UUTLtoGV/44yLg==
+"@polkadot/x-global@6.1.1", "@polkadot/x-global@^6.0.5":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-6.1.1.tgz#c9dab736cb0ff3274bebabeb5a9f1c57d126b73c"
+  integrity sha512-h/5K2i8S7oteQITD2aF3Scxd7uOPH4HaBk/DDiM7M89TvzMp+QLISkBapK2boAKRejceKULuKlFVKFGzryF1NA==
   dependencies:
-    "@babel/runtime" "^7.12.5"
+    "@babel/runtime" "^7.13.10"
+    "@types/node-fetch" "^2.5.10"
+    node-fetch "^2.6.1"
 
-"@polkadot/x-textdecoder@4.2.1":
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-4.2.1.tgz#c2fe9f5da9498d982f8fd9244a52e039c0f0dacc"
-  integrity sha512-B5t20PryMKr7kdd7q+kmzJPU01l28ZDD06cQ/ZFkybI7avI6PIz/U33ctXxiHOatbBRO6Ez8uzrWd3JmaQ2bGQ==
+"@polkadot/x-randomvalues@6.1.1":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-6.1.1.tgz#e57a31bc51513c88fd8228a24a154b5e426dcff1"
+  integrity sha512-b6IFmV64YdyWwWYnnqD/piQALRA4Xs/eQklBaldoBzg/INTWx8sA3M43HUOZlmcY4TLNAOG8mBxzrDmsFj3Ezw==
   dependencies:
-    "@babel/runtime" "^7.12.5"
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/x-global" "6.1.1"
 
-"@polkadot/x-textencoder@4.2.1":
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-4.2.1.tgz#cf6b92d7de0fb2dde8314e0f359dd83dc9f25036"
-  integrity sha512-EHc6RS9kjdP28q6EYlSgHF2MrJCdOTc5EVlqHL7V1UKLh3vD6QaWGYBwbzXNFPXO3RYPO/DKYCu4RxAVSM1OOg==
+"@polkadot/x-rxjs@^6.0.5":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/x-rxjs/-/x-rxjs-6.1.1.tgz#dae950f5034c3f357baaea9ad5be3ac6e990c49f"
+  integrity sha512-Hfpb3aIQkVlGM2bknMVZBudW2F7t5TJepPiiLoHJKA6IJ8mpQYZAUOQMUMt/teM8Ni4fTNGqrCGgR/xLm62mRA==
   dependencies:
-    "@babel/runtime" "^7.12.5"
+    "@babel/runtime" "^7.13.10"
+    rxjs "^6.6.7"
 
-"@polkadot/x-ws@^4.2.1":
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-4.2.1.tgz#f160a0c61227419b1d7da623a72ce21063ef69ee"
-  integrity sha512-7L1ve2rshBFI/00/0zkX1k0OP/rSD6Tp0Mj/GSg2UvnsmUb2Bb3OpwUJ4aTDr1En6OVGWj9c0fNO0tZR7rtoYA==
+"@polkadot/x-textdecoder@6.1.1":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-6.1.1.tgz#b19bfc375224c8c4069a6d69bc813419b1715942"
+  integrity sha512-z4a91pNkD6WNDp+Jl4nf6kLM5ZM1x5Zmrrs2qDmX3WP+/okJUb5J+RYXSDnPnKeuoyQHHV0cNuWos5nQZxmmrA==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/x-global" "6.1.1"
+
+"@polkadot/x-textencoder@6.1.1":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-6.1.1.tgz#1963034091398e93465ec2709d68ced88008a62e"
+  integrity sha512-NLL9HxigxKeI30X89z40m6DWu1RrL9mZvXwLbl5Se1ditNsOK5/3+gBSlLqZiuB7LuY0YJCCbkqMSp/mAANglA==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/x-global" "6.1.1"
+
+"@polkadot/x-ws@^6.0.5":
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-6.1.1.tgz#a1685366c2ee741968dcbcf35787d23b6db84a5a"
+  integrity sha512-deUXlOeTiPPukc5B/KwNg24xGSuTOqCE1SF5CjLX2R4e/ypjcjjO34zmmTHPEQBH4ms5t1amwRwyg8WYDXwARw==
   dependencies:
-    "@babel/runtime" "^7.12.5"
-    "@types/websocket" "^1.0.1"
-    websocket "^1.0.32"
+    "@babel/runtime" "^7.13.10"
+    "@polkadot/x-global" "6.1.1"
+    "@types/websocket" "^1.0.2"
+    websocket "^1.0.33"
 
 "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
   version "1.1.2"
@@ -617,6 +659,11 @@
     "@types/keygrip" "*"
     "@types/node" "*"
 
+"@types/copyfiles@^2.4.0":
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/@types/copyfiles/-/copyfiles-2.4.0.tgz#877ef9aa9def7df889fb1ca900206c79a873d113"
+  integrity sha512-ujm66wtJzW0ok5bIfwSZdvI4C4E6rbAvG58zow71wLjPPj65rIMu4Uy5LOx5H4eRvaagGUrrKTxqfLiDSsHEGQ==
+
 "@types/cors@2.8.8":
   version "2.8.8"
   resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.8.tgz#317a8d8561995c60e35b9e0fcaa8d36660c98092"
@@ -825,10 +872,10 @@
     "@types/node" "*"
     form-data "^3.0.0"
 
-"@types/node-fetch@^2.5.7":
-  version "2.5.8"
-  resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb"
-  integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==
+"@types/node-fetch@^2.5.10":
+  version "2.5.10"
+  resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132"
+  integrity sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ==
   dependencies:
     "@types/node" "*"
     form-data "^3.0.0"
@@ -912,7 +959,7 @@
   resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.1.3.tgz#366b394aa3fbeed2392bf0a20ded606fa4a3d35e"
   integrity sha512-DaOWN1zf7j+8nHhqXhIgNmS+ltAC53NXqGxYuBhWqWgqolRhddKzfZU814lkHQSTG0IUfQxU7Cg0gb8fFWo2mA==
 
-"@types/websocket@^1.0.1":
+"@types/websocket@^1.0.2":
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.2.tgz#d2855c6a312b7da73ed16ba6781815bf30c6187a"
   integrity sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ==
@@ -1308,7 +1355,7 @@ base-x@^3.0.8:
   dependencies:
     safe-buffer "^5.0.1"
 
-base64-js@^1.3.1:
+base64-js@^1.3.1, base64-js@^1.5.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
   integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -1717,11 +1764,29 @@ cookie@0.4.0:
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
   integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
 
+copyfiles@^2.4.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.4.1.tgz#d2dcff60aaad1015f09d0b66e7f0f1c5cd3c5da5"
+  integrity sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==
+  dependencies:
+    glob "^7.0.5"
+    minimatch "^3.0.3"
+    mkdirp "^1.0.4"
+    noms "0.0.0"
+    through2 "^2.0.1"
+    untildify "^4.0.0"
+    yargs "^16.1.0"
+
 core-js@^3.0.1:
   version "3.9.1"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.1.tgz#cec8de593db8eb2a85ffb0dbdeb312cb6e5460ae"
   integrity sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg==
 
+core-util-is@~1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+  integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
 cors@^2.8.4:
   version "2.8.5"
   resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
@@ -1920,7 +1985,7 @@ elegant-spinner@^1.0.1:
   resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
   integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
 
-elliptic@^6.5.3:
+elliptic@^6.5.4:
   version "6.5.4"
   resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
   integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
@@ -2000,7 +2065,7 @@ es-to-primitive@^1.2.1:
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
 
-es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
+es5-ext@^0.10.35, es5-ext@^0.10.50:
   version "0.10.53"
   resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
   integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
@@ -2009,7 +2074,7 @@ es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@
     es6-symbol "~3.1.3"
     next-tick "~1.0.0"
 
-es6-iterator@^2.0.3, es6-iterator@~2.0.3:
+es6-iterator@~2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
   integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
@@ -2026,16 +2091,6 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3:
     d "^1.0.1"
     ext "^1.1.2"
 
-es6-weak-map@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53"
-  integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==
-  dependencies:
-    d "1"
-    es5-ext "^0.10.46"
-    es6-iterator "^2.0.3"
-    es6-symbol "^3.1.1"
-
 escalade@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -2066,14 +2121,6 @@ etag@~1.8.1:
   resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
   integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
 
-event-emitter@^0.3.5:
-  version "0.3.5"
-  resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
-  integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=
-  dependencies:
-    d "1"
-    es5-ext "~0.10.14"
-
 eventemitter3@^3.1.0:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
@@ -2380,7 +2427,7 @@ glob-parent@^5.1.0:
   dependencies:
     is-glob "^4.0.1"
 
-glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
+glob@^7.0.5, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
   version "7.1.6"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
   integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@@ -2742,7 +2789,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -2762,7 +2809,7 @@ invert-kv@^2.0.0:
   resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
   integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
 
-ip-regex@^4.2.0:
+ip-regex@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5"
   integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==
@@ -2874,7 +2921,7 @@ is-plain-object@3.0.0:
   dependencies:
     isobject "^4.0.0"
 
-is-promise@^2.1.0, is-promise@^2.2.2:
+is-promise@^2.1.0:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
   integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
@@ -2931,6 +2978,16 @@ is-wsl@^2.1.1, is-wsl@^2.2.0:
   dependencies:
     is-docker "^2.0.0"
 
+isarray@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+  integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
+
+isarray@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+  integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+
 isexe@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -3268,13 +3325,6 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
-lru-queue@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3"
-  integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=
-  dependencies:
-    es5-ext "~0.10.2"
-
 make-error@^1.1.1:
   version "1.3.6"
   resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
@@ -3310,20 +3360,6 @@ mem@^4.0.0:
     mimic-fn "^2.0.0"
     p-is-promise "^2.0.0"
 
-memoizee@^0.4.14:
-  version "0.4.15"
-  resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72"
-  integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==
-  dependencies:
-    d "^1.0.1"
-    es5-ext "^0.10.53"
-    es6-weak-map "^2.0.3"
-    event-emitter "^0.3.5"
-    is-promise "^2.2.2"
-    lru-queue "^0.1.0"
-    next-tick "^1.1.0"
-    timers-ext "^0.1.7"
-
 merge-descriptors@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
@@ -3389,7 +3425,7 @@ minimalistic-crypto-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
 
-minimatch@^3.0.2, minimatch@^3.0.4:
+minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
   integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -3467,11 +3503,6 @@ neo-async@^2.6.0:
   resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
   integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
 
-next-tick@1, next-tick@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
-  integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
-
 next-tick@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
@@ -3499,6 +3530,14 @@ node-gyp-build@^4.2.0:
   resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739"
   integrity sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==
 
+noms@0.0.0:
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859"
+  integrity sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=
+  dependencies:
+    inherits "^2.0.1"
+    readable-stream "~1.0.31"
+
 normalize-package-data@^2.3.2:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -3987,6 +4026,11 @@ prettier@^1.19.1:
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
   integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
 
+process-nextick-args@~2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
 proxy-addr@~2.0.5:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@@ -4071,6 +4115,29 @@ readable-stream@^3.0.0, readable-stream@^3.6.0:
     string_decoder "^1.1.1"
     util-deprecate "^1.0.1"
 
+readable-stream@~1.0.31:
+  version "1.0.34"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
+  integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "0.0.1"
+    string_decoder "~0.10.x"
+
+readable-stream@~2.3.6:
+  version "2.3.7"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+  integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.3"
+    isarray "~1.0.0"
+    process-nextick-args "~2.0.0"
+    safe-buffer "~5.1.1"
+    string_decoder "~1.1.1"
+    util-deprecate "~1.0.1"
+
 redeyed@~2.1.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/redeyed/-/redeyed-2.1.1.tgz#8984b5815d99cb220469c99eeeffe38913e6cc0b"
@@ -4176,14 +4243,21 @@ run-parallel@^1.1.9:
   dependencies:
     queue-microtask "^1.2.2"
 
-rxjs@^6.3.3, rxjs@^6.5.1, rxjs@^6.6.3:
+rxjs@^6.3.3, rxjs@^6.5.1:
   version "6.6.6"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.6.tgz#14d8417aa5a07c5e633995b525e1e3c0dec03b70"
   integrity sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg==
   dependencies:
     tslib "^1.9.0"
 
-safe-buffer@5.1.2:
+rxjs@^6.6.7:
+  version "6.6.7"
+  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
+  integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
+  dependencies:
+    tslib "^1.9.0"
+
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@@ -4452,6 +4526,18 @@ string_decoder@^1.1.1:
   dependencies:
     safe-buffer "~5.2.0"
 
+string_decoder@~0.10.x:
+  version "0.10.31"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+  integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
+
+string_decoder@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+  dependencies:
+    safe-buffer "~5.1.0"
+
 strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -4559,13 +4645,13 @@ thenify-all@^1.0.0:
   dependencies:
     any-promise "^1.0.0"
 
-timers-ext@^0.1.7:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6"
-  integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==
+through2@^2.0.1:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
+  integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
   dependencies:
-    es5-ext "~0.10.46"
-    next-tick "1"
+    readable-stream "~2.3.6"
+    xtend "~4.0.1"
 
 to-regex-range@^5.0.1:
   version "5.0.1"
@@ -4756,6 +4842,11 @@ unpipe@1.0.0, unpipe@~1.0.0:
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
   integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
 
+untildify@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
+  integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
+
 utf-8-validate@^5.0.2:
   version "5.0.4"
   resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.4.tgz#72a1735983ddf7a05a43a9c6b67c5ce1c910f9b8"
@@ -4763,7 +4854,7 @@ utf-8-validate@^5.0.2:
   dependencies:
     node-gyp-build "^4.2.0"
 
-util-deprecate@^1.0.1:
+util-deprecate@^1.0.1, util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
@@ -4887,7 +4978,7 @@ wcwidth@^1.0.1:
   dependencies:
     defaults "^1.0.3"
 
-websocket@^1.0.32:
+websocket@^1.0.33:
   version "1.0.33"
   resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.33.tgz#407f763fc58e74a3fa41ca3ae5d78d3f5e3b82a5"
   integrity sha512-XwNqM2rN5eh3G2CUQE3OHZj+0xfdH42+OFK6LdC2yqiC0YU8e5UK0nYre220T0IyyN031V/XOvtHvXozvJYFWA==
@@ -5037,7 +5128,7 @@ xss@^1.0.8:
     commander "^2.20.3"
     cssfilter "0.0.10"
 
-xtend@^4.0.0:
+xtend@^4.0.0, xtend@~4.0.1:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
   integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
@@ -5144,7 +5235,7 @@ yargs@^12.0.2:
     y18n "^3.2.1 || ^4.0.0"
     yargs-parser "^11.1.1"
 
-yargs@^16.0.0, yargs@^16.0.3:
+yargs@^16.0.0, yargs@^16.0.3, yargs@^16.1.0:
   version "16.2.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
   integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==

+ 61 - 1
query-node/manifest.yml

@@ -26,6 +26,26 @@ typegen:
     - members.ReferralCutUpdated
     - members.InitialInvitationBalanceUpdated
     - members.LeaderInvitationQuotaUpdated
+    # Use Storage Working Group as a reference group (all groups emit the same events)
+    - storageWorkingGroup.OpeningAdded
+    - storageWorkingGroup.AppliedOnOpening
+    - storageWorkingGroup.OpeningFilled
+    - storageWorkingGroup.LeaderSet
+    - storageWorkingGroup.WorkerRoleAccountUpdated
+    - storageWorkingGroup.LeaderUnset
+    - storageWorkingGroup.WorkerExited
+    - storageWorkingGroup.TerminatedWorker
+    - storageWorkingGroup.TerminatedLeader
+    - storageWorkingGroup.StakeSlashed
+    - storageWorkingGroup.StakeDecreased
+    - storageWorkingGroup.StakeIncreased
+    - storageWorkingGroup.ApplicationWithdrawn
+    - storageWorkingGroup.OpeningCanceled
+    - storageWorkingGroup.BudgetSet
+    - storageWorkingGroup.WorkerRewardAccountUpdated
+    - storageWorkingGroup.WorkerRewardAmountUpdated
+    # - storageWorkingGroup.StatusTextChanged FIXME: Hydra bug
+    - storageWorkingGroup.BudgetSpending
   calls:
     - members.updateProfile
     - members.updateAccounts
@@ -38,12 +58,13 @@ mappings:
   # process only blocks with height >= 1M
   # blockInterval: '[1000000,]'
   # js module that exports the handler functions
-  mappingsModule: mappings/lib/mappings
+  mappingsModule: mappings/lib
   # additinal libraries the processor loads
   # typically it is a module with event and extrinsic types generated by hydra-typegen
   imports:
     - mappings/lib/generated/types
   eventHandlers:
+    # Membership module
     - event: members.MembershipBought
       handler: members_MembershipBought(DatabaseManager, SubstrateEvent)
     - event: members.MemberProfileUpdated
@@ -72,6 +93,45 @@ mappings:
       handler: members_InitialInvitationBalanceUpdated(DatabaseManager, SubstrateEvent)
     - event: members.LeaderInvitationQuotaUpdated
       handler: members_LeaderInvitationQuotaUpdated(DatabaseManager, SubstrateEvent)
+    # Working Groups module
+    - event: storageWorkingGroup.OpeningAdded
+      handler: workingGroups_OpeningAdded(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.AppliedOnOpening
+      handler: workingGroups_AppliedOnOpening(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.OpeningFilled
+      handler: workingGroups_OpeningFilled(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.LeaderSet
+      handler: workingGroups_LeaderSet(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.WorkerRoleAccountUpdated
+      handler: workingGroups_WorkerRoleAccountUpdated(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.LeaderUnset
+      handler: workingGroups_LeaderUnset(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.WorkerExited
+      handler: workingGroups_WorkerExited(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.TerminatedWorker
+      handler: workingGroups_TerminatedWorker(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.TerminatedLeader
+      handler: workingGroups_TerminatedLeader(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.StakeSlashed
+      handler: workingGroups_StakeSlashed(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.StakeDecreased
+      handler: workingGroups_StakeDecreased(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.StakeIncreased
+      handler: workingGroups_StakeIncreased(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.ApplicationWithdrawn
+      handler: workingGroups_ApplicationWithdrawn(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.OpeningCanceled
+      handler: workingGroups_OpeningCanceled(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.BudgetSet
+      handler: workingGroups_BudgetSet(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.WorkerRewardAccountUpdated
+      handler: workingGroups_WorkerRewardAccountUpdated(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.WorkerRewardAmountUpdated
+      handler: workingGroups_WorkerRewardAmountUpdated(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.StatusTextChanged
+      handler: workingGroups_StatusTextChanged(DatabaseManager, SubstrateEvent)
+    - event: storageWorkingGroup.BudgetSpending
+      handler: workingGroups_BudgetSpending(DatabaseManager, SubstrateEvent)
   extrinsicHandlers:
     # infer defaults here
     #- extrinsic: Balances.Transfer

+ 14 - 0
query-node/mappings/common.ts

@@ -1,6 +1,7 @@
 import { SubstrateEvent } from '@dzlzv/hydra-common'
 import { EventType } from 'query-node/dist/src/modules/enums/enums'
 import { Event } from 'query-node/dist/src/modules/event/event.model'
+import { Bytes } from '@polkadot/types'
 
 export function createEvent({ blockNumber, extrinsic, index }: SubstrateEvent, type: EventType): Event {
   return new Event({
@@ -11,3 +12,16 @@ export function createEvent({ blockNumber, extrinsic, index }: SubstrateEvent, t
     type,
   })
 }
+
+type MetadataClass<T> = {
+  deserializeBinary: (bytes: Uint8Array) => T
+}
+
+export function deserializeMetadata<T>(metadataType: MetadataClass<T>, metadataBytes: Bytes): T | null {
+  try {
+    return metadataType.deserializeBinary(metadataBytes.toU8a(true))
+  } catch (e) {
+    console.error(`Invalid opening metadata! (${metadataBytes.toHex()})`)
+    return null
+  }
+}

+ 2 - 1
query-node/mappings/index.ts

@@ -1 +1,2 @@
-export * from './mappings'
+export * from './membership'
+export * from './workingGroups'

+ 7 - 15
query-node/mappings/init.ts

@@ -2,29 +2,21 @@ import { ApiPromise, WsProvider } from '@polkadot/api'
 import { types } from '@joystream/types'
 import { makeDatabaseManager } from '@dzlzv/hydra-db-utils'
 import { createDBConnection } from '@dzlzv/hydra-processor'
-import { MembershipSystemSnapshot } from 'query-node/dist/src/modules/membership-system-snapshot/membership-system-snapshot.model'
 import path from 'path'
 
-// Temporary script to initialize processor database with some confing values initially hardcoded in the runtime
+// A script to initialize processor database with some initial values that cannot be fetched from events / extrinics
 async function init() {
   const provider = new WsProvider(process.env.WS_PROVIDER_ENDPOINT_URI)
   const api = await ApiPromise.create({ provider, types })
   const entitiesPath = path.resolve(__dirname, '../../generated/graphql-server/dist/src/modules/**/*.model.js')
+  // We need to create db connection (and configure env) before importing any warthog models
   const dbConnection = await createDBConnection([entitiesPath])
-  const initialInvitationCount = await api.query.members.initialInvitationCount.at(api.genesisHash)
-  const initialInvitationBalance = await api.query.members.initialInvitationBalance.at(api.genesisHash)
-  const referralCut = await api.query.members.referralCut.at(api.genesisHash)
-  const membershipPrice = await api.query.members.membershipPrice.at(api.genesisHash)
   const db = makeDatabaseManager(dbConnection.createEntityManager())
-  const membershipSystem = new MembershipSystemSnapshot({
-    snapshotBlock: 0,
-    snapshotTime: new Date(0),
-    defaultInviteCount: initialInvitationCount.toNumber(),
-    membershipPrice,
-    referralCut: referralCut.toNumber(),
-    invitedInitialBalance: initialInvitationBalance,
-  })
-  await db.save<MembershipSystemSnapshot>(membershipSystem)
+  // Only now we can import the initialization scripts (which include warthog models imports)
+  // eslint-disable-next-line @typescript-eslint/no-var-requires
+  const initializeDb = require('./initializeDb').default
+
+  await initializeDb(api, db)
 }
 
 init()

+ 41 - 0
query-node/mappings/initializeDb.ts

@@ -0,0 +1,41 @@
+import { ApiPromise } from '@polkadot/api'
+import { BalanceOf } from '@polkadot/types/interfaces'
+import { DatabaseManager } from '@dzlzv/hydra-db-utils'
+import { MembershipSystemSnapshot, WorkingGroup } from 'query-node/dist/model'
+
+async function initMembershipSystem(api: ApiPromise, db: DatabaseManager) {
+  const initialInvitationCount = await api.query.members.initialInvitationCount.at(api.genesisHash)
+  const initialInvitationBalance = await api.query.members.initialInvitationBalance.at(api.genesisHash)
+  const referralCut = await api.query.members.referralCut.at(api.genesisHash)
+  const membershipPrice = await api.query.members.membershipPrice.at(api.genesisHash)
+  const membershipSystem = new MembershipSystemSnapshot({
+    snapshotBlock: 0,
+    snapshotTime: new Date(0),
+    defaultInviteCount: initialInvitationCount.toNumber(),
+    membershipPrice,
+    referralCut: referralCut.toNumber(),
+    invitedInitialBalance: initialInvitationBalance,
+  })
+  await db.save<MembershipSystemSnapshot>(membershipSystem)
+}
+
+async function initWorkingGroups(api: ApiPromise, db: DatabaseManager) {
+  const groupNames = Object.keys(api.query).filter((k) => k.endsWith('WorkingGroup'))
+  const groups = await Promise.all(
+    groupNames.map(async (groupName) => {
+      const budget = await api.query[groupName].budget.at<BalanceOf>(api.genesisHash)
+      return new WorkingGroup({
+        name: groupName,
+        workers: [],
+        openings: [],
+        budget,
+      })
+    })
+  )
+  await Promise.all(groups.map((g) => db.save<WorkingGroup>(g)))
+}
+
+export default async function initializeDb(api: ApiPromise, db: DatabaseManager): Promise<void> {
+  await initMembershipSystem(api, db)
+  await initWorkingGroups(api, db)
+}

+ 22 - 19
query-node/mappings/mappings.ts → query-node/mappings/membership.ts

@@ -3,31 +3,34 @@ eslint-disable @typescript-eslint/naming-convention
 */
 import { SubstrateEvent } from '@dzlzv/hydra-common'
 import { DatabaseManager } from '@dzlzv/hydra-db-utils'
-import { Membership } from 'query-node/dist/src/modules/membership/membership.model'
 import { Members } from './generated/types'
 import BN from 'bn.js'
 import { Bytes } from '@polkadot/types'
-import { EventType, MembershipEntryMethod } from 'query-node/dist/src/modules/enums/enums'
-import { MembershipSystemSnapshot } from 'query-node/dist/src/modules/membership-system-snapshot/membership-system-snapshot.model'
-import { MemberMetadata } from 'query-node/dist/src/modules/member-metadata/member-metadata.model'
-import { MembershipBoughtEvent } from 'query-node/dist/src/modules/membership-bought-event/membership-bought-event.model'
-import { MemberProfileUpdatedEvent } from 'query-node/dist/src/modules/member-profile-updated-event/member-profile-updated-event.model'
-import { MemberAccountsUpdatedEvent } from 'query-node/dist/src/modules/member-accounts-updated-event/member-accounts-updated-event.model'
-import { MemberInvitedEvent } from 'query-node/dist/src/modules/member-invited-event/member-invited-event.model'
 import { MemberId, BuyMembershipParameters, InviteMembershipParameters } from '@joystream/types/augment/all'
 import { MembershipMetadata } from '@joystream/metadata-protobuf'
-import { Event } from 'query-node/dist/src/modules/event/event.model'
-import { MemberVerificationStatusUpdatedEvent } from 'query-node/dist/src/modules/member-verification-status-updated-event/member-verification-status-updated-event.model'
 import { createEvent } from './common'
-import { InvitesTransferredEvent } from 'query-node/dist/src/modules/invites-transferred-event/invites-transferred-event.model'
-import { StakingAccountConfirmedEvent } from 'query-node/dist/src/modules/staking-account-confirmed-event/staking-account-confirmed-event.model'
-import { StakingAccountRemovedEvent } from 'query-node/dist/src/modules/staking-account-removed-event/staking-account-removed-event.model'
-import { InitialInvitationCountUpdatedEvent } from 'query-node/dist/src/modules/initial-invitation-count-updated-event/initial-invitation-count-updated-event.model'
-import { MembershipPriceUpdatedEvent } from 'query-node/dist/src/modules/membership-price-updated-event/membership-price-updated-event.model'
-import { ReferralCutUpdatedEvent } from 'query-node/dist/src/modules/referral-cut-updated-event/referral-cut-updated-event.model'
-import { InitialInvitationBalanceUpdatedEvent } from 'query-node/dist/src/modules/initial-invitation-balance-updated-event/initial-invitation-balance-updated-event.model'
-import { StakingAccountAddedEvent } from 'query-node/dist/src/modules/staking-account-added-event/staking-account-added-event.model'
-import { LeaderInvitationQuotaUpdatedEvent } from 'query-node/dist/src/modules/leader-invitation-quota-updated-event/leader-invitation-quota-updated-event.model'
+import {
+  Membership,
+  EventType,
+  MembershipEntryMethod,
+  MembershipSystemSnapshot,
+  MemberMetadata,
+  MembershipBoughtEvent,
+  MemberProfileUpdatedEvent,
+  MemberAccountsUpdatedEvent,
+  MemberInvitedEvent,
+  Event,
+  MemberVerificationStatusUpdatedEvent,
+  InvitesTransferredEvent,
+  StakingAccountConfirmedEvent,
+  StakingAccountRemovedEvent,
+  InitialInvitationCountUpdatedEvent,
+  MembershipPriceUpdatedEvent,
+  ReferralCutUpdatedEvent,
+  InitialInvitationBalanceUpdatedEvent,
+  StakingAccountAddedEvent,
+  LeaderInvitationQuotaUpdatedEvent,
+} from 'query-node/dist/model'
 
 async function getMemberById(db: DatabaseManager, id: MemberId): Promise<Membership> {
   const member = await db.get(Membership, { where: { id: id.toString() }, relations: ['metadata'] })

+ 2 - 2
query-node/mappings/package.json

@@ -10,8 +10,8 @@
     "clean": "rm -rf lib"
   },
   "dependencies": {
-    "@dzlzv/hydra-common": "2.0.1-beta.17",
-    "@dzlzv/hydra-db-utils": "2.0.1-beta.17",
+    "@dzlzv/hydra-common": "2.1.0-beta.7",
+    "@dzlzv/hydra-db-utils": "2.1.0-beta.7",
     "@joystream/types": "^0.15.0",
     "warthog": "https://github.com/metmirr/warthog/releases/download/v2.23.0/warthog-v2.23.0.tgz"
   },

+ 278 - 0
query-node/mappings/workingGroups.ts

@@ -0,0 +1,278 @@
+/*
+eslint-disable @typescript-eslint/naming-convention
+*/
+import { SubstrateEvent } from '@dzlzv/hydra-common'
+import { DatabaseManager } from '@dzlzv/hydra-db-utils'
+import { StorageWorkingGroup as WorkingGroups } from './generated/types'
+import { ApplicationMetadata, OpeningMetadata } from '@joystream/metadata-protobuf'
+import { Bytes } from '@polkadot/types'
+import { createEvent, deserializeMetadata } from './common'
+import BN from 'bn.js'
+import {
+  WorkingGroupOpening,
+  OpeningAddedEvent,
+  WorkingGroup,
+  WorkingGroupOpeningMetadata,
+  ApplicationFormQuestion,
+  ApplicationFormQuestionType,
+  OpeningStatusOpen,
+  WorkingGroupOpeningType,
+  EventType,
+  Event,
+  WorkingGroupApplication,
+  ApplicationFormQuestionAnswer,
+  AppliedOnOpeningEvent,
+  Membership,
+  ApplicationStatusPending,
+} from 'query-node/dist/model'
+
+// Shortcuts
+type InputTypeMap = OpeningMetadata.ApplicationFormQuestion.InputTypeMap
+const InputType = OpeningMetadata.ApplicationFormQuestion.InputType
+
+// Reusable functions
+async function getWorkingGroup(db: DatabaseManager, event_: SubstrateEvent): Promise<WorkingGroup> {
+  const [groupName] = event_.name.split('.')
+  const group = await db.get(WorkingGroup, { where: { name: groupName } })
+  if (!group) {
+    throw new Error(`Working group ${groupName} not found!`)
+  }
+
+  return group
+}
+
+async function getApplicationFormQuestions(db: DatabaseManager, openingId: string): Promise<ApplicationFormQuestion[]> {
+  const openingWithQuestions = await db.get(WorkingGroupOpening, {
+    where: { id: openingId },
+    relations: ['metadata', 'metadata.applicationFormQuestions'],
+  })
+  if (!openingWithQuestions) {
+    throw new Error(`Opening not found by id: ${openingId}`)
+  }
+  if (!openingWithQuestions.metadata.applicationFormQuestions) {
+    throw new Error(`Application form questions not found for opening: ${openingId}`)
+  }
+  return openingWithQuestions.metadata.applicationFormQuestions
+}
+
+function parseQuestionInputType(type: InputTypeMap[keyof InputTypeMap]) {
+  if (type === InputType.TEXTAREA) {
+    return ApplicationFormQuestionType.TEXTAREA
+  }
+
+  return ApplicationFormQuestionType.TEXT
+}
+
+async function createOpeningMeta(db: DatabaseManager, metadataBytes: Bytes): Promise<WorkingGroupOpeningMetadata> {
+  const metadata = await deserializeMetadata(OpeningMetadata, metadataBytes)
+  if (!metadata) {
+    // TODO: Use some defaults?
+  }
+  const {
+    applicationFormQuestionsList,
+    applicationDetails,
+    description,
+    expectedEndingTimestamp,
+    hiringLimit,
+    shortDescription,
+  } = metadata!.toObject()
+
+  const openingMetadata = new WorkingGroupOpeningMetadata({
+    applicationDetails,
+    description,
+    shortDescription,
+    hiringLimit,
+    expectedEnding: new Date(expectedEndingTimestamp!),
+    applicationFormQuestions: [],
+  })
+
+  await db.save<WorkingGroupOpeningMetadata>(openingMetadata)
+
+  await Promise.all(
+    applicationFormQuestionsList.map(async ({ question, type }, index) => {
+      const applicationFormQuestion = new ApplicationFormQuestion({
+        question,
+        type: parseQuestionInputType(type!),
+        index,
+        openingMetadata,
+      })
+      await db.save<ApplicationFormQuestion>(applicationFormQuestion)
+      return applicationFormQuestion
+    })
+  )
+
+  return openingMetadata
+}
+
+async function createApplicationQuestionAnswers(
+  db: DatabaseManager,
+  application: WorkingGroupApplication,
+  metadataBytes: Bytes
+) {
+  const metadata = deserializeMetadata(ApplicationMetadata, metadataBytes)
+  if (!metadata) {
+    // TODO: Handle invalid state?
+  }
+  const questions = await getApplicationFormQuestions(db, application.opening.id)
+  const { answersList } = metadata!.toObject()
+  await Promise.all(
+    answersList.slice(0, questions.length).map(async (answer, index) => {
+      const applicationFormQuestionAnswer = new ApplicationFormQuestionAnswer({
+        application,
+        question: questions[index],
+        answer,
+      })
+
+      await db.save<ApplicationFormQuestionAnswer>(applicationFormQuestionAnswer)
+      return applicationFormQuestionAnswer
+    })
+  )
+}
+
+// Mapping functions
+export async function workingGroups_OpeningAdded(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
+  const {
+    balance: rewardPerBlock,
+    bytes: metadataBytes,
+    openingId,
+    openingType,
+    stakePolicy,
+  } = new WorkingGroups.OpeningAddedEvent(event_).data
+  const group = await getWorkingGroup(db, event_)
+  const metadata = await createOpeningMeta(db, metadataBytes)
+
+  const opening = new WorkingGroupOpening({
+    createdAt: new Date(event_.blockTimestamp.toNumber()),
+    updatedAt: new Date(event_.blockTimestamp.toNumber()),
+    createdAtBlock: event_.blockNumber,
+    id: openingId.toString(),
+    applications: [],
+    group,
+    metadata,
+    rewardPerBlock: rewardPerBlock.unwrapOr(new BN(0)),
+    stakeAmount: stakePolicy.stake_amount,
+    unstakingPeriod: stakePolicy.leaving_unstaking_period.toNumber(),
+    status: new OpeningStatusOpen(),
+    type: openingType.isLeader ? WorkingGroupOpeningType.LEADER : WorkingGroupOpeningType.REGULAR,
+  })
+
+  await db.save<WorkingGroupOpening>(opening)
+
+  const event = await createEvent(event_, EventType.OpeningAdded)
+  const openingAddedEvent = new OpeningAddedEvent({
+    event,
+    group,
+    opening,
+  })
+
+  await db.save<Event>(event)
+  await db.save<OpeningAddedEvent>(openingAddedEvent)
+}
+
+export async function workingGroups_AppliedOnOpening(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  event_.blockTimestamp = new BN(event_.blockTimestamp) // FIXME: Temporary fix for wrong blockTimestamp type
+  const {
+    applicationId,
+    applyOnOpeningParameters: {
+      opening_id: openingId,
+      description: metadataBytes,
+      member_id: memberId,
+      reward_account_id: rewardAccount,
+      role_account_id: roleAccout,
+      stake_parameters: { staking_account_id: stakingAccount },
+    },
+  } = new WorkingGroups.AppliedOnOpeningEvent(event_).data
+  const group = await getWorkingGroup(db, event_)
+
+  const application = new WorkingGroupApplication({
+    createdAt: new Date(event_.blockTimestamp.toNumber()),
+    updatedAt: new Date(event_.blockTimestamp.toNumber()),
+    createdAtBlock: event_.blockNumber,
+    id: applicationId.toString(),
+    opening: new WorkingGroupOpening({ id: openingId.toString() }),
+    applicant: new Membership({ id: memberId.toString() }),
+    rewardAccount: rewardAccount.toString(),
+    roleAccount: roleAccout.toString(),
+    stakingAccount: stakingAccount.toString(),
+    status: new ApplicationStatusPending(),
+    answers: [],
+  })
+
+  await db.save<WorkingGroupApplication>(application)
+  await createApplicationQuestionAnswers(db, application, metadataBytes)
+
+  const event = await createEvent(event_, EventType.AppliedOnOpening)
+  const appliedOnOpeningEvent = new AppliedOnOpeningEvent({
+    createdAt: new Date(event_.blockTimestamp.toNumber()),
+    updatedAt: new Date(event_.blockTimestamp.toNumber()),
+    event,
+    group,
+    opening: new WorkingGroupOpening({ id: openingId.toString() }),
+    application,
+  })
+
+  await db.save<Event>(event)
+  await db.save<AppliedOnOpeningEvent>(appliedOnOpeningEvent)
+}
+
+export async function workingGroups_OpeningFilled(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_LeaderSet(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_WorkerRoleAccountUpdated(
+  db: DatabaseManager,
+  event_: SubstrateEvent
+): Promise<void> {
+  // TBD
+}
+export async function workingGroups_LeaderUnset(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_WorkerExited(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_TerminatedWorker(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_TerminatedLeader(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_StakeSlashed(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_StakeDecreased(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_StakeIncreased(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_ApplicationWithdrawn(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_OpeningCanceled(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_BudgetSet(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_WorkerRewardAccountUpdated(
+  db: DatabaseManager,
+  event_: SubstrateEvent
+): Promise<void> {
+  // TBD
+}
+export async function workingGroups_WorkerRewardAmountUpdated(
+  db: DatabaseManager,
+  event_: SubstrateEvent
+): Promise<void> {
+  // TBD
+}
+export async function workingGroups_StatusTextChanged(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}
+export async function workingGroups_BudgetSpending(db: DatabaseManager, event_: SubstrateEvent): Promise<void> {
+  // TBD
+}

+ 1 - 1
query-node/package.json

@@ -42,7 +42,7 @@
     "tslib": "^2.0.0",
     "@types/bn.js": "^4.11.6",
     "bn.js": "^5.1.2",
-    "@dzlzv/hydra-processor": "2.0.1-beta.17",
+    "@dzlzv/hydra-processor": "2.1.0-beta.7",
     "envsub": "4.0.7"
   },
   "volta": {

+ 6 - 1
query-node/run-tests.sh

@@ -25,4 +25,9 @@ docker-compose down -v
 
 ./start.sh
 
-time yarn workspace integration-tests run-test-scenario olympia
+# pass the scenario name without .ts extension
+SCENARIO=$1
+# fallback if scenario if not specified
+SCENARIO=${SCENARIO:=full}
+
+time yarn workspace integration-tests run-test-scenario ${SCENARIO}

+ 596 - 0
query-node/schema.graphql

@@ -187,6 +187,7 @@ type MembershipSystemSnapshot @entity {
 # Membership-related events
 
 enum EventType {
+  # Memberships
   MembershipBought,
   MemberInvited,
   MemberProfileUpdated,
@@ -201,6 +202,26 @@ enum EventType {
   StakingAccountAddedEvent,
   StakingAccountConfirmed,
   StakingAccountRemoved,
+  # Working Groups
+  OpeningAdded,
+  AppliedOnOpening,
+  OpeningFilled,
+  LeaderSet,
+  WorkerRoleAccountUpdated,
+  LeaderUnset,
+  WorkerExited,
+  TerminatedWorker,
+  TerminatedLeader,
+  StakeSlashed,
+  StakeDecreased,
+  StakeIncreased,
+  ApplicationWithdrawn,
+  OpeningCanceled,
+  BudgetSet,
+  WorkerRewardAccountUpdated,
+  WorkerRewardAmountUpdated,
+  StatusTextChanged,
+  BudgetSpending,
 }
 
 type Event @entity {
@@ -220,6 +241,18 @@ type Event @entity {
   type: EventType!
 }
 
+# FIXME: Warthog bug
+
+# interface WorkingGroupsEvent @entity {
+#   "Generic event data"
+#   event: Event!
+# }
+
+# interface MembershipEvent @entity {
+#   "Generic event data"
+#   event: Event!
+# }
+
 
 type MembershipBoughtEvent @entity {
   "Generic event data"
@@ -395,3 +428,566 @@ type StakingAccountRemovedEvent @entity {
   "New staking account in SS58 encoding."
   account: String!
 }
+
+
+
+
+
+
+type WorkerStatusActive @variant {
+  # No additional information needed
+  _phantom: Int
+}
+
+type WorkerStatusLeft @variant {
+  # TODO: Variant relationships
+  # TODO: This is not yet emitted by runtime
+  workerLeftEventId: ID!
+
+  # Set when the unstaking period is finished
+  workerExitedEventId: ID
+}
+
+type WorkerStatusTerminated @variant {
+  # TODO: Variant relationship
+  terminatedWorkerEventId: ID!
+
+  # Set when the unstaking period is finished
+  workerExitedEventId: ID
+}
+
+
+union WorkerStatus = WorkerStatusActive | WorkerStatusLeft | WorkerStatusTerminated
+
+# Working Groups
+type Worker @entity {
+  "The group that the worker belongs to"
+  group: WorkingGroup!
+
+  "Worker membership"
+  membership: Membership!
+
+  "Worker's role account"
+  roleAccount: String!
+
+  "Worker's reward account"
+  rewardAccount: String!
+
+  "Worker's staking account"
+  stakeAccount: String!
+
+  "Current worker status"
+  status: WorkerStatus!
+
+  "Whether the worker is also the working group lead"
+  isLead: Boolean!
+
+  "Current role stake (in JOY)"
+  stake: BigInt!
+
+  "All related reward payouts"
+  payouts: [WorkerPayoutEvent!] @derivedFrom(field: "worker")
+
+  # TODO: should we use createdAt for consistency / doesn't Hydra actually require us to override this field?
+  "Blocknumber of the block the worker was hired at"
+  hiredAtBlock: Int!
+
+  "Time the worker was hired at"
+  hiredAtTime: DateTime!
+
+  "Related worker entry application"
+  application: WorkingGroupApplication!
+
+  "Worker's storage data"
+  storage: String
+}
+
+type WorkingGroupMetadata @entity {
+  "Status name"
+  name: String!
+
+  "Status message"
+  message: String
+
+  "Working group about text"
+  about: String
+
+  "Working group description text"
+  description: String
+
+  "Blocknumber of the block at which status was set"
+  setAtBlock: Int!
+
+  "The time at which status was set"
+  setAtTime: DateTime!
+}
+
+type WorkingGroup @entity {
+  "Working group runtime id"
+  id: ID!
+
+  "Working group name"
+  name: String!
+
+  "Working group current metadata"
+  status: WorkingGroupMetadata
+
+  "Current working group leader"
+  leader: Worker
+
+  "Workers that currently belong to the group or belonged to the group in the past"
+  workers: [Worker!] @derivedFrom(field: "group")
+
+  "All openings related to this group"
+  openings: [WorkingGroupOpening!] @derivedFrom(field: "group")
+
+  "Current working group budget (JOY)"
+  budget: BigInt!
+
+  # ...
+}
+
+type OpeningStatusCancelled @variant {
+  # TODO: Variant relationships
+  openingCancelledEventId: ID!
+}
+
+type OpeningStatusOpen @variant {
+  # No additional information needed
+  _phantom: Int
+}
+
+type OpeningStatusFilled @variant {
+  # TODO: Variant relationships
+  openingFilledEventId: ID!
+}
+
+union WorkingGroupOpeningStatus = OpeningStatusOpen | OpeningStatusFilled | OpeningStatusCancelled
+
+enum WorkingGroupOpeningType {
+  REGULAR
+  LEADER
+}
+
+type WorkingGroupOpeningMetadata @entity {
+  "Opening short description"
+  shortDescription: String!
+
+  "Opening description (md-formatted)"
+  description: String!
+
+  "Expected max. number of applicants that will be hired"
+  hiringLimit: Int!
+
+  "Expected time when the opening will close"
+  expectedEnding: DateTime!
+
+  "Md-formatted text explaining the application process"
+  applicationDetails: String!
+
+  "List of questions that should be answered during application"
+  applicationFormQuestions: [ApplicationFormQuestion!]! @derivedFrom(field: "openingMetadata")
+}
+
+type WorkingGroupOpening @entity {
+  "Opening runtime id"
+  id: ID!
+
+  "Related working group"
+  group: WorkingGroup!
+
+  "List of opening applications"
+  applications: [WorkingGroupApplication!] @derivedFrom(field: "opening")
+
+  "Type of the opening (Leader/Regular)"
+  type: WorkingGroupOpeningType!
+
+  "Current opening status"
+  status: WorkingGroupOpeningStatus!
+
+  "Opening metadata"
+  metadata: WorkingGroupOpeningMetadata!
+
+  "Role stake amount"
+  stakeAmount: BigInt!
+
+  "Role stake unstaking period in blocks"
+  unstakingPeriod: Int!
+
+  "Initial workers' reward per block"
+  rewardPerBlock: BigInt!
+
+  "Blocknumber of opening creation block"
+  createdAtBlock: Int!
+
+  "Time of opening creation"
+  createdAt: DateTime!
+}
+
+type ApplicationStatusPending @variant {
+  # No additional information needed
+  _phantom: Int
+}
+
+type ApplicationStatusAccepted @variant {
+  # TODO: Variant relationships
+  openingFilledEventId: ID!
+}
+
+type ApplicationStatusRejected @variant {
+  # TODO: Variant relationships
+  openingFilledEventId: ID!
+}
+
+type ApplicationStatusWithdrawn @variant {
+  # TODO: Variant relationships
+  applicationWithdrawnEventId: ID!
+}
+
+
+union WorkingGroupApplicationStatus = ApplicationStatusPending | ApplicationStatusAccepted | ApplicationStatusRejected | ApplicationStatusWithdrawn
+
+type WorkingGroupApplication @entity {
+  "Application runtime id"
+  id: ID!
+
+  "Related working group opening"
+  opening: WorkingGroupOpening!
+
+  "Applicant's membership"
+  applicant: Membership!
+
+  "Applicant's initial role account"
+  roleAccount: String!
+
+  "Applicant's initial reward account"
+  rewardAccount: String!
+
+  "Applicant's initial staking account"
+  stakingAccount: String!
+
+  "Answers to application form questions"
+  answers: [ApplicationFormQuestionAnswer!] @derivedFrom(field: "application")
+
+  "Current application status"
+  status: WorkingGroupApplicationStatus!
+
+  "Blocknumber of application creation block"
+  createdAtBlock: Int!
+
+  "Time of application creation"
+  createdAt: DateTime!
+}
+
+type ApplicationFormQuestionAnswer @entity {
+  "Related application"
+  application: WorkingGroupApplication!
+
+  "The question beeing answered"
+  question: ApplicationFormQuestion!
+
+  "Applicant's answer"
+  answer: String!
+}
+
+enum ApplicationFormQuestionType {
+  TEXT
+  TEXTAREA
+}
+
+type ApplicationFormQuestion @entity {
+  "Related opening metadata"
+  openingMetadata: WorkingGroupOpeningMetadata!
+
+  "The question itself"
+  question: String!
+
+  "Type of the question (UI answer input type)"
+  type: ApplicationFormQuestionType!
+
+  "Index of the question"
+  index: Int!
+}
+
+type OpeningAddedEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related opening"
+  opening: WorkingGroupOpening!
+
+  # Other opening data like: metadata, type, staking policy, reward etc. is immutable, so can be read directly from Opening entity
+  # TODO: Can it stay like this or are we expecting to introduce updateable openings in the future?
+}
+
+type AppliedOnOpeningEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related opening"
+  opening: WorkingGroupOpening!
+
+  "The application that was created"
+  application: WorkingGroupApplication!
+
+  # Same as with opening - application parameters are immutable and can be fetched from Application entity
+  # TODO: Can it stay like this or are we expecting to introduce updateable applications in the future?
+}
+
+type OpeningFilledEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related opening"
+  opening: WorkingGroupOpening!
+
+  # TODO: Can we just get accepted applications via event.opening.applications(where: { status: 'Accepted' })?
+  # Otherwise we would need a field like "acceptedIn" filed in application or a separate table to make OneToMany
+}
+
+type LeaderSetEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related Lead worker"
+  worker: Worker!
+}
+
+type WorkerRoleAccountUpdatedEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related worker"
+  worker: Worker!
+
+  "New role account"
+  newRoleAccount: String!
+}
+
+type LeaderUnsetEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+}
+
+type WorkerExitedEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related worker"
+  worker: Worker!
+}
+
+type TerminatedWorkerEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related worker"
+  worker: Worker!
+
+  "Slash amount (if any)"
+  penalty: BigInt
+
+  "Optional rationale"
+  rationale: String
+}
+
+type TerminatedLeaderEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related worker"
+  worker: Worker!
+
+  "Slash amount (if any)"
+  penalty: BigInt
+
+  "Optional rationale"
+  rationale: String
+}
+
+type StakeSlashedEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related worker"
+  worker: Worker!
+
+  "Balance that was requested to be slashed"
+  requestedAmount: BigInt!
+
+  "Balance that was actually slashed"
+  slashedAmount: BigInt!
+
+  "Optional rationale"
+  rationale: String
+}
+
+type StakeDecreasedEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related worker"
+  worker: Worker!
+
+  "The amount of JOY the stake was decreased by"
+  amount: BigInt!
+}
+
+type StakeIncreasedEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related worker"
+  worker: Worker!
+
+  "The amount of JOY the stake was increased by"
+  amount: BigInt!
+}
+
+type ApplicationWithdrawnEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related application"
+  application: WorkingGroupApplication!
+}
+
+type OpeningCanceledEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related opening"
+  opening: WorkingGroupOpening!
+}
+
+type BudgetSetEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "New working group budget"
+  newBudget: BigInt!
+}
+
+type WorkerRewardAccountUpdatedEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related worker"
+  worker: Worker!
+
+  "New reward account"
+  newRewardAccount: String!
+}
+
+type WorkerRewardAmountUpdatedEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Related worker"
+  worker: Worker!
+
+  "New worker reward per block"
+  newRewardPerBlock: BigInt!
+}
+
+# TODO: Should we rename the event/extrinsic in the runtime?
+type StatusTextChangedEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "New working group metadata"
+  metadata: WorkingGroupMetadata!
+}
+
+type BudgetSpendingEvent @entity {
+  "Generic event data"
+  event: Event!
+
+  "Related group"
+  group: WorkingGroup!
+
+  "Reciever account address"
+  reciever: String!
+
+  "Amount beeing spent"
+  amount: BigInt!
+
+  "Optional rationale"
+  rationale: String
+}
+
+enum PayoutType {
+  "Standard reward payout"
+  STANDARD_REWARD
+  "Return of the previously missed reward"
+  RETURN_MISSED
+}
+
+# TODO: This will be either based on the actual runtime event or be just a custom query-node event generated of preBlock/postBlock
+type WorkerPayoutEvent @entity {
+  "Type of the worker payout"
+  type: PayoutType
+
+  "Related worker"
+  worker: Worker!
+
+  "Amount recieved"
+  recieved: BigInt!
+
+  "Amount missed (due to, for example, empty working group budget)"
+  missed: BigInt!
+}

+ 1 - 1
tests/integration-tests/run-test-scenario.sh

@@ -7,7 +7,7 @@ cd $SCRIPT_PATH
 # pass the scenario name without .ts extension
 SCENARIO=$1
 # fallback if scenario if not specified
-SCENARIO=${SCENARIO:=olympia}
+SCENARIO=${SCENARIO:=full}
 
 # Execute the tests
 time DEBUG=* yarn workspace integration-tests node-ts-strict src/scenarios/${SCENARIO}.ts

+ 54 - 2
tests/integration-tests/src/Api.ts

@@ -14,7 +14,17 @@ import { types } from '@joystream/types'
 import { v4 as uuid } from 'uuid'
 import Debugger from 'debug'
 import { DispatchError } from '@polkadot/types/interfaces/system'
-import { EventDetails, MemberInvitedEventDetails, MembershipBoughtEventDetails, MembershipEventName } from './types'
+import {
+  EventDetails,
+  MemberInvitedEventDetails,
+  MembershipBoughtEventDetails,
+  MembershipEventName,
+  OpeningAddedEventDetails,
+  WorkingGroupsEventName,
+  WorkingGroupModuleName,
+  AppliedOnOpeningEventDetails,
+} from './types'
+import { ApplicationId, Opening, OpeningId } from '@joystream/types/working-group'
 
 export enum WorkingGroups {
   StorageWorkingGroup = 'storageWorkingGroup',
@@ -257,7 +267,19 @@ export class Api {
   ): Promise<EventDetails> {
     const details = await this.retrieveEventDetails(result, 'members', eventName)
     if (!details) {
-      throw new Error(`${eventName} details not found in result: ${JSON.stringify(result.toHuman())}`)
+      throw new Error(`${eventName} event details not found in result: ${JSON.stringify(result.toHuman())}`)
+    }
+    return details
+  }
+
+  public async retrieveWorkingGroupsEventDetails(
+    result: ISubmittableResult,
+    moduleName: WorkingGroupModuleName,
+    eventName: WorkingGroupsEventName
+  ): Promise<EventDetails> {
+    const details = await this.retrieveEventDetails(result, moduleName, eventName)
+    if (!details) {
+      throw new Error(`${eventName} event details not found in result: ${JSON.stringify(result.toHuman())}`)
     }
     return details
   }
@@ -278,6 +300,28 @@ export class Api {
     }
   }
 
+  public async retrieveOpeningAddedEventDetails(
+    result: ISubmittableResult,
+    moduleName: WorkingGroupModuleName
+  ): Promise<OpeningAddedEventDetails> {
+    const details = await this.retrieveWorkingGroupsEventDetails(result, moduleName, 'OpeningAdded')
+    return {
+      ...details,
+      openingId: details.event.data[0] as OpeningId,
+    }
+  }
+
+  public async retrieveAppliedOnOpeningEventDetails(
+    result: ISubmittableResult,
+    moduleName: WorkingGroupModuleName
+  ): Promise<AppliedOnOpeningEventDetails> {
+    const details = await this.retrieveWorkingGroupsEventDetails(result, moduleName, 'AppliedOnOpening')
+    return {
+      ...details,
+      applicationId: details.event.data[1] as ApplicationId,
+    }
+  }
+
   public getErrorNameFromExtrinsicFailedRecord(result: ISubmittableResult): string | undefined {
     const failed = result.findRecord('system', 'ExtrinsicFailed')
     if (!failed) {
@@ -297,4 +341,12 @@ export class Api {
       }
     }
   }
+
+  public async getOpening(group: WorkingGroupModuleName, id: OpeningId): Promise<Opening> {
+    const opening = await this.api.query[group].openingById(id)
+    if (opening.isEmpty) {
+      throw new Error(`Opening by id ${id} not found!`)
+    }
+    return opening
+  }
 }

+ 156 - 0
tests/integration-tests/src/QueryNodeApi.ts

@@ -1,14 +1,17 @@
 import { gql, ApolloClient, ApolloQueryResult, NormalizedCacheObject } from '@apollo/client'
 import { MemberId } from '@joystream/types/common'
 import {
+  AppliedOnOpeningEvent,
   InitialInvitationBalanceUpdatedEvent,
   InitialInvitationCountUpdatedEvent,
   MembershipPriceUpdatedEvent,
   MembershipSystemSnapshot,
+  OpeningAddedEvent,
   Query,
   ReferralCutUpdatedEvent,
 } from './QueryNodeApiSchema.generated'
 import Debugger from 'debug'
+import { ApplicationId, OpeningId } from '@joystream/types/working-group'
 
 export class QueryNodeApi {
   private readonly queryNodeProvider: ApolloClient<NormalizedCacheObject>
@@ -491,4 +494,157 @@ export class QueryNodeApi {
       })
     ).data.initialInvitationCountUpdatedEvents[0]
   }
+
+  public async getOpeningById(
+    id: OpeningId
+  ): Promise<ApolloQueryResult<Pick<Query, 'workingGroupOpeningByUniqueInput'>>> {
+    const OPENING_BY_ID = gql`
+      query($openingId: ID!) {
+        workingGroupOpeningByUniqueInput(where: { id: $openingId }) {
+          id
+          group {
+            name
+          }
+          applications {
+            id
+          }
+          type
+          status {
+            __typename
+          }
+          metadata {
+            shortDescription
+            description
+            hiringLimit
+            expectedEnding
+            applicationDetails
+            applicationFormQuestions {
+              question
+              type
+              index
+            }
+          }
+          stakeAmount
+          unstakingPeriod
+          rewardPerBlock
+          createdAtBlock
+          createdAt
+        }
+      }
+    `
+
+    this.queryDebug(`Executing getOpeningById(${id.toString()})`)
+
+    return this.queryNodeProvider.query<Pick<Query, 'workingGroupOpeningByUniqueInput'>>({
+      query: OPENING_BY_ID,
+      variables: { openingId: id.toString() },
+    })
+  }
+
+  public async getApplicationById(
+    id: ApplicationId
+  ): Promise<ApolloQueryResult<Pick<Query, 'workingGroupApplicationByUniqueInput'>>> {
+    const APPLICATION_BY_ID = gql`
+      query($applicationId: ID!) {
+        workingGroupApplicationByUniqueInput(where: { id: $applicationId }) {
+          id
+          createdAtBlock
+          createdAt
+          opening {
+            id
+          }
+          applicant {
+            id
+          }
+          roleAccount
+          rewardAccount
+          stakingAccount
+          status {
+            __typename
+          }
+          answers {
+            question {
+              question
+            }
+            answer
+          }
+        }
+      }
+    `
+
+    this.queryDebug(`Executing getApplicationById(${id.toString()})`)
+
+    return this.queryNodeProvider.query<Pick<Query, 'workingGroupApplicationByUniqueInput'>>({
+      query: APPLICATION_BY_ID,
+      variables: { applicationId: id.toString() },
+    })
+  }
+
+  public async getAppliedOnOpeningEvent(
+    blockNumber: number,
+    indexInBlock: number
+  ): Promise<AppliedOnOpeningEvent | undefined> {
+    const APPLIED_ON_OPENING_BY_ID = gql`
+      query($eventId: ID!) {
+        appliedOnOpeningEvents(where: { eventId_eq: $eventId }) {
+          event {
+            inBlock
+            inExtrinsic
+            indexInBlock
+            type
+          }
+          group {
+            name
+          }
+          opening {
+            id
+          }
+          application {
+            id
+          }
+        }
+      }
+    `
+
+    const eventId = `${blockNumber}-${indexInBlock}`
+    this.queryDebug(`Executing getAppliedOnOpeningEvent(${eventId})`)
+
+    return (
+      await this.queryNodeProvider.query<Pick<Query, 'appliedOnOpeningEvents'>>({
+        query: APPLIED_ON_OPENING_BY_ID,
+        variables: { eventId },
+      })
+    ).data.appliedOnOpeningEvents[0]
+  }
+
+  public async getOpeningAddedEvent(blockNumber: number, indexInBlock: number): Promise<OpeningAddedEvent | undefined> {
+    const OPENING_ADDED_BY_ID = gql`
+      query($eventId: ID!) {
+        openingAddedEvents(where: { eventId_eq: $eventId }) {
+          event {
+            inBlock
+            inExtrinsic
+            indexInBlock
+            type
+          }
+          group {
+            name
+          }
+          opening {
+            id
+          }
+        }
+      }
+    `
+
+    const eventId = `${blockNumber}-${indexInBlock}`
+    this.queryDebug(`Executing getOpeningAddedEvent(${eventId})`)
+
+    return (
+      await this.queryNodeProvider.query<Pick<Query, 'openingAddedEvents'>>({
+        query: OPENING_ADDED_BY_ID,
+        variables: { eventId },
+      })
+    ).data.openingAddedEvents[0]
+  }
 }

Різницю між файлами не показано, бо вона завелика
+ 226 - 548
tests/integration-tests/src/QueryNodeApiSchema.generated.ts


+ 8 - 9
tests/integration-tests/src/fixtures/membershipModule.ts

@@ -18,25 +18,24 @@ import {
   StakingAccountAddedEvent,
   StakingAccountConfirmedEvent,
   StakingAccountRemovedEvent,
-  Event,
   MembershipSystemSnapshot,
 } from '../QueryNodeApiSchema.generated'
 import { blake2AsHex } from '@polkadot/util-crypto'
 import { SubmittableExtrinsic } from '@polkadot/api/types'
 import { CreateInterface, createType } from '@joystream/types'
 import { MembershipMetadata } from '@joystream/metadata-protobuf'
-import { EventDetails, MemberInvitedEventDetails, MembershipBoughtEventDetails, MembershipEventName } from '../types'
+import {
+  MemberContext,
+  AnyQueryNodeEvent,
+  EventDetails,
+  MemberInvitedEventDetails,
+  MembershipBoughtEventDetails,
+  MembershipEventName,
+} from '../types'
 
 // FIXME: Retrieve from runtime when possible!
 const MINIMUM_STAKING_ACCOUNT_BALANCE = 200
 
-type MemberContext = {
-  account: string
-  memberId: MemberId
-}
-
-type AnyQueryNodeEvent = { event: Event }
-
 // common code for fixtures
 abstract class MembershipFixture extends BaseFixture {
   generateParamsFromAccountId(accountId: string): CreateInterface<BuyMembershipParameters> {

+ 302 - 0
tests/integration-tests/src/fixtures/workingGroupsModule.ts

@@ -0,0 +1,302 @@
+import { Api } from '../Api'
+import BN from 'bn.js'
+import { assert } from 'chai'
+import { BaseFixture } from '../Fixture'
+import Debugger from 'debug'
+import { QueryNodeApi } from '../QueryNodeApi'
+import {
+  ApplicationFormQuestionType,
+  AppliedOnOpeningEvent,
+  EventType,
+  OpeningAddedEvent,
+  WorkingGroupApplication,
+  WorkingGroupOpening,
+  WorkingGroupOpeningType,
+} from '../QueryNodeApiSchema.generated'
+import { ApplicationMetadata, OpeningMetadata } from '@joystream/metadata-protobuf'
+import { WorkingGroupModuleName, MemberContext, AppliedOnOpeningEventDetails, OpeningAddedEventDetails } from '../types'
+import { OpeningId } from '@joystream/types/working-group'
+import { Utils } from '../utils'
+import _ from 'lodash'
+
+// TODO: Fetch from runtime when possible!
+const MIN_APPLICATION_STAKE = new BN(2000)
+const MIN_USTANKING_PERIOD = 43201
+
+export type OpeningParams = {
+  stake: BN
+  unstakingPeriod: number
+  reward: BN
+  metadata: OpeningMetadata.AsObject
+}
+
+const queryNodeQuestionTypeToMetadataQuestionType = (type: ApplicationFormQuestionType) => {
+  if (type === ApplicationFormQuestionType.Text) {
+    return OpeningMetadata.ApplicationFormQuestion.InputType.TEXT
+  }
+
+  return OpeningMetadata.ApplicationFormQuestion.InputType.TEXTAREA
+}
+
+export class SudoCreateLeadOpeningFixture extends BaseFixture {
+  private query: QueryNodeApi
+  private group: WorkingGroupModuleName
+  private debug: Debugger.Debugger
+  private openingParams: OpeningParams
+  private createdOpeningId?: OpeningId
+
+  private defaultOpeningParams: OpeningParams = {
+    stake: MIN_APPLICATION_STAKE,
+    unstakingPeriod: MIN_USTANKING_PERIOD,
+    reward: new BN(10),
+    metadata: {
+      shortDescription: 'Test sudo lead opening',
+      description: '# Test sudo lead opening',
+      expectedEndingTimestamp: Date.now() + 60,
+      hiringLimit: 1,
+      applicationDetails: '- This is automatically created opening, do not apply!',
+      applicationFormQuestionsList: [
+        { question: 'Question 1?', type: OpeningMetadata.ApplicationFormQuestion.InputType.TEXT },
+        { question: 'Question 2?', type: OpeningMetadata.ApplicationFormQuestion.InputType.TEXTAREA },
+      ],
+    },
+  }
+
+  public getDefaultOpeningParams(): OpeningParams {
+    return this.defaultOpeningParams
+  }
+
+  private getMetadata(): OpeningMetadata {
+    const metadataObj = this.openingParams.metadata as Required<OpeningMetadata.AsObject>
+    const metadata = new OpeningMetadata()
+    metadata.setShortDescription(metadataObj.shortDescription)
+    metadata.setDescription(metadataObj.description)
+    metadata.setExpectedEndingTimestamp(metadataObj.expectedEndingTimestamp)
+    metadata.setHiringLimit(metadataObj.hiringLimit)
+    metadata.setApplicationDetails(metadataObj.applicationDetails)
+    metadataObj.applicationFormQuestionsList.forEach(({ question, type }) => {
+      const applicationFormQuestion = new OpeningMetadata.ApplicationFormQuestion()
+      applicationFormQuestion.setQuestion(question!)
+      applicationFormQuestion.setType(type!)
+      metadata.addApplicationFormQuestions(applicationFormQuestion)
+    })
+
+    return metadata
+  }
+
+  public getCreatedOpeningId(): OpeningId {
+    if (!this.createdOpeningId) {
+      throw new Error('Trying to get created opening id before it was created!')
+    }
+    return this.createdOpeningId
+  }
+
+  public constructor(
+    api: Api,
+    query: QueryNodeApi,
+    group: WorkingGroupModuleName,
+    openingParams?: Partial<OpeningParams>
+  ) {
+    super(api)
+    this.query = query
+    this.debug = Debugger('fixture:SudoCreateLeadOpeningFixture')
+    this.group = group
+    this.openingParams = _.merge(this.defaultOpeningParams, openingParams)
+  }
+
+  private assertOpeningMatchQueriedResult(
+    eventDetails: OpeningAddedEventDetails,
+    qOpening?: WorkingGroupOpening | null
+  ) {
+    if (!qOpening) {
+      throw new Error('Query node: Opening not found')
+    }
+    assert.equal(qOpening.id, eventDetails.openingId.toString())
+    assert.equal(qOpening.createdAtBlock, eventDetails.blockNumber)
+    assert.equal(qOpening.group.name, this.group)
+    assert.equal(qOpening.rewardPerBlock, this.openingParams.reward.toString())
+    assert.equal(qOpening.type, WorkingGroupOpeningType.Leader)
+    assert.equal(qOpening.status.__typename, 'OpeningStatusOpen')
+    assert.equal(qOpening.stakeAmount, this.openingParams.stake.toString())
+    assert.equal(qOpening.unstakingPeriod, this.openingParams.unstakingPeriod)
+    // Metadata
+    assert.equal(qOpening.metadata.shortDescription, this.openingParams.metadata.shortDescription)
+    assert.equal(qOpening.metadata.description, this.openingParams.metadata.description)
+    assert.equal(
+      new Date(qOpening.metadata.expectedEnding).getTime(),
+      this.openingParams.metadata.expectedEndingTimestamp
+    )
+    assert.equal(qOpening.metadata.hiringLimit, this.openingParams.metadata.hiringLimit)
+    assert.equal(qOpening.metadata.applicationDetails, this.openingParams.metadata.applicationDetails)
+    assert.deepEqual(
+      qOpening.metadata.applicationFormQuestions
+        .sort((a, b) => a.index - b.index)
+        .map(({ question, type }) => ({
+          question,
+          type: queryNodeQuestionTypeToMetadataQuestionType(type),
+        })),
+      this.openingParams.metadata.applicationFormQuestionsList
+    )
+  }
+
+  private assertQueriedOpeningAddedEventIsValid(
+    eventDetails: OpeningAddedEventDetails,
+    txHash: string,
+    qEvent?: OpeningAddedEvent
+  ) {
+    if (!qEvent) {
+      throw new Error('Query node: OpeningAdded event not found')
+    }
+    assert.equal(qEvent.event.inExtrinsic, txHash)
+    assert.equal(qEvent.event.type, EventType.OpeningAdded)
+    assert.equal(qEvent.group.name, this.group)
+    assert.equal(qEvent.opening.id, eventDetails.openingId.toString())
+  }
+
+  async execute(): Promise<void> {
+    const tx = this.api.tx.sudo.sudo(
+      this.api.tx[this.group].addOpening(
+        Utils.metadataToBytes(this.getMetadata()),
+        'Leader',
+        { stake_amount: this.openingParams.stake, leaving_unstaking_period: this.openingParams.unstakingPeriod },
+        this.openingParams.reward
+      )
+    )
+    const sudoKey = await this.api.query.sudo.key()
+    const result = await this.api.signAndSend(tx, sudoKey)
+    const eventDetails = await this.api.retrieveOpeningAddedEventDetails(result, this.group)
+
+    this.createdOpeningId = eventDetails.openingId
+
+    this.debug(`Lead opening created (id: ${eventDetails.openingId.toString()})`)
+
+    // Query-node part:
+    // Query the opening
+    await this.query.tryQueryWithTimeout(
+      () => this.query.getOpeningById(eventDetails.openingId),
+      (r) => this.assertOpeningMatchQueriedResult(eventDetails, r.data.workingGroupOpeningByUniqueInput)
+    )
+    // Query the event
+    const qOpeningAddedEvent = await this.query.getOpeningAddedEvent(
+      eventDetails.blockNumber,
+      eventDetails.indexInBlock
+    )
+    this.assertQueriedOpeningAddedEventIsValid(eventDetails, tx.hash.toString(), qOpeningAddedEvent)
+  }
+}
+
+export class ApplyOnOpeningHappyCaseFixture extends BaseFixture {
+  private query: QueryNodeApi
+  private group: WorkingGroupModuleName
+  private debug: Debugger.Debugger
+  private applicant: MemberContext
+  private stakingAccount: string
+  private openingId: OpeningId
+  private openingMetadata: OpeningMetadata.AsObject
+
+  public constructor(
+    api: Api,
+    query: QueryNodeApi,
+    group: WorkingGroupModuleName,
+    applicant: MemberContext,
+    stakingAccount: string,
+    openingId: OpeningId,
+    openingMetadata: OpeningMetadata.AsObject
+  ) {
+    super(api)
+    this.query = query
+    this.debug = Debugger('fixture:ApplyOnOpeningHappyCaseFixture')
+    this.group = group
+    this.applicant = applicant
+    this.stakingAccount = stakingAccount
+    this.openingId = openingId
+    this.openingMetadata = openingMetadata
+  }
+
+  private getMetadata(): ApplicationMetadata {
+    const metadata = new ApplicationMetadata()
+    this.openingMetadata.applicationFormQuestionsList.forEach((question, i) => {
+      metadata.addAnswers(`Answer ${i}`)
+    })
+    return metadata
+  }
+
+  private assertApplicationMatchQueriedResult(
+    eventDetails: AppliedOnOpeningEventDetails,
+    qApplication?: WorkingGroupApplication | null
+  ) {
+    if (!qApplication) {
+      throw new Error('Application not found')
+    }
+    assert.equal(qApplication.id, eventDetails.applicationId.toString())
+    assert.equal(qApplication.createdAtBlock, eventDetails.blockNumber)
+    assert.equal(qApplication.opening.id, this.openingId.toString())
+    assert.equal(qApplication.applicant.id, this.applicant.memberId.toString())
+    assert.equal(qApplication.roleAccount, this.applicant.account)
+    assert.equal(qApplication.rewardAccount, this.applicant.account)
+    assert.equal(qApplication.stakingAccount, this.stakingAccount)
+    assert.equal(qApplication.status.__typename, 'ApplicationStatusPending')
+
+    const applicationMetadata = this.getMetadata()
+    assert.deepEqual(
+      qApplication.answers.map(({ question: { question }, answer }) => ({ question, answer })),
+      this.openingMetadata.applicationFormQuestionsList.map(({ question }, index) => ({
+        question,
+        answer: applicationMetadata.getAnswersList()[index],
+      }))
+    )
+  }
+
+  private assertQueriedOpeningAddedEventIsValid(
+    eventDetails: AppliedOnOpeningEventDetails,
+    txHash: string,
+    qEvent?: AppliedOnOpeningEvent
+  ) {
+    if (!qEvent) {
+      throw new Error('Query node: AppliedOnOpening event not found')
+    }
+    assert.equal(qEvent.event.inExtrinsic, txHash)
+    assert.equal(qEvent.event.type, EventType.AppliedOnOpening)
+    assert.equal(qEvent.group.name, this.group)
+    assert.equal(qEvent.opening.id, this.openingId.toString())
+    assert.equal(qEvent.application.id, eventDetails.applicationId.toString())
+  }
+
+  async execute(): Promise<void> {
+    const opening = await this.api.getOpening(this.group, this.openingId)
+    const stake = opening.stake_policy.stake_amount
+    const stakingAccountBalance = await this.api.getBalance(this.stakingAccount)
+    assert.isAbove(stakingAccountBalance.toNumber(), stake.toNumber())
+
+    const tx = this.api.tx[this.group].applyOnOpening({
+      member_id: this.applicant.memberId,
+      description: Utils.metadataToBytes(this.getMetadata()),
+      opening_id: this.openingId,
+      reward_account_id: this.applicant.account,
+      role_account_id: this.applicant.account,
+      stake_parameters: {
+        stake,
+        staking_account_id: this.stakingAccount,
+      },
+    })
+    const txFee = await this.api.estimateTxFee(tx, this.applicant.account)
+    await this.api.treasuryTransferBalance(this.applicant.account, txFee)
+    const result = await this.api.signAndSend(tx, this.applicant.account)
+    const eventDetails = await this.api.retrieveAppliedOnOpeningEventDetails(result, this.group)
+
+    this.debug(`Application submitted (id: ${eventDetails.applicationId.toString()})`)
+
+    // Query-node part:
+    // Query the application
+    await this.query.tryQueryWithTimeout(
+      () => this.query.getApplicationById(eventDetails.applicationId),
+      (r) => this.assertApplicationMatchQueriedResult(eventDetails, r.data.workingGroupApplicationByUniqueInput)
+    )
+    // Query the event
+    const qAppliedOnOpeningEvent = await this.query.getAppliedOnOpeningEvent(
+      eventDetails.blockNumber,
+      eventDetails.indexInBlock
+    )
+    this.assertQueriedOpeningAddedEventIsValid(eventDetails, tx.hash.toString(), qAppliedOnOpeningEvent)
+  }
+}

+ 45 - 0
tests/integration-tests/src/flows/working-groups/leadOpening.ts

@@ -0,0 +1,45 @@
+import { FlowProps } from '../../Flow'
+import { ApplyOnOpeningHappyCaseFixture, SudoCreateLeadOpeningFixture } from '../../fixtures/workingGroupsModule'
+
+import Debugger from 'debug'
+import { FixtureRunner } from '../../Fixture'
+import { AddStakingAccountsHappyCaseFixture, BuyMembershipHappyCaseFixture } from '../../fixtures/membershipModule'
+
+export default async function leadOpening({ api, query, env }: FlowProps): Promise<void> {
+  const debug = Debugger('flow:lead-opening')
+  debug('Started')
+  api.enableDebugTxLogs()
+
+  const sudoLeadOpeningFixture = new SudoCreateLeadOpeningFixture(api, query, 'storageWorkingGroup')
+  await new FixtureRunner(sudoLeadOpeningFixture).run()
+  const openingId = sudoLeadOpeningFixture.getCreatedOpeningId()
+  const openingParams = sudoLeadOpeningFixture.getDefaultOpeningParams()
+
+  const [applicantAcc, stakingAcc] = (await api.createKeyPairs(2)).map((kp) => kp.address)
+  const buyMembershipFixture = new BuyMembershipHappyCaseFixture(api, query, [applicantAcc])
+  await new FixtureRunner(buyMembershipFixture).run()
+  const [applicantMemberId] = buyMembershipFixture.getCreatedMembers()
+
+  const applicantContext = {
+    account: applicantAcc,
+    memberId: applicantMemberId,
+  }
+
+  const addStakingAccFixture = new AddStakingAccountsHappyCaseFixture(api, query, applicantContext, [stakingAcc])
+  await new FixtureRunner(addStakingAccFixture).run()
+
+  await api.treasuryTransferBalance(stakingAcc, openingParams.stake)
+
+  const applyOnOpeningFixture = new ApplyOnOpeningHappyCaseFixture(
+    api,
+    query,
+    'storageWorkingGroup',
+    applicantContext,
+    stakingAcc,
+    openingId,
+    openingParams.metadata
+  )
+  await new FixtureRunner(applyOnOpeningFixture).run()
+
+  debug('Done')
+}

+ 2 - 0
tests/integration-tests/src/scenarios/full.ts

@@ -0,0 +1,2 @@
+import './memberships'
+import './workingGroups'

+ 0 - 0
tests/integration-tests/src/scenarios/olympia.ts → tests/integration-tests/src/scenarios/memberships.ts


+ 6 - 0
tests/integration-tests/src/scenarios/workingGroups.ts

@@ -0,0 +1,6 @@
+import leadOpening from '../flows/working-groups/leadOpening'
+import { scenario } from '../Scenario'
+
+scenario(async ({ job }) => {
+  job('sudo lead opening', leadOpening)
+})

+ 41 - 0
tests/integration-tests/src/types.ts

@@ -1,5 +1,14 @@
 import { MemberId } from '@joystream/types/common'
+import { ApplicationId, OpeningId } from '@joystream/types/working-group'
 import { Event } from '@polkadot/types/interfaces/system'
+import { Event as GenericEventData } from './QueryNodeApiSchema.generated'
+
+export type MemberContext = {
+  account: string
+  memberId: MemberId
+}
+
+export type AnyQueryNodeEvent = { event: GenericEventData }
 
 export interface EventDetails {
   event: Event
@@ -30,3 +39,35 @@ export type MembershipEventName =
   | 'ReferralCutUpdated'
   | 'InitialInvitationBalanceUpdated'
   | 'LeaderInvitationQuotaUpdated'
+
+export interface OpeningAddedEventDetails extends EventDetails {
+  openingId: OpeningId
+}
+
+export interface AppliedOnOpeningEventDetails extends EventDetails {
+  applicationId: ApplicationId
+}
+
+export type WorkingGroupsEventName =
+  | 'OpeningAdded'
+  | 'AppliedOnOpening'
+  | 'OpeningFilled'
+  | 'LeaderSet'
+  | 'WorkerRoleAccountUpdated'
+  | 'LeaderUnset'
+  | 'WorkerExited'
+  | 'TerminatedWorker'
+  | 'TerminatedLeader'
+  | 'StakeSlashed'
+  | 'StakeDecreased'
+  | 'StakeIncreased'
+  | 'ApplicationWithdrawn'
+  | 'OpeningCanceled'
+  | 'BudgetSet'
+  | 'WorkerRewardAccountUpdated'
+  | 'WorkerRewardAmountUpdated'
+  | 'StatusTextChanged'
+  | 'BudgetSpending'
+
+// TODO: Other groups...
+export type WorkingGroupModuleName = 'storageWorkingGroup'

+ 6 - 0
tests/integration-tests/src/utils.ts

@@ -4,6 +4,8 @@ import { blake2AsHex } from '@polkadot/util-crypto'
 import BN from 'bn.js'
 import fs from 'fs'
 import { decodeAddress } from '@polkadot/keyring'
+import { Bytes } from '@polkadot/types'
+import { createType } from '@joystream/types'
 
 export class Utils {
   private static LENGTH_ADDRESS = 32 + 1 // publicKey + prefix
@@ -45,4 +47,8 @@ export class Utils {
   public static camelToSnakeCase(key: string): string {
     return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
   }
+
+  public static metadataToBytes(meta: { serializeBinary(): Uint8Array }): Bytes {
+    return createType('Bytes', '0x' + Buffer.from(meta.serializeBinary()).toString('hex'))
+  }
 }

+ 15 - 15
yarn.lock

@@ -1564,19 +1564,19 @@
     ajv "^6.12.0"
     ajv-keywords "^3.4.1"
 
-"@dzlzv/hydra-common@2.0.1-beta.17", "@dzlzv/hydra-common@^2.0.1-beta.17":
-  version "2.0.1-beta.17"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-common/-/hydra-common-2.0.1-beta.17.tgz#2c99c88ff877add3d3f9ad9a94f5788d755c4c42"
-  integrity sha512-fOxzei0mCRD7KL3MSY5saoJFknnoVysy9qHpfk6yNkyFsykHmekv1mqgicNwuC5HLq/ChxKoQHPeuVV3oltG/w==
+"@dzlzv/hydra-common@2.1.0-beta.7", "@dzlzv/hydra-common@^2.1.0-beta.7":
+  version "2.1.0-beta.7"
+  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-common/-/hydra-common-2.1.0-beta.7.tgz#7b0159b4d6ab396fe94cffbd0e3527c68fab1c94"
+  integrity sha512-UrudbN6eKl13teVqapMs3nliim7/dJ7xba0hXuhdGLWhMwp9O1F+9YRXRAj6UQgfVf1n9bo99MkvxiJ+tKWggg==
   dependencies:
     bn.js "^5.1.3"
 
-"@dzlzv/hydra-db-utils@2.0.1-beta.17", "@dzlzv/hydra-db-utils@^2.0.1-beta.17":
-  version "2.0.1-beta.17"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-db-utils/-/hydra-db-utils-2.0.1-beta.17.tgz#92ddc4c26ef22e1d247ace2f83e5b93d727f09f9"
-  integrity sha512-DPPCD5NfKICorkHsam55VMPsEXQCtuGVe6LasjEWdseDN2d9Px8t8o2+up51nOy8Gqc1dm0xCk+qSTe7GRD8ow==
+"@dzlzv/hydra-db-utils@2.1.0-beta.7", "@dzlzv/hydra-db-utils@^2.1.0-beta.7":
+  version "2.1.0-beta.7"
+  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-db-utils/-/hydra-db-utils-2.1.0-beta.7.tgz#83de3152b9a88158a74c2dc3192e5503bb2b962a"
+  integrity sha512-Qlt/FaHRnyH9h07MBXVQaCGgAa7A+feJglITUwrRJ7bvYHRVo/vyhFpQ4X2E4TazUUPoY4BWQ7IAG4eQXOsogQ==
   dependencies:
-    "@dzlzv/hydra-common" "^2.0.1-beta.17"
+    "@dzlzv/hydra-common" "^2.1.0-beta.7"
     "@types/ioredis" "^4.17.4"
     bn.js "^5.1.3"
     ioredis "^4.17.3"
@@ -1584,13 +1584,13 @@
     shortid "^2.2.16"
     typeorm "^0.2.25"
 
-"@dzlzv/hydra-processor@2.0.1-beta.17":
-  version "2.0.1-beta.17"
-  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-processor/-/hydra-processor-2.0.1-beta.17.tgz#eefe5f54157722b162ffcff629eadbf16c82e1f1"
-  integrity sha512-fKLNt9+Z2py+R91CxPbPJi7kdNCZadnerBCxf4UUCrCr2PIpqDRF/0KsKxEHnW1xGpgTe0LhbA7d4vMbsV0X/w==
+"@dzlzv/hydra-processor@2.1.0-beta.7":
+  version "2.1.0-beta.7"
+  resolved "https://registry.yarnpkg.com/@dzlzv/hydra-processor/-/hydra-processor-2.1.0-beta.7.tgz#950c0ea3a306dc1dad531efafd21d85160f10f41"
+  integrity sha512-y2+x6BzjqXdJqq9MJrj8asCRLzhUNexHpHz4Ox0Qf6gzu4DPYQV8r9C+LwEyaN4aXDit5ZEMqve9aBPNJZnTFw==
   dependencies:
-    "@dzlzv/hydra-common" "^2.0.1-beta.17"
-    "@dzlzv/hydra-db-utils" "^2.0.1-beta.17"
+    "@dzlzv/hydra-common" "^2.1.0-beta.7"
+    "@dzlzv/hydra-db-utils" "^2.1.0-beta.7"
     "@oclif/command" "^1.8.0"
     "@oclif/config" "^1"
     "@oclif/errors" "^1.3.3"

Деякі файли не було показано, через те що забагато файлів було змінено