From ed44adb730838dfa08e5e615cf5a457c81271150 Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Mon, 5 Sep 2022 21:40:10 +0900 Subject: [PATCH 01/15] feat: add basic settings for finschia package --- .pnp.cjs | 224 +++++++++++++++++++ packages/finschia/.eslintignore | 1 + packages/finschia/.eslintrc.js | 94 ++++++++ packages/finschia/.gitignore | 4 + packages/finschia/.nycrc.yml | 1 + packages/finschia/CUSTOM_PROTOBUF_CODECS.md | 233 ++++++++++++++++++++ packages/finschia/README.md | 2 + packages/finschia/jasmine-testrunner.js | 38 ++++ packages/finschia/karma.conf.js | 71 ++++++ packages/finschia/package.json | 91 ++++++++ packages/finschia/tsconfig.eslint.json | 9 + packages/finschia/tsconfig.json | 12 + packages/finschia/typedoc.js | 11 + packages/finschia/webpack.web.config.js | 40 ++++ yarn.lock | 57 +++++ 15 files changed, 888 insertions(+) create mode 120000 packages/finschia/.eslintignore create mode 100644 packages/finschia/.eslintrc.js create mode 100644 packages/finschia/.gitignore create mode 120000 packages/finschia/.nycrc.yml create mode 100644 packages/finschia/CUSTOM_PROTOBUF_CODECS.md create mode 100644 packages/finschia/README.md create mode 100644 packages/finschia/jasmine-testrunner.js create mode 100644 packages/finschia/karma.conf.js create mode 100644 packages/finschia/package.json create mode 100644 packages/finschia/tsconfig.eslint.json create mode 100644 packages/finschia/tsconfig.json create mode 100644 packages/finschia/typedoc.js create mode 100644 packages/finschia/webpack.web.config.js diff --git a/.pnp.cjs b/.pnp.cjs index b941f737e..c40c6769e 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -26,6 +26,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "name": "@lbmjs/cosmwasm-stargate", "reference": "workspace:packages/cosmwasm-stargate" }, + { + "name": "@lbmjs/finschia", + "reference": "workspace:packages/finschia" + }, { "name": "@lbmjs/stargate", "reference": "workspace:packages/stargate" @@ -35,6 +39,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "ignorePatternData": "(^(?:\\.yarn\\/sdks(?:\\/(?!\\.{1,2}(?:\\/|$))(?:(?:(?!(?:^|\\/)\\.{1,2}(?:\\/|$)).)*?)|$))$)", "fallbackExclusionList": [ ["@lbmjs/cosmwasm-stargate", ["workspace:packages/cosmwasm-stargate"]], + ["@lbmjs/finschia", ["workspace:packages/finschia"]], ["@lbmjs/stargate", ["workspace:packages/stargate"]], ["lbmjs-monorepo-root", ["workspace:."]] ], @@ -239,6 +244,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "@lbmjs/cosmwasm-stargate", "workspace:packages/cosmwasm-stargate" ], + [ + "@lbmjs/finschia", + "workspace:packages/finschia" + ], [ "@lbmjs/stargate", "workspace:packages/stargate" @@ -3256,6 +3265,65 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "SOFT", }] ]], + ["@lbmjs/finschia", [ + ["workspace:packages/finschia", { + "packageLocation": "./packages/finschia/", + "packageDependencies": [ + ["@lbmjs/finschia", "workspace:packages/finschia"], + ["@cosmjs/amino", "npm:0.28.4"], + ["@cosmjs/crypto", "npm:0.28.4"], + ["@cosmjs/encoding", "npm:0.28.4"], + ["@cosmjs/math", "npm:0.28.4"], + ["@cosmjs/proto-signing", "npm:0.28.4"], + ["@cosmjs/stargate", "npm:0.28.4"], + ["@cosmjs/tendermint-rpc", "npm:0.28.4"], + ["@cosmjs/utils", "npm:0.28.4"], + ["@istanbuljs/nyc-config-typescript", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:1.0.2"], + ["@types/eslint-plugin-prettier", "npm:3.1.0"], + ["@types/jasmine", "npm:3.10.3"], + ["@types/karma-firefox-launcher", "npm:2.1.1"], + ["@types/karma-jasmine", "npm:4.0.2"], + ["@types/karma-jasmine-html-reporter", "npm:1.7.0"], + ["@types/long", "npm:4.0.1"], + ["@types/node", "npm:15.14.9"], + ["@typescript-eslint/eslint-plugin", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:5.17.0"], + ["@typescript-eslint/parser", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:5.17.0"], + ["cosmjs-types", "npm:0.4.0"], + ["eslint", "npm:7.32.0"], + ["eslint-config-prettier", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:8.3.0"], + ["eslint-import-resolver-node", "npm:0.3.6"], + ["eslint-plugin-import", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:2.25.4"], + ["eslint-plugin-prettier", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:3.4.1"], + ["eslint-plugin-simple-import-sort", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:7.0.0"], + ["esm", "npm:3.2.25"], + ["glob", "npm:7.2.0"], + ["jasmine", "npm:3.99.0"], + ["jasmine-spec-reporter", "npm:6.0.0"], + ["karma", "npm:6.3.17"], + ["karma-chrome-launcher", "npm:3.1.0"], + ["karma-firefox-launcher", "npm:2.1.2"], + ["karma-jasmine", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:4.0.1"], + ["karma-jasmine-html-reporter", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:1.7.0"], + ["karma-spec-reporter", "virtual:26e5161f9334867c32e64ba710d150b793423888353a1f38ef374e114f66ab3bd954121c8c836600895fce8de791269d42d96437ce1fd17b65f1988c2ccf9850#npm:0.0.33"], + ["karma-typescript", "virtual:26e5161f9334867c32e64ba710d150b793423888353a1f38ef374e114f66ab3bd954121c8c836600895fce8de791269d42d96437ce1fd17b65f1988c2ccf9850#npm:5.5.3"], + ["lbmjs-types", "npm:0.46.0-rc7"], + ["long", "npm:4.0.0"], + ["nyc", "npm:15.1.0"], + ["prettier", "npm:2.5.1"], + ["protobufjs", "npm:6.10.2"], + ["readonly-date", "npm:1.0.0"], + ["ses", "npm:0.11.1"], + ["source-map-support", "npm:0.5.21"], + ["ts-node", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:8.10.2"], + ["typedoc", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:0.22.11"], + ["typescript", "patch:typescript@npm%3A4.4.4#~builtin::version=4.4.4&hash=ddd1e8"], + ["webpack", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:5.68.0"], + ["webpack-cli", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:4.9.2"], + ["xstream", "npm:11.14.0"] + ], + "linkType": "SOFT", + }] + ]], ["@lbmjs/stargate", [ ["workspace:packages/stargate", { "packageLocation": "./packages/stargate/", @@ -4099,6 +4167,23 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "webpack" ], "linkType": "HARD", + }], + ["virtual:f6b229ce6542e610f1ece32df3b38615370a33a65368c09e8ed35198859df7978d11316dec50072fe62c8622947824b1428d2ebb4cc3a6012673efefae243437#npm:1.1.1", { + "packageLocation": "./.yarn/__virtual__/@webpack-cli-configtest-virtual-72ddfcc368/0/cache/@webpack-cli-configtest-npm-1.1.1-87de53d084-c4e7fca213.zip/node_modules/@webpack-cli/configtest/", + "packageDependencies": [ + ["@webpack-cli/configtest", "virtual:f6b229ce6542e610f1ece32df3b38615370a33a65368c09e8ed35198859df7978d11316dec50072fe62c8622947824b1428d2ebb4cc3a6012673efefae243437#npm:1.1.1"], + ["@types/webpack", null], + ["@types/webpack-cli", null], + ["webpack", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:5.68.0"], + ["webpack-cli", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:4.9.2"] + ], + "packagePeers": [ + "@types/webpack-cli", + "@types/webpack", + "webpack-cli", + "webpack" + ], + "linkType": "HARD", }] ]], ["@webpack-cli/info", [ @@ -4136,6 +4221,20 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "webpack-cli" ], "linkType": "HARD", + }], + ["virtual:f6b229ce6542e610f1ece32df3b38615370a33a65368c09e8ed35198859df7978d11316dec50072fe62c8622947824b1428d2ebb4cc3a6012673efefae243437#npm:1.4.1", { + "packageLocation": "./.yarn/__virtual__/@webpack-cli-info-virtual-7b4eb397bf/0/cache/@webpack-cli-info-npm-1.4.1-34c6d9b3f1-7a7cac2ba4.zip/node_modules/@webpack-cli/info/", + "packageDependencies": [ + ["@webpack-cli/info", "virtual:f6b229ce6542e610f1ece32df3b38615370a33a65368c09e8ed35198859df7978d11316dec50072fe62c8622947824b1428d2ebb4cc3a6012673efefae243437#npm:1.4.1"], + ["@types/webpack-cli", null], + ["envinfo", "npm:7.8.1"], + ["webpack-cli", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:4.9.2"] + ], + "packagePeers": [ + "@types/webpack-cli", + "webpack-cli" + ], + "linkType": "HARD", }] ]], ["@webpack-cli/serve", [ @@ -4179,6 +4278,23 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "webpack-dev-server" ], "linkType": "HARD", + }], + ["virtual:f6b229ce6542e610f1ece32df3b38615370a33a65368c09e8ed35198859df7978d11316dec50072fe62c8622947824b1428d2ebb4cc3a6012673efefae243437#npm:1.6.1", { + "packageLocation": "./.yarn/__virtual__/@webpack-cli-serve-virtual-a358b0287c/0/cache/@webpack-cli-serve-npm-1.6.1-e0ea59ed03-8b273f906a.zip/node_modules/@webpack-cli/serve/", + "packageDependencies": [ + ["@webpack-cli/serve", "virtual:f6b229ce6542e610f1ece32df3b38615370a33a65368c09e8ed35198859df7978d11316dec50072fe62c8622947824b1428d2ebb4cc3a6012673efefae243437#npm:1.6.1"], + ["@types/webpack-cli", null], + ["@types/webpack-dev-server", null], + ["webpack-cli", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:4.9.2"], + ["webpack-dev-server", null] + ], + "packagePeers": [ + "@types/webpack-cli", + "@types/webpack-dev-server", + "webpack-cli", + "webpack-dev-server" + ], + "linkType": "HARD", }] ]], ["@xtuc/ieee754", [ @@ -9869,6 +9985,36 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ], "linkType": "HARD", }], + ["virtual:71315d65b1bb717c381f9295495352636af6cf963b063a7e83526da86d6f194d6341c9a1a5abe8ccb0cedc1c137500206d03f7b9d7960a7e796292f3438ce996#npm:5.3.1", { + "packageLocation": "./.yarn/__virtual__/terser-webpack-plugin-virtual-cf464f9fb9/0/cache/terser-webpack-plugin-npm-5.3.1-0c0596f996-1b808fd4f5.zip/node_modules/terser-webpack-plugin/", + "packageDependencies": [ + ["terser-webpack-plugin", "virtual:71315d65b1bb717c381f9295495352636af6cf963b063a7e83526da86d6f194d6341c9a1a5abe8ccb0cedc1c137500206d03f7b9d7960a7e796292f3438ce996#npm:5.3.1"], + ["@swc/core", null], + ["@types/esbuild", null], + ["@types/swc__core", null], + ["@types/uglify-js", null], + ["@types/webpack", null], + ["esbuild", null], + ["jest-worker", "npm:27.5.0"], + ["schema-utils", "npm:3.1.1"], + ["serialize-javascript", "npm:6.0.0"], + ["source-map", "npm:0.6.1"], + ["terser", "virtual:91a5298f2798ca7f643c571475647ab83e5dea9c4988aefe8dad4926b0c6a1223f7d4afc50ab5c8975a77470e8a56240493bd2206c5744007264c0d9eae32cf5#npm:5.10.0"], + ["uglify-js", null], + ["webpack", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:5.68.0"] + ], + "packagePeers": [ + "@swc/core", + "@types/esbuild", + "@types/swc__core", + "@types/uglify-js", + "@types/webpack", + "esbuild", + "uglify-js", + "webpack" + ], + "linkType": "HARD", + }], ["virtual:e6d62b4776899ed5de48873c3ab82275d7b4d146355f6ef801ee2b12fa79ec862f650c4bf015f20170162e67d66fb729c832a8b81efec86a8443de7cd1f305c4#npm:5.3.1", { "packageLocation": "./.yarn/__virtual__/terser-webpack-plugin-virtual-91a5298f27/0/cache/terser-webpack-plugin-npm-5.3.1-0c0596f996-1b808fd4f5.zip/node_modules/terser-webpack-plugin/", "packageDependencies": [ @@ -10426,6 +10572,43 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "webpack-cli" ], "linkType": "HARD", + }], + ["virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:5.68.0", { + "packageLocation": "./.yarn/__virtual__/webpack-virtual-71315d65b1/0/cache/webpack-npm-5.68.0-f34609ad11-ac6efd861a.zip/node_modules/webpack/", + "packageDependencies": [ + ["webpack", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:5.68.0"], + ["@types/eslint-scope", "npm:3.7.3"], + ["@types/estree", "npm:0.0.50"], + ["@types/webpack-cli", null], + ["@webassemblyjs/ast", "npm:1.11.1"], + ["@webassemblyjs/wasm-edit", "npm:1.11.1"], + ["@webassemblyjs/wasm-parser", "npm:1.11.1"], + ["acorn", "npm:8.7.0"], + ["acorn-import-assertions", "virtual:e6d62b4776899ed5de48873c3ab82275d7b4d146355f6ef801ee2b12fa79ec862f650c4bf015f20170162e67d66fb729c832a8b81efec86a8443de7cd1f305c4#npm:1.8.0"], + ["browserslist", "npm:4.19.1"], + ["chrome-trace-event", "npm:1.0.3"], + ["enhanced-resolve", "npm:5.9.0"], + ["es-module-lexer", "npm:0.9.3"], + ["eslint-scope", "npm:5.1.1"], + ["events", "npm:3.3.0"], + ["glob-to-regexp", "npm:0.4.1"], + ["graceful-fs", "npm:4.2.9"], + ["json-parse-better-errors", "npm:1.0.2"], + ["loader-runner", "npm:4.2.0"], + ["mime-types", "npm:2.1.34"], + ["neo-async", "npm:2.6.2"], + ["schema-utils", "npm:3.1.1"], + ["tapable", "npm:2.2.1"], + ["terser-webpack-plugin", "virtual:71315d65b1bb717c381f9295495352636af6cf963b063a7e83526da86d6f194d6341c9a1a5abe8ccb0cedc1c137500206d03f7b9d7960a7e796292f3438ce996#npm:5.3.1"], + ["watchpack", "npm:2.3.1"], + ["webpack-cli", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:4.9.2"], + ["webpack-sources", "npm:3.2.3"] + ], + "packagePeers": [ + "@types/webpack-cli", + "webpack-cli" + ], + "linkType": "HARD", }] ]], ["webpack-cli", [ @@ -10517,6 +10700,47 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "webpack" ], "linkType": "HARD", + }], + ["virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:4.9.2", { + "packageLocation": "./.yarn/__virtual__/webpack-cli-virtual-f6b229ce65/0/cache/webpack-cli-npm-4.9.2-5e7d77ef6f-ffb4c5d53a.zip/node_modules/webpack-cli/", + "packageDependencies": [ + ["webpack-cli", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:4.9.2"], + ["@discoveryjs/json-ext", "npm:0.5.6"], + ["@types/webpack", null], + ["@types/webpack-bundle-analyzer", null], + ["@types/webpack-cli__generators", null], + ["@types/webpack-cli__migrate", null], + ["@types/webpack-dev-server", null], + ["@webpack-cli/configtest", "virtual:f6b229ce6542e610f1ece32df3b38615370a33a65368c09e8ed35198859df7978d11316dec50072fe62c8622947824b1428d2ebb4cc3a6012673efefae243437#npm:1.1.1"], + ["@webpack-cli/generators", null], + ["@webpack-cli/info", "virtual:f6b229ce6542e610f1ece32df3b38615370a33a65368c09e8ed35198859df7978d11316dec50072fe62c8622947824b1428d2ebb4cc3a6012673efefae243437#npm:1.4.1"], + ["@webpack-cli/migrate", null], + ["@webpack-cli/serve", "virtual:f6b229ce6542e610f1ece32df3b38615370a33a65368c09e8ed35198859df7978d11316dec50072fe62c8622947824b1428d2ebb4cc3a6012673efefae243437#npm:1.6.1"], + ["colorette", "npm:2.0.16"], + ["commander", "npm:7.2.0"], + ["execa", "npm:5.1.1"], + ["fastest-levenshtein", "npm:1.0.12"], + ["import-local", "npm:3.1.0"], + ["interpret", "npm:2.2.0"], + ["rechoir", "npm:0.7.1"], + ["webpack", "virtual:7b7a30ab09c232808f75d8deefc2be1f787c4d994bf0447ebbc0120f713df9b186b49db25d8d4a325f09a8c21d3026240b3241d151187e257eff7792b66c65e7#npm:5.68.0"], + ["webpack-bundle-analyzer", null], + ["webpack-dev-server", null], + ["webpack-merge", "npm:5.8.0"] + ], + "packagePeers": [ + "@types/webpack-bundle-analyzer", + "@types/webpack-cli__generators", + "@types/webpack-cli__migrate", + "@types/webpack-dev-server", + "@types/webpack", + "@webpack-cli/generators", + "@webpack-cli/migrate", + "webpack-bundle-analyzer", + "webpack-dev-server", + "webpack" + ], + "linkType": "HARD", }] ]], ["webpack-merge", [ diff --git a/packages/finschia/.eslintignore b/packages/finschia/.eslintignore new file mode 120000 index 000000000..86039baf5 --- /dev/null +++ b/packages/finschia/.eslintignore @@ -0,0 +1 @@ +../../.eslintignore \ No newline at end of file diff --git a/packages/finschia/.eslintrc.js b/packages/finschia/.eslintrc.js new file mode 100644 index 000000000..b76dd7be3 --- /dev/null +++ b/packages/finschia/.eslintrc.js @@ -0,0 +1,94 @@ +module.exports = { + env: { + es6: true, + jasmine: true, + node: true, + worker: true, + }, + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 2018, + project: "./tsconfig.eslint.json", + tsconfigRootDir: __dirname, + }, + plugins: ["@typescript-eslint", "prettier", "simple-import-sort", "import"], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + "plugin:prettier/recommended", + "plugin:import/typescript", + ], + rules: { + curly: ["warn", "multi-line", "consistent"], + "no-bitwise": "warn", + "no-console": ["warn", { allow: ["error", "info", "table", "warn"] }], + "no-param-reassign": "warn", + "no-shadow": "off", // disabled in favour of @typescript-eslint/no-shadow, see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-shadow.md + "no-unused-vars": "off", // disabled in favour of @typescript-eslint/no-unused-vars, see https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md + "prefer-const": "warn", + radix: ["warn", "always"], + "spaced-comment": ["warn", "always", { line: { markers: ["/ + queryService.MyCustomQuery({ foo: foo }), + }, + }; +} +function setupYyyExtension(base: QueryClient) { + // ... +} + +// Setup the query client +const queryClient = QueryClient.withExtensions( + tendermintClient, + setupXxxExtension, + setupYyyExtension, + // You can add up to 18 extensions +); + +// Inside an async function... +// Now your query client has been extended +const queryResult = await queryClient.mymodule.customQuery("bar"); +``` + +You can see how CosmJS sets up the `bank` extension for its default query client +[here](https://github.com/cosmos/cosmjs/blob/v0.26.4/packages/stargate/src/queries/bank.ts). diff --git a/packages/finschia/README.md b/packages/finschia/README.md new file mode 100644 index 000000000..13bf02216 --- /dev/null +++ b/packages/finschia/README.md @@ -0,0 +1,2 @@ +# @lbmjs/stargate + diff --git a/packages/finschia/jasmine-testrunner.js b/packages/finschia/jasmine-testrunner.js new file mode 100644 index 000000000..afefb63a4 --- /dev/null +++ b/packages/finschia/jasmine-testrunner.js @@ -0,0 +1,38 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +if (process.env.SES_ENABLED) { + require("ses/lockdown"); + // eslint-disable-next-line no-undef + lockdown(); +} + +require("source-map-support").install(); +const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json"); + +// setup Jasmine +const Jasmine = require("jasmine"); +const jasmine = new Jasmine(); +jasmine.loadConfig({ + spec_dir: "build", + spec_files: ["**/*.spec.js"], + helpers: [], + random: false, + seed: null, + stopSpecOnExpectationFailure: false, +}); +jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000; + +// setup reporter +const { SpecReporter } = require("jasmine-spec-reporter"); +const reporter = new SpecReporter({ + ...defaultSpecReporterConfig, + spec: { + ...defaultSpecReporterConfig.spec, + displaySuccessful: !process.argv.includes("--quiet"), + }, +}); + +// initialize and execute +jasmine.env.clearReporters(); +jasmine.addReporter(reporter); +void jasmine.execute(); diff --git a/packages/finschia/karma.conf.js b/packages/finschia/karma.conf.js new file mode 100644 index 000000000..7853d6723 --- /dev/null +++ b/packages/finschia/karma.conf.js @@ -0,0 +1,71 @@ +const realBrowser = String(process.env.BROWSER).match(/^(1|true)$/gi); +const travisLaunchers = { + // eslint-disable-next-line @typescript-eslint/naming-convention + chrome_travis: { + base: "Chrome", + flags: ["--no-sandbox"], + }, +}; + +const localBrowsers = realBrowser ? Object.keys(travisLaunchers) : ["Chrome"]; + +module.exports = function (config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: ".", + + // frameworks to use + // available frameworks: https://www.npmjs.com/search?q=keywords:karma-adapter + frameworks: ["jasmine", "karma-typescript"], + plugins: ["karma-jasmine", "karma-chrome-launcher", "karma-typescript", "karma-spec-reporter"], + karmaTypescriptConfig: { + tsconfig: "./tsconfig.json", + }, + client: { + // leave Jasmine Spec Runner output visible in browser + clearContext: false, + }, + + // list of files / patterns to load in the browser + files: [{ pattern: "src/**/*.ts" }, { pattern: "src/**/*.spec.ts" }], + + // list of files / patterns to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://www.npmjs.com/search?q=keywords:karma-preprocessor + preprocessors: { + "src/**/*.ts": ["karma-typescript"], + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://www.npmjs.com/search?q=keywords:karma-reporter + reporters: ["spec", "karma-typescript"], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // start these browsers + // available browser launchers: https://www.npmjs.com/search?q=keywords:karma-launcher + browsers: localBrowsers, + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: true, + + // Concurrency level + // how many browser instances should be started simultaneously + concurrency: Infinity, + }); +}; diff --git a/packages/finschia/package.json b/packages/finschia/package.json new file mode 100644 index 000000000..a94a6a626 --- /dev/null +++ b/packages/finschia/package.json @@ -0,0 +1,91 @@ +{ + "name": "@lbmjs/finschia", + "version": "0.4.0-rc7", + "description": "Utilities for LBM SDK 0.45.0-rc7", + "contributors": [ + "Simon Warta ", + "zemyblue " + ], + "license": "Apache-2.0", + "main": "build/index.js", + "types": "build/index.d.ts", + "files": [ + "build/", + "*.md", + "!*.spec.*", + "!**/testdata/" + ], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "docs": "typedoc --options typedoc.js", + "lint": "eslint --max-warnings 0 \"./**/*.ts\" \"./*.js\"", + "lint-fix": "eslint --fix --max-warnings 0 \"./**/*.ts\" \"./*.js\"", + "format": "prettier --write --loglevel warn \"./src/**/*.ts\"", + "format-text": "prettier --write \"./*.md\"", + "build": "rm -rf ./build && tsc", + "build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build", + "test-node": "yarn node jasmine-testrunner.js", + "test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox", + "test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless", + "test": "yarn build-or-skip && yarn test-node", + "coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet", + "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" + }, + "dependencies": { + "@cosmjs/amino": "0.28.4", + "@cosmjs/encoding": "0.28.4", + "@cosmjs/math": "0.28.4", + "@cosmjs/proto-signing": "0.28.4", + "@cosmjs/stargate": "0.28.4", + "@cosmjs/tendermint-rpc": "0.28.4", + "@cosmjs/utils": "0.28.4", + "cosmjs-types": "0.4.0", + "lbmjs-types": "^0.46.0-rc7", + "long": "^4.0.0", + "protobufjs": "~6.10.2", + "xstream": "^11.14.0" + }, + "devDependencies": { + "@cosmjs/crypto": "0.28.4", + "@istanbuljs/nyc-config-typescript": "^1.0.1", + "@types/eslint-plugin-prettier": "^3", + "@types/jasmine": "^3.8", + "@types/karma-firefox-launcher": "^2", + "@types/karma-jasmine": "^4", + "@types/karma-jasmine-html-reporter": "^1", + "@types/long": "^4.0.1", + "@types/node": "^15.0.1", + "@typescript-eslint/eslint-plugin": "^5.13.0", + "@typescript-eslint/parser": "^5.13.0", + "eslint": "^7.5", + "eslint-config-prettier": "^8.3.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-simple-import-sort": "^7.0.0", + "esm": "^3.2.25", + "glob": "^7.1.6", + "jasmine": "^3.99", + "jasmine-spec-reporter": "^6", + "karma": "^6.3.17", + "karma-chrome-launcher": "^3.1.0", + "karma-firefox-launcher": "^2.1.0", + "karma-jasmine": "^4.0.1", + "karma-jasmine-html-reporter": "^1.5.4", + "karma-spec-reporter": "^0.0.33", + "karma-typescript": "^5.5.3", + "nyc": "^15.1.0", + "prettier": "^2.4.1", + "readonly-date": "^1.0.0", + "ses": "^0.11.0", + "source-map-support": "^0.5.19", + "ts-node": "^8", + "typedoc": "^0.22", + "typescript": "~4.4", + "webpack": "^5.32.0", + "webpack-cli": "^4.6.0" + } +} diff --git a/packages/finschia/tsconfig.eslint.json b/packages/finschia/tsconfig.eslint.json new file mode 100644 index 000000000..9a9f3b570 --- /dev/null +++ b/packages/finschia/tsconfig.eslint.json @@ -0,0 +1,9 @@ +{ + // extend your base config so you don't have to redefine your compilerOptions + "extends": "./tsconfig.json", + "include": [ + "src/**/*", + "*.js", + ".eslintrc.js" + ] +} diff --git a/packages/finschia/tsconfig.json b/packages/finschia/tsconfig.json new file mode 100644 index 000000000..4840979dc --- /dev/null +++ b/packages/finschia/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "experimentalDecorators": true, + "rootDir": "src" + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/finschia/typedoc.js b/packages/finschia/typedoc.js new file mode 100644 index 000000000..ffe4be645 --- /dev/null +++ b/packages/finschia/typedoc.js @@ -0,0 +1,11 @@ +const packageJson = require("./package.json"); + +module.exports = { + entryPoints: ["./src"], + out: "docs", + exclude: "**/*.spec.ts", + name: `${packageJson.name} Documentation`, + readme: "README.md", + excludeExternals: true, + excludePrivate: true, +}; diff --git a/packages/finschia/webpack.web.config.js b/packages/finschia/webpack.web.config.js new file mode 100644 index 000000000..b2ed7ef72 --- /dev/null +++ b/packages/finschia/webpack.web.config.js @@ -0,0 +1,40 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +const glob = require("glob"); +const path = require("path"); +const webpack = require("webpack"); + +const target = "web"; +const distdir = path.join(__dirname, "dist", "web"); + +module.exports = [ + { + // bundle used for Karma tests + target: target, + entry: glob.sync("./build/**/*.spec.js"), + output: { + path: distdir, + filename: "tests.js", + }, + plugins: [ + new webpack.EnvironmentPlugin({ + SIMAPP42_ENABLED: "", + SLOW_SIMAPP42_ENABLED: "", + SIMAPP44_ENABLED: "", + SLOW_SIMAPP44_ENABLED: "", + }), + new webpack.ProvidePlugin({ + Buffer: ["buffer", "Buffer"], + }), + ], + resolve: { + fallback: { + buffer: false, + crypto: false, + events: false, + path: false, + stream: false, + string_decoder: false, + }, + }, + }, +]; diff --git a/yarn.lock b/yarn.lock index f30e84317..57cc51367 100644 --- a/yarn.lock +++ b/yarn.lock @@ -589,6 +589,63 @@ __metadata: languageName: unknown linkType: soft +"@lbmjs/finschia@workspace:packages/finschia": + version: 0.0.0-use.local + resolution: "@lbmjs/finschia@workspace:packages/finschia" + dependencies: + "@cosmjs/amino": 0.28.4 + "@cosmjs/crypto": 0.28.4 + "@cosmjs/encoding": 0.28.4 + "@cosmjs/math": 0.28.4 + "@cosmjs/proto-signing": 0.28.4 + "@cosmjs/stargate": 0.28.4 + "@cosmjs/tendermint-rpc": 0.28.4 + "@cosmjs/utils": 0.28.4 + "@istanbuljs/nyc-config-typescript": ^1.0.1 + "@types/eslint-plugin-prettier": ^3 + "@types/jasmine": ^3.8 + "@types/karma-firefox-launcher": ^2 + "@types/karma-jasmine": ^4 + "@types/karma-jasmine-html-reporter": ^1 + "@types/long": ^4.0.1 + "@types/node": ^15.0.1 + "@typescript-eslint/eslint-plugin": ^5.13.0 + "@typescript-eslint/parser": ^5.13.0 + cosmjs-types: 0.4.0 + eslint: ^7.5 + eslint-config-prettier: ^8.3.0 + eslint-import-resolver-node: ^0.3.4 + eslint-plugin-import: ^2.22.1 + eslint-plugin-prettier: ^3.4.0 + eslint-plugin-simple-import-sort: ^7.0.0 + esm: ^3.2.25 + glob: ^7.1.6 + jasmine: ^3.99 + jasmine-spec-reporter: ^6 + karma: ^6.3.17 + karma-chrome-launcher: ^3.1.0 + karma-firefox-launcher: ^2.1.0 + karma-jasmine: ^4.0.1 + karma-jasmine-html-reporter: ^1.5.4 + karma-spec-reporter: ^0.0.33 + karma-typescript: ^5.5.3 + lbmjs-types: ^0.46.0-rc7 + long: ^4.0.0 + nyc: ^15.1.0 + prettier: ^2.4.1 + protobufjs: ~6.10.2 + readonly-date: ^1.0.0 + ses: ^0.11.0 + source-map-support: ^0.5.19 + ts-node: ^8 + typedoc: ^0.22 + typescript: ~4.4 + webpack: ^5.32.0 + webpack-cli: ^4.6.0 + xstream: ^11.14.0 + languageName: unknown + linkType: soft + "@lbmjs/stargate@workspace:packages/stargate": version: 0.0.0-use.local resolution: "@lbmjs/stargate@workspace:packages/stargate" From 1789773cddb17ffb124fa2b14cb145611e58bfbd Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Mon, 5 Sep 2022 21:40:28 +0900 Subject: [PATCH 02/15] feat: add finschia client with new modules --- packages/finschia/src/finschiaClient.ts | 118 ++++ .../src/modules/collection/messages.ts | 50 ++ .../src/modules/collection/queries.spec.ts | 657 ++++++++++++++++++ .../src/modules/collection/queries.ts | 158 +++++ .../finschia/src/modules/collection/utils.ts | 28 + .../src/modules/evidence/aminomessages.ts | 28 + .../src/modules/evidence/queries.spec.ts | 28 + .../finschia/src/modules/evidence/queries.ts | 32 + .../src/modules/feegrant/aminomessages.ts | 8 + .../finschia/src/modules/feegrant/messages.ts | 7 + .../src/modules/feegrant/queries.spec.ts | 99 +++ .../finschia/src/modules/feegrant/queries.ts | 40 ++ .../src/modules/foundation/messages.ts | 199 ++++++ .../src/modules/foundation/queries.spec.ts | 190 +++++ .../src/modules/foundation/queries.ts | 99 +++ .../src/modules/ibc/aminomessages.spec.ts | 184 +++++ .../finschia/src/modules/ibc/aminomessages.ts | 113 +++ .../src/modules/ibc/ibctestdata.spec.ts | 112 +++ packages/finschia/src/modules/ibc/messages.ts | 57 ++ .../finschia/src/modules/ibc/queries.spec.ts | 559 +++++++++++++++ packages/finschia/src/modules/ibc/queries.ts | 538 ++++++++++++++ packages/finschia/src/modules/index.ts | 32 + .../finschia/src/modules/token/messages.ts | 28 + .../src/modules/token/queries.spec.ts | 385 ++++++++++ .../finschia/src/modules/token/queries.ts | 75 ++ packages/finschia/src/paths.spec.ts | 26 + packages/finschia/src/paths.ts | 15 + packages/finschia/src/testutils.spec.ts | 233 +++++++ packages/finschia/src/utils.ts | 7 + 29 files changed, 4105 insertions(+) create mode 100644 packages/finschia/src/finschiaClient.ts create mode 100644 packages/finschia/src/modules/collection/messages.ts create mode 100644 packages/finschia/src/modules/collection/queries.spec.ts create mode 100644 packages/finschia/src/modules/collection/queries.ts create mode 100644 packages/finschia/src/modules/collection/utils.ts create mode 100644 packages/finschia/src/modules/evidence/aminomessages.ts create mode 100644 packages/finschia/src/modules/evidence/queries.spec.ts create mode 100644 packages/finschia/src/modules/evidence/queries.ts create mode 100644 packages/finschia/src/modules/feegrant/aminomessages.ts create mode 100644 packages/finschia/src/modules/feegrant/messages.ts create mode 100644 packages/finschia/src/modules/feegrant/queries.spec.ts create mode 100644 packages/finschia/src/modules/feegrant/queries.ts create mode 100644 packages/finschia/src/modules/foundation/messages.ts create mode 100644 packages/finschia/src/modules/foundation/queries.spec.ts create mode 100644 packages/finschia/src/modules/foundation/queries.ts create mode 100644 packages/finschia/src/modules/ibc/aminomessages.spec.ts create mode 100644 packages/finschia/src/modules/ibc/aminomessages.ts create mode 100644 packages/finschia/src/modules/ibc/ibctestdata.spec.ts create mode 100644 packages/finschia/src/modules/ibc/messages.ts create mode 100644 packages/finschia/src/modules/ibc/queries.spec.ts create mode 100644 packages/finschia/src/modules/ibc/queries.ts create mode 100644 packages/finschia/src/modules/index.ts create mode 100644 packages/finschia/src/modules/token/messages.ts create mode 100644 packages/finschia/src/modules/token/queries.spec.ts create mode 100644 packages/finschia/src/modules/token/queries.ts create mode 100644 packages/finschia/src/paths.spec.ts create mode 100644 packages/finschia/src/paths.ts create mode 100644 packages/finschia/src/testutils.spec.ts create mode 100644 packages/finschia/src/utils.ts diff --git a/packages/finschia/src/finschiaClient.ts b/packages/finschia/src/finschiaClient.ts new file mode 100644 index 000000000..590196d5c --- /dev/null +++ b/packages/finschia/src/finschiaClient.ts @@ -0,0 +1,118 @@ +import { GeneratedType, OfflineSigner, Registry } from "@cosmjs/proto-signing"; +import { + defaultRegistryTypes, + QueryClient, + SigningStargateClient, + SigningStargateClientOptions, +} from "@cosmjs/stargate"; +import { HttpEndpoint, Tendermint34Client } from "@cosmjs/tendermint-rpc"; + +import { + CollectionExtension, + collectionTypes, + EvidenceExtension, + FeeGrantExtension, + feegrantTypes, + FoundationExtension, + foundationTypes, + IbcExtension, + ibcTypes, + setupCollectionExtension, + setupEvidenceExtension, + setupFeeGrantExtension, + setupFoundationExtension, + setupIbcExtension, + setupTokenExtension, + TokenExtension, + tokenTypes, +} from "./modules"; + +export const finschiaRegistryTypes: ReadonlyArray<[string, GeneratedType]> = [ + ...defaultRegistryTypes, + ...feegrantTypes, + ...ibcTypes, + ...tokenTypes, + ...foundationTypes, + ...collectionTypes, +]; + +function createDefaultRegistry(): Registry { + return new Registry(finschiaRegistryTypes); +} +export class FinschiaClient extends SigningStargateClient { + public override readonly registry: Registry; + + private readonly finschiaQueryClient: + | (QueryClient & + CollectionExtension & + EvidenceExtension & + FeeGrantExtension & + FoundationExtension & + IbcExtension & + TokenExtension) + | undefined; + + public static override async connectWithSigner( + endpoint: string | HttpEndpoint, + signer: OfflineSigner, + options: SigningStargateClientOptions = {}, + ): Promise { + const tmClient = await Tendermint34Client.connect(endpoint); + return new FinschiaClient(tmClient, signer, options); + } + + public static override async offline( + signer: OfflineSigner, + options: SigningStargateClientOptions = {}, + ): Promise { + return new FinschiaClient(undefined, signer, options); + } + + protected constructor( + tmClient: Tendermint34Client | undefined, + signer: OfflineSigner, + options: SigningStargateClientOptions, + ) { + super(tmClient, signer, options); + + const { registry = createDefaultRegistry() } = options; + this.registry = registry; + + if (tmClient) { + this.finschiaQueryClient = QueryClient.withExtensions( + tmClient, + setupCollectionExtension, + setupEvidenceExtension, + setupFeeGrantExtension, + setupFoundationExtension, + setupIbcExtension, + setupTokenExtension, + ); + } + } + + protected getFinschiaQueryClient(): + | (QueryClient & + CollectionExtension & + EvidenceExtension & + FeeGrantExtension & + FoundationExtension & + IbcExtension & + TokenExtension) + | undefined { + return this.finschiaQueryClient; + } + + protected forceGetFinschiaQueryClient(): QueryClient & + CollectionExtension & + EvidenceExtension & + FeeGrantExtension & + FoundationExtension & + IbcExtension & + TokenExtension { + if (!this.finschiaQueryClient) { + throw new Error("Query client not available. You cannot use online functionality in offline mode."); + } + return this.finschiaQueryClient; + } +} diff --git a/packages/finschia/src/modules/collection/messages.ts b/packages/finschia/src/modules/collection/messages.ts new file mode 100644 index 000000000..a4a646b61 --- /dev/null +++ b/packages/finschia/src/modules/collection/messages.ts @@ -0,0 +1,50 @@ +import { GeneratedType } from "@cosmjs/proto-signing"; +import { + MsgApprove, + MsgAttach, + MsgAttachFrom, + MsgBurnFT, + MsgBurnFTFrom, + MsgBurnNFT, + MsgBurnNFTFrom, + MsgCreateContract, + MsgDetach, + MsgDetachFrom, + MsgDisapprove, + MsgGrantPermission, + MsgIssueFT, + MsgIssueNFT, + MsgMintFT, + MsgMintNFT, + MsgModify, + MsgRevokePermission, + MsgTransferFT, + MsgTransferFTFrom, + MsgTransferNFT, + MsgTransferNFTFrom, +} from "lbmjs-types/lbm/collection/v1/tx"; + +export const collectionTypes: ReadonlyArray<[string, GeneratedType]> = [ + ["/lbm.collection.v1.MsgTransferFT", MsgTransferFT], + ["/lbm.collection.v1.MsgTransferFTFrom", MsgTransferFTFrom], + ["/lbm.collection.v1.MsgTransferNFT", MsgTransferNFT], + ["/lbm.collection.v1.MsgTransferNFTFrom", MsgTransferNFTFrom], + ["/lbm.collection.v1.MsgApprove", MsgApprove], + ["/lbm.collection.v1.MsgDisapprove", MsgDisapprove], + ["/lbm.collection.v1.MsgCreateContract", MsgCreateContract], + ["/lbm.collection.v1.MsgIssueFT", MsgIssueFT], + ["/lbm.collection.v1.MsgIssueNFT", MsgIssueNFT], + ["/lbm.collection.v1.MsgMintFT", MsgMintFT], + ["/lbm.collection.v1.MsgMintNFT", MsgMintNFT], + ["/lbm.collection.v1.MsgBurnFT", MsgBurnFT], + ["/lbm.collection.v1.MsgBurnFTFrom", MsgBurnFTFrom], + ["/lbm.collection.v1.MsgBurnNFT", MsgBurnNFT], + ["/lbm.collection.v1.MsgBurnNFTFrom", MsgBurnNFTFrom], + ["/lbm.collection.v1.MsgModify", MsgModify], + ["/lbm.collection.v1.MsgGrantPermission", MsgGrantPermission], + ["/lbm.collection.v1.MsgRevokePermission", MsgRevokePermission], + ["/lbm.collection.v1.MsgAttach", MsgAttach], + ["/lbm.collection.v1.MsgDetach", MsgDetach], + ["/lbm.collection.v1.MsgAttachFrom", MsgAttachFrom], + ["/lbm.collection.v1.MsgDetachFrom", MsgDetachFrom], +]; diff --git a/packages/finschia/src/modules/collection/queries.spec.ts b/packages/finschia/src/modules/collection/queries.spec.ts new file mode 100644 index 000000000..e1b6fa78f --- /dev/null +++ b/packages/finschia/src/modules/collection/queries.spec.ts @@ -0,0 +1,657 @@ +import { coins } from "@cosmjs/amino"; +import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing"; +import { assertIsDeliverTxSuccess, logs, QueryClient } from "@cosmjs/stargate"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { assert, sleep } from "@cosmjs/utils"; +import { FT, OwnerNFT } from "lbmjs-types/lbm/collection/v1/collection"; +import { + MsgApprove, + MsgAttach, + MsgCreateContract, + MsgIssueFT, + MsgIssueNFT, + MsgMintNFT, + MsgTransferFT, + MsgTransferNFT, +} from "lbmjs-types/lbm/collection/v1/tx"; + +import { FinschiaClient } from "../../finschiaClient"; +import { makeLinkPath } from "../../paths"; +import { + defaultSigningClientOptions, + faucet, + pendingWithoutSimapp, + simapp, + simappEnabled, +} from "../../testutils.spec"; +import { CollectionExtension, setupCollectionExtension } from "./queries"; +import { ftCoins } from "./utils"; + +async function makeClientWithCollection( + rpcUrl: string, +): Promise<[QueryClient & CollectionExtension, Tendermint34Client]> { + const tmClient = await Tendermint34Client.connect(rpcUrl); + return [QueryClient.withExtensions(tmClient, setupCollectionExtension), tmClient]; +} + +describe("CollectionExtension (fungible token)", () => { + const defaultFee = { + amount: coins(250000, "cony"), + gas: "1500000", // 1.5 million + }; + + const owner = faucet.address0; + const toAddr = faucet.address1; + const contractName = "TestContract"; + const tokenName = "TestToken"; + const baseImgUrl = "https://test.network"; + const tokenMeta = "Test Meta data"; + const decimals = 6; + const amount = "1000000"; + const sentAmount = "500000"; + + let contractId: string | undefined; + let tokenId: string | undefined; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await FinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + // CreateContract + { + const msg: MsgCreateContract = { + owner: owner, + name: contractName, + baseImgUri: baseImgUrl, + meta: "test", + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgCreateContract", + value: msg, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + const parsedLogs = logs.parseRawLog(result.rawLog); + contractId = logs.findAttribute( + parsedLogs, + "lbm.collection.v1.EventCreatedContract", + "contract_id", + ).value; + assert(contractId, "Missing contract ID"); + contractId = contractId.replace(/^"(.*)"$/, "$1"); + assertIsDeliverTxSuccess(result); + } + + // IssueFT + { + const msg: MsgIssueFT = { + contractId: contractId, + name: tokenName, + meta: tokenMeta, + decimals: decimals, + mintable: true, + owner: owner, + to: owner, + amount: amount, + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgIssueFT", + value: msg, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + const parsedLogs = logs.parseRawLog(result.rawLog); + tokenId = logs.findAttribute(parsedLogs, "issue_ft", "token_id").value; + assert(tokenId, "Missing token ID"); + tokenId = tokenId.replace(/^"(.*)"$/, "$1"); + assertIsDeliverTxSuccess(result); + } + + // TransferFT + { + const msg: MsgTransferFT = { + contractId: contractId, + from: owner, + to: toAddr, + amount: ftCoins(sentAmount, tokenId), + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgTransferFT", + value: msg, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + assertIsDeliverTxSuccess(result); + } + + await sleep(75); + + client.disconnect(); + } + }); + + describe("query", () => { + it("contract", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const contract = await client.collection.contract(contractId); + expect(contract).toBeDefined(); + expect(contract.contractId).toEqual(contractId); + expect(contract.name).toEqual(contractName); + expect(contract.meta).toEqual("test"); + expect(contract.baseImgUri).toEqual(baseImgUrl); + + tmClient.disconnect(); + }); + it("tokenClassTypeName", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenId, "Mission token Id1"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + const classId = tokenId.substr(0, 8); + const name = await client.collection.tokenClassTypeName(contractId, classId); + expect(name).toEqual("lbm.collection.v1.FTClass"); + + tmClient.disconnect(); + }); + it("token", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenId, "Missing token ID"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const token = await client.collection.token(contractId, tokenId); + expect(token.typeUrl).toEqual("/lbm.collection.v1.FT"); + const ft = FT.decode(token.value); + expect(ft.contractId).toEqual(contractId); + expect(ft.tokenId).toEqual(tokenId); + expect(ft.name).toEqual(tokenName); + expect(ft.meta).toEqual(tokenMeta); + expect(ft.decimals).toEqual(decimals); + expect(ft.mintable).toBeTrue(); + + tmClient.disconnect(); + }); + it("tokens", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenId, "Missing token ID"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const tokens = await client.collection.tokens(contractId); + expect(tokens.length).toEqual(1); + const ft = FT.decode(tokens[0].value); + expect(ft.contractId).toEqual(contractId); + expect(ft.tokenId).toEqual(tokenId); + expect(ft.name).toEqual(tokenName); + expect(ft.meta).toEqual(tokenMeta); + expect(ft.decimals).toEqual(decimals); + expect(ft.mintable).toBeTrue(); + + tmClient.disconnect(); + }); + it("balance", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenId, "Missing token ID"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const coin = await client.collection.balance(contractId, owner, tokenId); + expect(coin).toBeDefined(); + expect(coin.tokenId).toEqual(tokenId); + expect(coin.amount).toEqual((parseInt(amount, 10) - parseInt(sentAmount, 10)).toString()); + + tmClient.disconnect(); + }); + it("allBalances", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenId, "Missing token ID"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const coins = await client.collection.allBalances(contractId, toAddr); + expect(coins.length).toEqual(1); + expect(coins[0].tokenId).toEqual(tokenId); + expect(coins[0].amount).toEqual(sentAmount); + + tmClient.disconnect(); + }); + it("supply", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing class ID"); + assert(tokenId, "Missing token ID"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const supply = await client.collection.ftSupply(contractId, tokenId); + expect(supply).toBeDefined(); + expect(supply).toEqual(amount); + + tmClient.disconnect(); + }); + it("ftMinted, ftBurnt", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing class ID"); + assert(tokenId, "Missing token ID"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const minted = await client.collection.ftMinted(contractId, tokenId); + expect(minted).toEqual(amount); + + const burnt = await client.collection.ftBurnt(contractId, tokenId); + expect(burnt).toEqual("0"); + + tmClient.disconnect(); + }); + }); +}); + +describe("CollectionExtension (non-fungible token)", () => { + const defaultFee = { + amount: coins(250000, "cony"), + gas: "1500000", // 1.5 million + }; + + const owner = faucet.address0; + const toAddr = faucet.address1; + const contractName = "TestContract"; + const contractBaseImgUrl = "https://test.network"; + const tokenName = "TestToken"; + + let contractId: string | undefined; + let tokenType: string | undefined; + let tokenId1: string | undefined; + let tokenId2: string | undefined; + let tokenId3: string | undefined; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0), makeLinkPath(1)], + prefix: simapp.prefix, + }); + const client = await FinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + // CreateContract + { + const msg: MsgCreateContract = { + owner: owner, + name: contractName, + baseImgUri: contractBaseImgUrl, + meta: "Test NFT Contract", + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgCreateContract", + value: msg, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + const parsedLogs = logs.parseRawLog(result.rawLog); + contractId = logs.findAttribute( + parsedLogs, + "lbm.collection.v1.EventCreatedContract", + "contract_id", + ).value; + assert(contractId, "Missing contract ID"); + contractId = contractId.replace(/^"(.*)"$/, "$1"); + assertIsDeliverTxSuccess(result); + } + + // IssueFT + { + const msg: MsgIssueNFT = { + contractId: contractId, + name: tokenName, + meta: "Test Meta data", + owner: owner, + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgIssueNFT", + value: msg, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + const parsedLogs = logs.parseRawLog(result.rawLog); + tokenType = logs.findAttribute(parsedLogs, "issue_nft", "token_type").value; + assert(tokenType, "Missing contract ID"); + tokenType = tokenType.replace(/^"(.*)"$/, "$1"); + assertIsDeliverTxSuccess(result); + } + + // MintNFT(1) + { + const msg: MsgMintNFT = { + contractId: contractId, + from: owner, + to: owner, + params: [ + { + tokenType: tokenType, + name: "Minted TestToken 1", + meta: "Test NFT 1", + }, + ], + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgMintNFT", + value: msg, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + const parsedLogs = logs.parseRawLog(result.rawLog); + tokenId1 = logs.findAttribute(parsedLogs, "mint_nft", "token_id").value; + assertIsDeliverTxSuccess(result); + } + + // MintNFT(2) + { + const msg: MsgMintNFT = { + contractId: contractId, + from: owner, + to: owner, + params: [ + { + tokenType: tokenType, + name: "Minted TestToken 2", + meta: "Test NFT 2", + }, + ], + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgMintNFT", + value: msg, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + const parsedLogs = logs.parseRawLog(result.rawLog); + tokenId2 = logs.findAttribute(parsedLogs, "mint_nft", "token_id").value; + assertIsDeliverTxSuccess(result); + } + + // MintNFT(3) + { + const msg: MsgMintNFT = { + contractId: contractId, + from: owner, + to: owner, + params: [ + { + tokenType: tokenType, + name: "Minted TestToken 3", + meta: "Test NFT 3", + }, + ], + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgMintNFT", + value: msg, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + const parsedLogs = logs.parseRawLog(result.rawLog); + tokenId3 = logs.findAttribute(parsedLogs, "mint_nft", "token_id").value; + assertIsDeliverTxSuccess(result); + } + + // TransferNFT + { + const msg: MsgTransferNFT = { + contractId: contractId, + from: owner, + to: toAddr, + tokenIds: [tokenId2], + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgTransferNFT", + value: msg, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + assertIsDeliverTxSuccess(result); + } + + // Attach ( tokenId1(parent) <--- tokenId3(child) ) + { + const msg: MsgAttach = { + contractId: contractId, + from: owner, + tokenId: tokenId3, + toTokenId: tokenId1, + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgAttach", + value: msg, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + assertIsDeliverTxSuccess(result); + } + + // Approve + { + const msg: MsgApprove = { + contractId: contractId, + approver: toAddr, + proxy: owner, + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.collection.v1.MsgApprove", + value: msg, + }; + const result = await client.signAndBroadcast(toAddr, [msgAny], defaultFee); + assertIsDeliverTxSuccess(result); + } + + await sleep(75); + + client.disconnect(); + } + }); + + describe("query", () => { + it("contract", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const contract = await client.collection.contract(contractId); + expect(contract).toBeDefined(); + expect(contract.contractId).toEqual(contractId); + expect(contract.name).toEqual(contractName); + expect(contract.meta).toEqual("Test NFT Contract"); + expect(contract.baseImgUri).toEqual(contractBaseImgUrl); + + tmClient.disconnect(); + }); + it("tokenClassTypeName", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenId1, "Mission token Id1"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + const classId = tokenId1.substr(0, 8); + const name = await client.collection.tokenClassTypeName(contractId, classId); + expect(name).toEqual("lbm.collection.v1.NFTClass"); + + tmClient.disconnect(); + }); + it("tokenTypes", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenType, "Mission token type"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const types = await client.collection.tokenTypes(contractId); + expect(types.length).toEqual(1); + expect(types[0].contractId).toEqual(contractId); + expect(types[0].tokenType).toEqual(tokenType); + expect(types[0].name).toEqual(tokenName); + expect(types[0].meta).toEqual("Test Meta data"); + + tmClient.disconnect(); + }); + it("tokesWithTokenType", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenType, "Mission token type"); + assert(tokenId1, "Mission token Id1"); + assert(tokenId2, "Mission token Id1"); + assert(tokenId3, "Mission token Id3"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const tokens = await client.collection.tokensWithTokenType(contractId, tokenType); + expect(tokens.length).toEqual(3); + expect(tokens[0].typeUrl).toEqual("/lbm.collection.v1.OwnerNFT"); + expect(tokens[1].typeUrl).toEqual("/lbm.collection.v1.OwnerNFT"); + expect(tokens[2].typeUrl).toEqual("/lbm.collection.v1.OwnerNFT"); + let nft = OwnerNFT.decode(tokens[0].value); + expect(nft.contractId).toEqual(contractId); + expect(nft.tokenId).toEqual(tokenId1); + expect(nft.name).toEqual("Minted TestToken 1"); + expect(nft.meta).toEqual("Test NFT 1"); + expect(nft.owner).toEqual(owner); + nft = OwnerNFT.decode(tokens[1].value); + expect(nft.contractId).toEqual(contractId); + expect(nft.tokenId).toEqual(tokenId2); + expect(nft.name).toEqual("Minted TestToken 2"); + expect(nft.meta).toEqual("Test NFT 2"); + expect(nft.owner).toEqual(toAddr); + nft = OwnerNFT.decode(tokens[2].value); + expect(nft.contractId).toEqual(contractId); + expect(nft.tokenId).toEqual(tokenId3); + expect(nft.name).toEqual("Minted TestToken 3"); + expect(nft.meta).toEqual("Test NFT 3"); + expect(nft.owner).toEqual(owner); + + tmClient.disconnect(); + }); + it("token", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenId1, "Mission token Id1"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const token = await client.collection.token(contractId, tokenId1); + expect(token.typeUrl).toEqual("/lbm.collection.v1.OwnerNFT"); + const ownerNFT = OwnerNFT.decode(token.value); + expect(ownerNFT.contractId).toEqual(contractId); + expect(ownerNFT.tokenId).toEqual(tokenId1); + expect(ownerNFT.name).toEqual("Minted TestToken 1"); + expect(ownerNFT.meta).toEqual("Test NFT 1"); + expect(ownerNFT.owner).toEqual(owner); + + tmClient.disconnect(); + }); + it("nftSupply, nftMinted, nftBurnt", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenType, "Mission token type"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const supply = await client.collection.nftSupply(contractId, tokenType); + expect(supply).toBeDefined(); + expect(supply).toEqual("3"); + + const minted = await client.collection.nftMinted(contractId, tokenType); + expect(minted).toEqual("3"); + + const burnt = await client.collection.nftBurnt(contractId, tokenType); + expect(burnt).toEqual("0"); + + tmClient.disconnect(); + }); + it("root", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenType, "Mission token type"); + assert(tokenId1, "Mission token Id"); + assert(tokenId3, "Mission token Id3"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + let nft = await client.collection.root(contractId, tokenId1); + expect(nft.id).toEqual(tokenId1); + + nft = await client.collection.root(contractId, tokenId3); + expect(nft.id).toEqual(tokenId1); + + tmClient.disconnect(); + }); + it("parent", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenType, "Mission token type"); + assert(tokenId1, "Mission token Id"); + assert(tokenId3, "Mission token Id3"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + try { + await client.collection.parent(contractId, tokenId1); + } catch (err) {} + + const nft = await client.collection.parent(contractId, tokenId3); + expect(nft.id).toEqual(tokenId1); + + tmClient.disconnect(); + }); + it("children", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenType, "Mission token type"); + assert(tokenId1, "Mission token Id"); + assert(tokenId3, "Mission token Id3"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const nfts = await client.collection.children(contractId, tokenId1); + expect(nfts.length).toEqual(1); + expect(nfts[0].id).toEqual(tokenId3); + + try { + await client.collection.children(contractId, tokenId3); + } catch (err) {} + + tmClient.disconnect(); + }); + it("granteeGrants", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenType, "Mission token type"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + let grants = await client.collection.granteeGrants(contractId, owner); + expect(grants.length).toEqual(4); + + grants = await client.collection.granteeGrants(contractId, toAddr); + expect(grants.length).toEqual(0); + + tmClient.disconnect(); + }); + it("approved", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenType, "Mission token type"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + let approved = await client.collection.approved(contractId, owner, toAddr); + expect(approved).toBeTrue(); + + approved = await client.collection.approved(contractId, toAddr, owner); + expect(approved).toBeFalse(); + + tmClient.disconnect(); + }); + it("approvers", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + assert(tokenType, "Mission token type"); + const [client, tmClient] = await makeClientWithCollection(simapp.tendermintUrl); + + const approvers = await client.collection.approvers(contractId, owner); + expect(approvers.length).toEqual(1); + expect(approvers[0]).toEqual(toAddr); + + tmClient.disconnect(); + }); + }); +}); diff --git a/packages/finschia/src/modules/collection/queries.ts b/packages/finschia/src/modules/collection/queries.ts new file mode 100644 index 000000000..8dc3de40e --- /dev/null +++ b/packages/finschia/src/modules/collection/queries.ts @@ -0,0 +1,158 @@ +import { createProtobufRpcClient, QueryClient } from "@cosmjs/stargate"; +import { assert } from "@cosmjs/utils"; +import { Any } from "cosmjs-types/google/protobuf/any"; +import { Coin, Contract, Grant, NFT, TokenType } from "lbmjs-types/lbm/collection/v1/collection"; +import { QueryClientImpl } from "lbmjs-types/lbm/collection/v1/query"; + +export interface CollectionExtension { + readonly collection: { + readonly balance: (contractId: string, address: string, tokenId: string) => Promise; + readonly allBalances: (contractId: string, address: string) => Promise; // Since 0.46.0 + readonly ftSupply: (contractId: string, tokenId: string) => Promise; + readonly ftMinted: (contractId: string, tokenId: string) => Promise; + readonly ftBurnt: (contractId: string, tokenId: string) => Promise; + readonly nftSupply: (contractId: string, tokenType: string) => Promise; + readonly nftMinted: (contractId: string, tokenType: string) => Promise; + readonly nftBurnt: (contractId: string, tokenType: string) => Promise; + readonly contract: (contractId: string) => Promise; + readonly tokenClassTypeName: (contractId: string, classId: string) => Promise; + readonly tokenType: (contractId: string, tokenType: string) => Promise; + readonly tokenTypes: (contractId: string) => Promise; + readonly token: (contractId: string, tokenId: string) => Promise; + readonly tokensWithTokenType: (contractId: string, tokenType: string) => Promise; + readonly tokens: (contractId: string) => Promise; + readonly root: (contractId: string, tokenId: string) => Promise; + readonly parent: (contractId: string, tokenId: string) => Promise; + readonly children: (contractId: string, tokenId: string) => Promise; + readonly granteeGrants: (contractId: string, grantee: string) => Promise; // Since 0.46.0 + readonly approved: (contractId: string, address: string, approver: string) => Promise; + readonly approvers: (contractId: string, address: string) => Promise; + }; +} + +export function setupCollectionExtension(base: QueryClient): CollectionExtension { + const rpc = createProtobufRpcClient(base); + // Use this service to get easy typed access to query methods + // This cannot be used for proof verification + const queryService = new QueryClientImpl(rpc); + + return { + collection: { + balance: async (contractId: string, address: string, tokenId: string) => { + const { balance } = await queryService.Balance({ + contractId: contractId, + address: address, + tokenId: tokenId, + }); + assert(balance); + return balance; + }, + allBalances: async (contractId: string, address: string) => { + const { balances } = await queryService.AllBalances({ contractId: contractId, address: address }); + assert(balances); + return balances; + }, + ftSupply: async (contractId: string, tokenId: string) => { + const { supply } = await queryService.FTSupply({ contractId: contractId, tokenId: tokenId }); + assert(supply); + return supply; + }, + ftMinted: async (contractId: string, tokenId: string) => { + const { minted } = await queryService.FTMinted({ contractId: contractId, tokenId: tokenId }); + assert(minted); + return minted; + }, + ftBurnt: async (contractId: string, tokenId: string) => { + const { burnt } = await queryService.FTBurnt({ contractId: contractId, tokenId: tokenId }); + assert(burnt); + return burnt; + }, + nftSupply: async (contractId: string, tokenType: string) => { + const { supply } = await queryService.NFTSupply({ contractId: contractId, tokenType: tokenType }); + assert(supply); + return supply; + }, + nftMinted: async (contractId: string, tokenType: string) => { + const { minted } = await queryService.NFTMinted({ contractId: contractId, tokenType: tokenType }); + assert(minted); + return minted; + }, + nftBurnt: async (contractId: string, tokenType: string) => { + const { burnt } = await queryService.NFTBurnt({ contractId: contractId, tokenType: tokenType }); + assert(burnt); + return burnt; + }, + contract: async (contractId: string) => { + const { contract } = await queryService.Contract({ contractId: contractId }); + assert(contract); + return contract; + }, + tokenClassTypeName: async (contractId: string, classId: string) => { + const { name } = await queryService.TokenClassTypeName({ contractId, classId }); + assert(name); + return name; + }, + tokenType: async (contractId: string, sTokenType: string) => { + const { tokenType } = await queryService.TokenType({ contractId: contractId, tokenType: sTokenType }); + assert(tokenType); + return tokenType; + }, + tokenTypes: async (contractId: string) => { + const { tokenTypes } = await queryService.TokenTypes({ contractId: contractId }); + return tokenTypes; + }, + token: async (contractId: string, tokenId: string) => { + const { token } = await queryService.Token({ contractId: contractId, tokenId: tokenId }); + assert(token); + // let ret: FT | NFT | null; + // if (token.typeUrl == "/lbm.collection.v1.FT") { + // ret = FT.decode(token.value); + // } else if (token.typeUrl == "/lbm.collection.v1.NFT") { + // ret = NFT.decode(token.value); + // } + return token; + }, + tokensWithTokenType: async (contractId: string, tokenType: string) => { + const { tokens } = await queryService.TokensWithTokenType({ + contractId: contractId, + tokenType: tokenType, + }); + return tokens; + }, + tokens: async (contractId: string) => { + const { tokens } = await queryService.Tokens({ contractId: contractId }); + return tokens; + }, + root: async (contractId: string, tokenId: string) => { + const { root } = await queryService.Root({ contractId: contractId, tokenId: tokenId }); + assert(root); + return root; + }, + parent: async (contractId: string, tokenId: string) => { + const { parent } = await queryService.Parent({ contractId: contractId, tokenId: tokenId }); + assert(parent); + return parent; + }, + children: async (contractId: string, tokenId: string) => { + const { children } = await queryService.Children({ contractId: contractId, tokenId: tokenId }); + return children; + }, + granteeGrants: async (contractId: string, grantee: string) => { + const { grants } = await queryService.GranteeGrants({ contractId: contractId, grantee: grantee }); + return grants; + }, + approved: async (contractId: string, address: string, approver: string) => { + const { approved } = await queryService.Approved({ + contractId: contractId, + address: address, + approver: approver, + }); + return approved; + }, + approvers: async (contractId: string, address: string) => { + const { approvers } = await queryService.Approvers({ contractId: contractId, address: address }); + return approvers; + }, + }, + }; +} diff --git a/packages/finschia/src/modules/collection/utils.ts b/packages/finschia/src/modules/collection/utils.ts new file mode 100644 index 000000000..bcb8be00b --- /dev/null +++ b/packages/finschia/src/modules/collection/utils.ts @@ -0,0 +1,28 @@ +import { Uint53 } from "@cosmjs/math"; +import { Coin } from "lbmjs-types/lbm/collection/v1/collection"; + +export function ftCoin(amount: number | string, tokenId: string): Coin { + let outAmount: string; + if (typeof amount === "number") { + try { + outAmount = new Uint53(amount).toString(); + } catch (_err) { + throw new Error( + "Given amount is not a safe integer. Consider using a string instead to overcome the limitations of JS numbers.", + ); + } + } else { + if (!amount.match(/^[0-9]+$/)) { + throw new Error("Invalid unsigned integer string format"); + } + outAmount = amount.replace(/^0*/, "") || "0"; + } + return { + amount: outAmount, + tokenId: tokenId, + }; +} + +export function ftCoins(amount: number | string, tokenId: string): Coin[] { + return [ftCoin(amount, tokenId)]; +} diff --git a/packages/finschia/src/modules/evidence/aminomessages.ts b/packages/finschia/src/modules/evidence/aminomessages.ts new file mode 100644 index 000000000..7542f01a8 --- /dev/null +++ b/packages/finschia/src/modules/evidence/aminomessages.ts @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { AminoMsg } from "@cosmjs/amino"; +import { AminoConverters } from "@cosmjs/stargate"; + +// See https://github.com/cosmos/cosmos-sdk/blob/v0.45.1/proto/cosmos/evidence/v1beta1/tx.proto + +interface Any { + readonly type_url: string; + readonly value: Uint8Array; +} + +/** Supports submitting arbitrary evidence */ +export interface AminoMsgSubmitEvidence extends AminoMsg { + readonly type: "cosmos-sdk/MsgSubmitEvidence"; + readonly value: { + /** Bech32 account address */ + readonly submitter: string; + readonly evidence: Any; + }; +} + +export function isAminoMsgSubmitEvidence(msg: AminoMsg): msg is AminoMsgSubmitEvidence { + return msg.type === "cosmos-sdk/MsgSubmitEvidence"; +} + +export function createEvidenceAminoConverters(): AminoConverters { + throw new Error("Not implemented"); +} diff --git a/packages/finschia/src/modules/evidence/queries.spec.ts b/packages/finschia/src/modules/evidence/queries.spec.ts new file mode 100644 index 000000000..27c9151a2 --- /dev/null +++ b/packages/finschia/src/modules/evidence/queries.spec.ts @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { QueryClient } from "@cosmjs/stargate"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; + +import { pendingWithoutSimapp, simapp } from "../../testutils.spec"; +import { EvidenceExtension, setupEvidenceExtension } from "./queries"; + +async function makeClientWithEvidence( + rpcUrl: string, +): Promise<[QueryClient & EvidenceExtension, Tendermint34Client]> { + const tmClient = await Tendermint34Client.connect(rpcUrl); + return [QueryClient.withExtensions(tmClient, setupEvidenceExtension), tmClient]; +} + +describe("EvidenceExtension", () => { + describe("AllEvidence", () => { + it("works", async () => { + pendingWithoutSimapp(); + const [client, tmClient] = await makeClientWithEvidence(simapp.tendermintUrl); + + const response = await client.evidence.allEvidence(); + expect(response.evidence).toBeDefined(); + expect(response.evidence).not.toBeNull(); + + tmClient.disconnect(); + }); + }); +}); diff --git a/packages/finschia/src/modules/evidence/queries.ts b/packages/finschia/src/modules/evidence/queries.ts new file mode 100644 index 000000000..a6fb97e71 --- /dev/null +++ b/packages/finschia/src/modules/evidence/queries.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { createPagination, createProtobufRpcClient, QueryClient } from "@cosmjs/stargate"; +import { + QueryAllEvidenceResponse, + QueryClientImpl, + QueryEvidenceResponse, +} from "cosmjs-types/cosmos/evidence/v1beta1/query"; + +export interface EvidenceExtension { + readonly evidence: { + evidence: (evidenceHash: Uint8Array) => Promise; + allEvidence: (paginationKey?: Uint8Array) => Promise; + }; +} + +export function setupEvidenceExtension(base: QueryClient): EvidenceExtension { + const rpc = createProtobufRpcClient(base); + // Use this service to get easy typed access to query methods + // This cannot be used for proof verification + const queryService = new QueryClientImpl(rpc); + + return { + evidence: { + evidence: async (evidenceHash: Uint8Array) => { + return await queryService.Evidence({ evidenceHash: evidenceHash }); + }, + allEvidence: async (paginationKey?: Uint8Array) => { + return await queryService.AllEvidence({ pagination: createPagination(paginationKey) }); + }, + }, + }; +} diff --git a/packages/finschia/src/modules/feegrant/aminomessages.ts b/packages/finschia/src/modules/feegrant/aminomessages.ts new file mode 100644 index 000000000..7f30bf579 --- /dev/null +++ b/packages/finschia/src/modules/feegrant/aminomessages.ts @@ -0,0 +1,8 @@ +import { AminoConverters } from "@cosmjs/stargate"; + +export function createFreegrantAminoConverters(): AminoConverters { + return { + "/cosmos.feegrant.v1beta1.MsgGrantAllowance": "not_supported_by_chain", + "/cosmos.feegrant.v1beta1.MsgRevokeAllowance": "not_supported_by_chain", + }; +} diff --git a/packages/finschia/src/modules/feegrant/messages.ts b/packages/finschia/src/modules/feegrant/messages.ts new file mode 100644 index 000000000..8821d4d96 --- /dev/null +++ b/packages/finschia/src/modules/feegrant/messages.ts @@ -0,0 +1,7 @@ +import { GeneratedType } from "@cosmjs/proto-signing"; +import { MsgGrantAllowance, MsgRevokeAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/tx"; + +export const feegrantTypes: ReadonlyArray<[string, GeneratedType]> = [ + ["/cosmos.feegrant.v1beta1.MsgGrantAllowance", MsgGrantAllowance], + ["/cosmos.feegrant.v1beta1.MsgRevokeAllowance", MsgRevokeAllowance], +]; diff --git a/packages/finschia/src/modules/feegrant/queries.spec.ts b/packages/finschia/src/modules/feegrant/queries.spec.ts new file mode 100644 index 000000000..b951bac97 --- /dev/null +++ b/packages/finschia/src/modules/feegrant/queries.spec.ts @@ -0,0 +1,99 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { coins } from "@cosmjs/amino"; +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; +import { assertIsDeliverTxSuccess, QueryClient } from "@cosmjs/stargate"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { sleep } from "@cosmjs/utils"; +import { BasicAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/feegrant"; + +import { FinschiaClient } from "../../finschiaClient"; +import { makeLinkPath } from "../../paths"; +import { + defaultSigningClientOptions, + faucet, + makeRandomAddress, + pendingWithoutSimapp, + simapp, + simappEnabled, +} from "../../testutils.spec"; +import { FeeGrantExtension, setupFeeGrantExtension } from "./queries"; + +async function makeClientWithFeeGrant( + rpcUrl: string, +): Promise<[QueryClient & FeeGrantExtension, Tendermint34Client]> { + const tmClient = await Tendermint34Client.connect(rpcUrl); + return [QueryClient.withExtensions(tmClient, setupFeeGrantExtension), tmClient]; +} + +describe("FeeGrantExtension", () => { + const defaultFee = { + amount: coins(25000, "cony"), + gas: "1500000", // 1.5 million + }; + const granterAddress = faucet.address0; + const granteeAddress = makeRandomAddress(); + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await FinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + const msg = { + typeUrl: "/cosmos.feegrant.v1beta1.MsgGrantAllowance", + value: { + granter: granterAddress, + grantee: granteeAddress, + allowance: { + typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance", + value: BasicAllowance.encode({ spendLimit: [] }).finish(), + }, + }, + }; + const grantResult = await client.signAndBroadcast( + granterAddress, + [msg], + defaultFee, + "Test feegrant for simd", + ); + assertIsDeliverTxSuccess(grantResult); + + await sleep(75); + + client.disconnect(); + } + }); + + describe("allowance", () => { + it("work", async () => { + pendingWithoutSimapp(); + const [client, tmClient] = await makeClientWithFeeGrant(simapp.tendermintUrl); + + const response = await client.feegrant.allowance(granterAddress, granteeAddress); + expect(response.allowance).toBeDefined(); + expect(response.allowance).not.toBeNull(); + + tmClient.disconnect(); + }); + }); + + describe("allowances", () => { + it("work", async () => { + pendingWithoutSimapp(); + const [client, tmClient] = await makeClientWithFeeGrant(simapp.tendermintUrl); + + const response = await client.feegrant.allowances(granteeAddress); + expect(response.allowances).toBeDefined(); + expect(response.allowances).not.toBeNull(); + expect(response.allowances.length).toBeGreaterThanOrEqual(1); + + tmClient.disconnect(); + }); + }); +}); diff --git a/packages/finschia/src/modules/feegrant/queries.ts b/packages/finschia/src/modules/feegrant/queries.ts new file mode 100644 index 000000000..5e895b194 --- /dev/null +++ b/packages/finschia/src/modules/feegrant/queries.ts @@ -0,0 +1,40 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { createPagination, createProtobufRpcClient, QueryClient } from "@cosmjs/stargate"; +import { QueryAllowanceResponse, QueryAllowancesResponse } from "cosmjs-types/cosmos/feegrant/v1beta1/query"; +import { QueryAllowancesByGranterResponse, QueryClientImpl } from "lbmjs-types/cosmos/feegrant/v1beta1/query"; + +export interface FeeGrantExtension { + readonly feegrant: { + readonly allowance: (granter: string, grantee: string) => Promise; + readonly allowances: (grantee: string, paginationKey?: Uint8Array) => Promise; + readonly allowancesByGranter: ( + granter: string, + paginationKey?: Uint8Array, + ) => Promise; + }; +} + +export function setupFeeGrantExtension(base: QueryClient): FeeGrantExtension { + const rpc = createProtobufRpcClient(base); + const queryService = new QueryClientImpl(rpc); + + return { + feegrant: { + allowance: async (granter: string, grantee: string) => { + return await queryService.Allowance({ granter: granter, grantee: grantee }); + }, + allowances: async (grantee: string, paginationKey?: Uint8Array) => { + return await queryService.Allowances({ + grantee: grantee, + pagination: createPagination(paginationKey), + }); + }, + allowancesByGranter: async (granter: string, paginationKey?: Uint8Array) => { + return await queryService.AllowancesByGranter({ + granter, + pagination: createPagination(paginationKey), + }); + }, + }, + }; +} diff --git a/packages/finschia/src/modules/foundation/messages.ts b/packages/finschia/src/modules/foundation/messages.ts new file mode 100644 index 000000000..7329628b8 --- /dev/null +++ b/packages/finschia/src/modules/foundation/messages.ts @@ -0,0 +1,199 @@ +import { Decimal } from "@cosmjs/math"; +import { EncodeObject, GeneratedType } from "@cosmjs/proto-signing"; +import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; +import { Duration } from "cosmjs-types/google/protobuf/duration"; +import { ReceiveFromTreasuryAuthorization } from "lbmjs-types/lbm/foundation/v1/authz"; +import { + DecisionPolicyWindows, + Member, + PercentageDecisionPolicy, + ThresholdDecisionPolicy, +} from "lbmjs-types/lbm/foundation/v1/foundation"; +import { + Exec, + MsgExec, + MsgFundTreasury, + MsgGrant, + MsgLeaveFoundation, + MsgRevoke, + MsgSubmitProposal, + MsgUpdateDecisionPolicy, + MsgUpdateMembers, + MsgVote, + MsgWithdrawFromTreasury, + MsgWithdrawProposal, +} from "lbmjs-types/lbm/foundation/v1/tx"; + +import { longify } from "../../utils"; + +export const foundationTypes: ReadonlyArray<[string, GeneratedType]> = [ + ["/lbm.foundation.v1.MsgFundTreasury", MsgFundTreasury], + ["/lbm.foundation.v1.MsgWithdrawFromTreasury", MsgWithdrawFromTreasury], + ["/lbm.foundation.v1.MsgUpdateMembers", MsgUpdateMembers], + ["/lbm.foundation.v1.MsgUpdateDecisionPolicy", MsgUpdateDecisionPolicy], + ["/lbm.foundation.v1.MsgSubmitProposal", MsgSubmitProposal], + ["/lbm.foundation.v1.MsgWithdrawProposal", MsgWithdrawProposal], + ["/lbm.foundation.v1.MsgVote", MsgVote], + ["/lbm.foundation.v1.MsgExec", MsgExec], + ["/lbm.foundation.v1.MsgLeaveFoundation", MsgLeaveFoundation], + ["/lbm.foundation.v1.MsgGrant", MsgGrant], + ["/lbm.foundation.v1.MsgRevoke", MsgRevoke], + ["/lbm.foundation.v1.ReceiveFromTreasuryAuthorization", ReceiveFromTreasuryAuthorization], + ["/lbm.foundation.v1.DecisionPolicyWindows", DecisionPolicyWindows], + ["/lbm.foundation.v1.ThresholdDecisionPolicy", ThresholdDecisionPolicy], + ["/lbm.foundation.v1.PercentageDecisionPolicy", PercentageDecisionPolicy], +]; + +export function createMsgSubmitProposal( + proposers: string[], + messages: EncodeObject[], + metadata = "", + exec: Exec = Exec.EXEC_TRY, +): EncodeObject { + return { + typeUrl: "/lbm.foundation.v1.MsgSubmitProposal", + value: { + proposers: proposers, + metadata: metadata, + messages: messages, + exec: exec, + }, + }; +} + +export function createMsgGrant(operator: string, grantee: string): EncodeObject { + return { + typeUrl: "/lbm.foundation.v1.MsgGrant", + value: Uint8Array.from( + MsgGrant.encode({ + operator: operator, + grantee: grantee, + authorization: { + typeUrl: "/lbm.foundation.v1.ReceiveFromTreasuryAuthorization", + value: Uint8Array.from(ReceiveFromTreasuryAuthorization.encode({}).finish()), + }, + }).finish(), + ), + }; +} + +export function createMsgRevoke(operator: string, grantee: string, msgTypeUrl: string): EncodeObject { + return { + typeUrl: "/lbm.foundation.v1.MsgRevoke", + value: Uint8Array.from( + MsgRevoke.encode({ + operator: operator, + grantee: grantee, + msgTypeUrl: msgTypeUrl, + }).finish(), + ), + }; +} + +export function createMsgWithdrawFromTreasury( + operator: string, + toAddress: string, + amount: Coin[], +): EncodeObject { + return { + typeUrl: "/lbm.foundation.v1.MsgWithdrawFromTreasury", + value: Uint8Array.from( + MsgWithdrawFromTreasury.encode({ + operator: operator, + to: toAddress, + amount: amount, + }).finish(), + ), + }; +} + +export function createMsgUpdateMembers(operator: string, members: Member[]): EncodeObject { + return { + typeUrl: "/lbm.foundation.v1.MsgUpdateMembers", + value: Uint8Array.from( + MsgUpdateMembers.encode({ + operator: operator, + memberUpdates: members, + }).finish(), + ), + }; +} + +export function createMsgUpdateDecisionPolicy( + operator: string, + decisionPolicy: ThresholdDecisionPolicyEncodeObject | PercentageDecisionPolicyEncodeObject, +): EncodeObject { + return { + typeUrl: "/lbm.foundation.v1.MsgUpdateDecisionPolicy", + value: Uint8Array.from( + MsgUpdateDecisionPolicy.encode({ + operator: operator, + decisionPolicy: decisionPolicy, + }).finish(), + ), + }; +} + +export interface ThresholdDecisionPolicyEncodeObject extends EncodeObject { + readonly typeUrl: "/lbm.foundation.v1.ThresholdDecisionPolicy"; + readonly value: Uint8Array; +} + +export function isThresholdDecisionPolicyEncodeObject( + object: EncodeObject, +): object is ThresholdDecisionPolicyEncodeObject { + return ( + (object as ThresholdDecisionPolicyEncodeObject).typeUrl === "/lbm.foundation.v1.ThresholdDecisionPolicy" + ); +} + +export function createThresholdDecisionPolicy( + threshold: string, + votingPeriod = "86400", + minExecutionPeriod = "0", +): ThresholdDecisionPolicyEncodeObject { + return { + typeUrl: "/lbm.foundation.v1.ThresholdDecisionPolicy", + value: Uint8Array.from( + ThresholdDecisionPolicy.encode({ + threshold: Decimal.fromUserInput(threshold, 18).atomics, + windows: { + votingPeriod: Duration.fromPartial({ seconds: longify(votingPeriod) }), + minExecutionPeriod: Duration.fromPartial({ seconds: longify(minExecutionPeriod) }), + }, + }).finish(), + ), + }; +} + +export interface PercentageDecisionPolicyEncodeObject extends EncodeObject { + readonly typeUrl: "/lbm.foundation.v1.PercentageDecisionPolicy"; + readonly value: Uint8Array; +} + +export function isPercentageDecisionPolicyEncodeObject( + object: EncodeObject, +): object is PercentageDecisionPolicyEncodeObject { + return ( + (object as PercentageDecisionPolicyEncodeObject).typeUrl === "/lbm.foundation.v1.PercentageDecisionPolicy" + ); +} + +export function createPercentageDecisionPolicy( + percentage: string, + votingPeriod = "86400", + minExecutionPeriod = "0", +): PercentageDecisionPolicyEncodeObject { + return { + typeUrl: "/lbm.foundation.v1.PercentageDecisionPolicy", + value: Uint8Array.from( + PercentageDecisionPolicy.encode({ + percentage: Decimal.fromUserInput(percentage, 18).atomics, + windows: { + votingPeriod: Duration.fromPartial({ seconds: longify(votingPeriod) }), + minExecutionPeriod: Duration.fromPartial({ seconds: longify(minExecutionPeriod) }), + }, + }).finish(), + ), + }; +} diff --git a/packages/finschia/src/modules/foundation/queries.spec.ts b/packages/finschia/src/modules/foundation/queries.spec.ts new file mode 100644 index 000000000..331b714e1 --- /dev/null +++ b/packages/finschia/src/modules/foundation/queries.spec.ts @@ -0,0 +1,190 @@ +import { coins } from "@cosmjs/amino"; +import { Decimal } from "@cosmjs/math"; +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; +import { assertIsDeliverTxSuccess, QueryClient } from "@cosmjs/stargate"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { sleep } from "@cosmjs/utils"; +import { ThresholdDecisionPolicy } from "lbmjs-types/lbm/foundation/v1/foundation"; + +import { FinschiaClient } from "../../finschiaClient"; +import { makeLinkPath } from "../../paths"; +import { + defaultSigningClientOptions, + faucet, + pendingWithoutSimapp, + simapp, + simappEnabled, +} from "../../testutils.spec"; +import { + createMsgGrant, + createMsgSubmitProposal, + createMsgUpdateDecisionPolicy, + createMsgWithdrawFromTreasury, + createThresholdDecisionPolicy, + isThresholdDecisionPolicyEncodeObject, +} from "./messages"; +import { FoundationExtension, setupFoundationExtension } from "./queries"; + +async function makeClientWithFoundation( + rpcUrl: string, +): Promise<[QueryClient & FoundationExtension, Tendermint34Client]> { + const tmClient = await Tendermint34Client.connect(rpcUrl); + return [QueryClient.withExtensions(tmClient, setupFoundationExtension), tmClient]; +} + +describe("FoundationExtension", () => { + const defaultFee = { + amount: coins(25000, "cony"), + gas: "1500000", // 1.5 million + }; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await FinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + const msg = { + typeUrl: "/lbm.foundation.v1.MsgFundTreasury", + value: { + from: faucet.address0, + amount: coins(1000, "cony"), + }, + }; + const result = await client.signAndBroadcast(faucet.address0, [msg], defaultFee); + assertIsDeliverTxSuccess(result); + + await sleep(75); + } + }); + + describe("Query Treasury", () => { + it("work", async () => { + pendingWithoutSimapp(); + const [client, tmClient] = await makeClientWithFoundation(simapp.tendermintUrl); + + const result = await client.foundation.treasury(); + expect(result).toBeDefined(); + expect(result).not.toBeNull(); + expect(result.length).toEqual(1); + + tmClient.disconnect(); + }); + }); +}); + +describe("FoundationExtension grant and withdrawFromTreasury", () => { + const defaultFee = { + amount: coins(25000, "cony"), + gas: "1500000", // 1.5 million + }; + const operatorAddress = "link1gx2dzurw686q340q94njwacpnax48pw824tksx"; + const granteeAddress = faucet.address5; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await FinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + // proposal grant + { + const msgGrant = createMsgGrant(operatorAddress, granteeAddress); + const msg = createMsgSubmitProposal([faucet.address0], [msgGrant]); + const result = await client.signAndBroadcast(faucet.address0, [msg], defaultFee); + assertIsDeliverTxSuccess(result); + + await sleep(75); + } + + // proposal withdrawFromTreasury + { + const msgWithdraw = createMsgWithdrawFromTreasury( + operatorAddress, + granteeAddress, + coins(100, "cony"), + ); + const msg = createMsgSubmitProposal([faucet.address0], [msgWithdraw]); + const result = await client.signAndBroadcast(faucet.address0, [msg], defaultFee); + assertIsDeliverTxSuccess(result); + + await sleep(75); + } + } + }); + + describe("Query result of Grant", () => { + it("grant", async () => { + pendingWithoutSimapp(); + const [client, tmClient] = await makeClientWithFoundation(simapp.tendermintUrl); + + const response = await client.foundation.grants("link14nvvrk4dz3k695t8740vqzjnvrwszwm69hw0ls", ""); + expect(response.authorizations).toBeDefined(); + expect(response.authorizations).not.toBeNull(); + expect(response.authorizations.length).toEqual(1); + + tmClient.disconnect(); + }); + }); +}); + +describe("FoundationExtension DecisionPolicy", () => { + const defaultFee = { + amount: coins(25000, "cony"), + gas: "1500000", // 1.5 million + }; + const operatorAddress = "link1gx2dzurw686q340q94njwacpnax48pw824tksx"; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await FinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + // proposal updateDicisionPolicy + const decisionPolisy = createThresholdDecisionPolicy("5"); + const msgDecisionPolicy = createMsgUpdateDecisionPolicy(operatorAddress, decisionPolisy); + const msg = createMsgSubmitProposal([faucet.address0], [msgDecisionPolicy]); + const result = await client.signAndBroadcast(faucet.address0, [msg], defaultFee); + assertIsDeliverTxSuccess(result); + + await sleep(75); + } + }); + + describe("Query decisionPolicy", () => { + it("work", async () => { + pendingWithoutSimapp(); + const [client, tmClient] = await makeClientWithFoundation(simapp.tendermintUrl); + + const foundationInfo = await client.foundation.foundationInfo(); + expect(foundationInfo).toBeDefined(); + expect(foundationInfo).not.toBeNull(); + expect(isThresholdDecisionPolicyEncodeObject(foundationInfo!.decisionPolicy!)).toBeTrue(); + + // check threshold + const thresholdDecisionPolicy = ThresholdDecisionPolicy.decode(foundationInfo!.decisionPolicy!.value); + expect(Decimal.fromAtomics(thresholdDecisionPolicy.threshold, 18).toString()).toEqual("5"); + + tmClient.disconnect(); + }); + }); +}); diff --git a/packages/finschia/src/modules/foundation/queries.ts b/packages/finschia/src/modules/foundation/queries.ts new file mode 100644 index 000000000..ad208edf0 --- /dev/null +++ b/packages/finschia/src/modules/foundation/queries.ts @@ -0,0 +1,99 @@ +import { Uint64 } from "@cosmjs/math"; +import { createPagination, createProtobufRpcClient, QueryClient } from "@cosmjs/stargate"; +import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; +import { + FoundationInfo, + Member, + Proposal, + TallyResult, + Vote, +} from "lbmjs-types/lbm/foundation/v1/foundation"; +import { + QueryClientImpl, + QueryGrantsResponse, + QueryMembersResponse, + QueryProposalsResponse, + QueryVotesResponse, +} from "lbmjs-types/lbm/foundation/v1/query"; + +import { longify } from "../../utils"; + +export type FoundationProposalId = string | number | Long | Uint64; + +export interface FoundationExtension { + readonly foundation: { + readonly treasury: () => Promise; + readonly foundationInfo: () => Promise; + readonly member: (address: string) => Promise; + readonly members: (paginationKey?: Uint8Array) => Promise; + readonly proposal: (proposalId: FoundationProposalId) => Promise; + readonly proposals: (paginationKey?: Uint8Array) => Promise; + readonly vote: (proposalId: FoundationProposalId, voter: string) => Promise; + readonly votes: ( + proposalId: FoundationProposalId, + paginationKey?: Uint8Array, + ) => Promise; + readonly tallyResult: (proposalId: FoundationProposalId) => Promise; + readonly grants: ( + grantee: string, + msgTypeUrl: string, + paginationKey?: Uint8Array, + ) => Promise; + }; +} + +export function setupFoundationExtension(base: QueryClient): FoundationExtension { + const rpc = createProtobufRpcClient(base); + + // Use this service to get easy typed access to query methods + // This cannot be used for proof verification + const queryService = new QueryClientImpl(rpc); + + return { + foundation: { + treasury: async () => { + const response = await queryService.Treasury({}); + return response.amount; + }, + foundationInfo: async () => { + const response = await queryService.FoundationInfo({}); + return response.info; + }, + member: async (address: string) => { + const response = await queryService.Member({ address: address }); + return response.member; + }, + members: async (paginationKey?: Uint8Array) => { + return await queryService.Members({ pagination: createPagination(paginationKey) }); + }, + proposal: async (proposalId: FoundationProposalId) => { + const response = await queryService.Proposal({ proposalId: longify(proposalId) }); + return response.proposal; + }, + proposals: async (paginationKey?: Uint8Array) => { + return await queryService.Proposals({ pagination: createPagination(paginationKey) }); + }, + vote: async (proposalId: FoundationProposalId, voter: string) => { + const response = await queryService.Vote({ proposalId: longify(proposalId), voter: voter }); + return response.vote; + }, + votes: async (proposalId: FoundationProposalId, paginationKey?: Uint8Array) => { + return await queryService.Votes({ + proposalId: longify(proposalId), + pagination: createPagination(paginationKey), + }); + }, + tallyResult: async (proposalId: FoundationProposalId) => { + const response = await queryService.TallyResult({ proposalId: longify(proposalId) }); + return response.tally; + }, + grants: async (grantee: string, msgTypeUrl: string, paginationKey?: Uint8Array) => { + return await queryService.Grants({ + grantee: grantee, + msgTypeUrl: msgTypeUrl, + pagination: createPagination(paginationKey), + }); + }, + }, + }; +} diff --git a/packages/finschia/src/modules/ibc/aminomessages.spec.ts b/packages/finschia/src/modules/ibc/aminomessages.spec.ts new file mode 100644 index 000000000..8a2835eeb --- /dev/null +++ b/packages/finschia/src/modules/ibc/aminomessages.spec.ts @@ -0,0 +1,184 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { coin } from "@cosmjs/proto-signing"; +import { AminoTypes } from "@cosmjs/stargate"; +import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; +import Long from "long"; + +import { AminoMsgTransfer, createIbcAminoConverters } from "./aminomessages"; + +describe("AminoTypes", () => { + describe("toAmino", () => { + it("works for MsgTransfer", () => { + const msg: MsgTransfer = { + sourcePort: "testport", + sourceChannel: "testchannel", + token: coin(1234, "utest"), + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + timeoutHeight: { + revisionHeight: Long.fromString("123", true), + revisionNumber: Long.fromString("456", true), + }, + timeoutTimestamp: Long.fromString("789", true), + }; + const aminoTypes = new AminoTypes(createIbcAminoConverters()); + const aminoMsg = aminoTypes.toAmino({ + typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", + value: msg, + }); + const expected: AminoMsgTransfer = { + type: "cosmos-sdk/MsgTransfer", + value: { + source_port: "testport", + source_channel: "testchannel", + token: coin(1234, "utest"), + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + timeout_height: { + revision_height: "123", + revision_number: "456", + }, + timeout_timestamp: "789", + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgTransfer with empty values", () => { + const msg: MsgTransfer = { + sourcePort: "testport", + sourceChannel: "testchannel", + token: coin(1234, "utest"), + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + timeoutHeight: { + revisionHeight: Long.UZERO, + revisionNumber: Long.UZERO, + }, + timeoutTimestamp: Long.UZERO, + }; + const aminoTypes = new AminoTypes(createIbcAminoConverters()); + const aminoMsg = aminoTypes.toAmino({ + typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", + value: msg, + }); + const expected: AminoMsgTransfer = { + type: "cosmos-sdk/MsgTransfer", + value: { + source_port: "testport", + source_channel: "testchannel", + token: coin(1234, "utest"), + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + timeout_height: { + revision_height: undefined, + revision_number: undefined, + }, + timeout_timestamp: undefined, + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgTransfer with no height timeout", () => { + const msg: MsgTransfer = { + sourcePort: "testport", + sourceChannel: "testchannel", + token: coin(1234, "utest"), + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + timeoutHeight: undefined, + timeoutTimestamp: Long.UZERO, + }; + const aminoMsg = new AminoTypes(createIbcAminoConverters()).toAmino({ + typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", + value: msg, + }); + const expected: AminoMsgTransfer = { + type: "cosmos-sdk/MsgTransfer", + value: { + source_port: "testport", + source_channel: "testchannel", + token: coin(1234, "utest"), + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + timeout_height: {}, + timeout_timestamp: undefined, + }, + }; + expect(aminoMsg).toEqual(expected); + }); + }); + + describe("fromAmino", () => { + it("works for MsgTransfer", () => { + const aminoMsg: AminoMsgTransfer = { + type: "cosmos-sdk/MsgTransfer", + value: { + source_port: "testport", + source_channel: "testchannel", + token: coin(1234, "utest"), + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + timeout_height: { + revision_height: "123", + revision_number: "456", + }, + timeout_timestamp: "789", + }, + }; + const msg = new AminoTypes(createIbcAminoConverters()).fromAmino(aminoMsg); + const expectedValue: MsgTransfer = { + sourcePort: "testport", + sourceChannel: "testchannel", + token: coin(1234, "utest"), + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + timeoutHeight: { + revisionHeight: Long.fromString("123", true), + revisionNumber: Long.fromString("456", true), + }, + timeoutTimestamp: Long.fromString("789", true), + }; + expect(msg).toEqual({ + typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", + value: expectedValue, + }); + }); + + it("works for MsgTransfer with default values", () => { + const aminoMsg: AminoMsgTransfer = { + type: "cosmos-sdk/MsgTransfer", + value: { + source_port: "testport", + source_channel: "testchannel", + token: coin(1234, "utest"), + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + timeout_height: { + // revision_height omitted + // revision_number omitted + }, + // timeout_timestamp omitted + }, + }; + const msg = new AminoTypes(createIbcAminoConverters()).fromAmino(aminoMsg); + const expectedValue: MsgTransfer = { + sourcePort: "testport", + sourceChannel: "testchannel", + token: coin(1234, "utest"), + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + timeoutHeight: { + revisionHeight: Long.UZERO, + revisionNumber: Long.UZERO, + }, + timeoutTimestamp: Long.UZERO, + }; + expect(msg).toEqual({ + typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", + value: expectedValue, + }); + }); + }); +}); diff --git a/packages/finschia/src/modules/ibc/aminomessages.ts b/packages/finschia/src/modules/ibc/aminomessages.ts new file mode 100644 index 000000000..141555c6d --- /dev/null +++ b/packages/finschia/src/modules/ibc/aminomessages.ts @@ -0,0 +1,113 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { AminoMsg, Coin } from "@cosmjs/amino"; +import { AminoConverters } from "@cosmjs/stargate"; +import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; +import Long from "long"; + +// https://github.com/cosmos/ibc-go/blob/07b6a97b67d17fd214a83764cbdb2c2c3daef445/modules/core/02-client/types/client.pb.go#L297-L312 +interface AminoHeight { + /** 0 values must be omitted (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/x/ibc/core/02-client/types/client.pb.go#L252). */ + readonly revision_number?: string; + /** 0 values must be omitted (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/x/ibc/core/02-client/types/client.pb.go#L254). */ + readonly revision_height?: string; +} + +// https://github.com/cosmos/ibc-go/blob/07b6a97b67d17fd214a83764cbdb2c2c3daef445/modules/apps/transfer/types/tx.pb.go#L33-L53 +/** Transfers fungible tokens (i.e Coins) between ICS20 enabled chains */ +export interface AminoMsgTransfer extends AminoMsg { + readonly type: "cosmos-sdk/MsgTransfer"; + readonly value: { + readonly source_port: string; + readonly source_channel: string; + readonly token?: Coin; + /** Bech32 account address */ + readonly sender: string; + /** Bech32 account address */ + readonly receiver: string; + /** + * The timeout as a (revision_number, revision_height) pair. + * + * This fied is is non-optional (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/x/ibc/applications/transfer/types/tx.pb.go#L49). + * In order to not set the timeout height, set it to {}. + */ + readonly timeout_height: AminoHeight; + /** + * Timeout timestamp in nanoseconds since Unix epoch. The timeout is disabled when set to 0. + * + * 0 values must be omitted (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/x/ibc/applications/transfer/types/tx.pb.go#L52). + */ + readonly timeout_timestamp?: string; + }; +} + +export function isAminoMsgTransfer(msg: AminoMsg): msg is AminoMsgTransfer { + return msg.type === "cosmos-sdk/MsgTransfer"; +} + +function omitDefault(input: T): T | undefined { + if (typeof input === "string") { + return input === "" ? undefined : input; + } + + if (typeof input === "number") { + return input === 0 ? undefined : input; + } + + if (Long.isLong(input)) { + return input.isZero() ? undefined : input; + } + + throw new Error(`Got unsupported type '${typeof input}'`); +} + +export function createIbcAminoConverters(): AminoConverters { + return { + "/ibc.applications.transfer.v1.MsgTransfer": { + aminoType: "cosmos-sdk/MsgTransfer", + toAmino: ({ + sourcePort, + sourceChannel, + token, + sender, + receiver, + timeoutHeight, + timeoutTimestamp, + }: MsgTransfer): AminoMsgTransfer["value"] => ({ + source_port: sourcePort, + source_channel: sourceChannel, + token: token, + sender: sender, + receiver: receiver, + timeout_height: timeoutHeight + ? { + revision_height: omitDefault(timeoutHeight.revisionHeight)?.toString(), + revision_number: omitDefault(timeoutHeight.revisionNumber)?.toString(), + } + : {}, + timeout_timestamp: omitDefault(timeoutTimestamp)?.toString(), + }), + fromAmino: ({ + source_port, + source_channel, + token, + sender, + receiver, + timeout_height, + timeout_timestamp, + }: AminoMsgTransfer["value"]): MsgTransfer => ({ + sourcePort: source_port, + sourceChannel: source_channel, + token: token, + sender: sender, + receiver: receiver, + timeoutHeight: timeout_height + ? { + revisionHeight: Long.fromString(timeout_height.revision_height || "0", true), + revisionNumber: Long.fromString(timeout_height.revision_number || "0", true), + } + : undefined, + timeoutTimestamp: Long.fromString(timeout_timestamp || "0", true), + }), + }, + }; +} diff --git a/packages/finschia/src/modules/ibc/ibctestdata.spec.ts b/packages/finschia/src/modules/ibc/ibctestdata.spec.ts new file mode 100644 index 000000000..1ee5eb836 --- /dev/null +++ b/packages/finschia/src/modules/ibc/ibctestdata.spec.ts @@ -0,0 +1,112 @@ +import { fromBase64 } from "@cosmjs/encoding"; +import { + Channel, + Counterparty as ChannelCounterparty, + IdentifiedChannel, + Order, + PacketState, + State as ChannelState, +} from "cosmjs-types/ibc/core/channel/v1/channel"; +import { MerklePrefix } from "cosmjs-types/ibc/core/commitment/v1/commitment"; +import { + ConnectionEnd, + Counterparty as ConnectionCounterparty, + IdentifiedConnection, + State as ConnectionState, + Version, +} from "cosmjs-types/ibc/core/connection/v1/connection"; +import Long from "long"; + +// From scripts/simapp42/genesis-ibc.json + +export const portId = "transfer"; +export const channelId = "channel-0"; +export const connectionId = "connection-0"; +export const clientId = "99-ostracon-0"; + +export const channel = Channel.fromPartial({ + state: ChannelState.STATE_OPEN, + ordering: Order.ORDER_UNORDERED, + counterparty: ChannelCounterparty.fromPartial({ + portId: portId, + channelId: channelId, + }), + connectionHops: [connectionId], + version: "ics20-1", +}); + +export const identifiedChannel = IdentifiedChannel.fromPartial({ + state: ChannelState.STATE_OPEN, + ordering: Order.ORDER_UNORDERED, + counterparty: ChannelCounterparty.fromPartial({ + portId: portId, + channelId: "channel-0", + }), + connectionHops: [connectionId], + version: "ics20-1", + portId: portId, + channelId: channelId, +}); + +/** + * ``` + * jq ".channel_genesis.commitments[0]" scripts/simapp42/genesis-ibc.json + * ``` + */ +export const commitment = { + sequence: 1, + data: fromBase64("hYz5Dx6o09DcSEWZR6xlJYwLgYUnLithsXMGtujic4I="), +}; + +export const packetState = PacketState.fromPartial({ + portId: portId, + channelId: channelId, + sequence: Long.fromInt(commitment.sequence, true), + data: commitment.data, +}); + +/** + * Unfortunatly empty right now + * + * ``` + * jq ".channel_genesis.acknowledgements" scripts/simapp42/genesis-ibc.json + * ``` + */ +export const packetAcknowledgements: PacketState[] = []; + +export const connection = ConnectionEnd.fromPartial({ + clientId: clientId, + versions: [ + Version.fromPartial({ + identifier: "1", + features: ["ORDER_ORDERED", "ORDER_UNORDERED"], + }), + ], + state: ConnectionState.STATE_OPEN, + counterparty: ConnectionCounterparty.fromPartial({ + clientId: "07-tendermint-0", + connectionId: "connection-0", + prefix: MerklePrefix.fromPartial({ + keyPrefix: fromBase64("aWJj"), + }), + }), +}); + +export const identifiedConnection = IdentifiedConnection.fromPartial({ + id: connectionId, + clientId: clientId, + versions: [ + Version.fromPartial({ + identifier: "1", + features: ["ORDER_ORDERED", "ORDER_UNORDERED"], + }), + ], + state: ConnectionState.STATE_OPEN, + counterparty: ConnectionCounterparty.fromPartial({ + clientId: "07-tendermint-0", + connectionId: "connection-0", + prefix: MerklePrefix.fromPartial({ + keyPrefix: fromBase64("aWJj"), + }), + }), +}); diff --git a/packages/finschia/src/modules/ibc/messages.ts b/packages/finschia/src/modules/ibc/messages.ts new file mode 100644 index 000000000..2eb32da87 --- /dev/null +++ b/packages/finschia/src/modules/ibc/messages.ts @@ -0,0 +1,57 @@ +import { EncodeObject, GeneratedType } from "@cosmjs/proto-signing"; +import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; +import { + MsgAcknowledgement, + MsgChannelCloseConfirm, + MsgChannelCloseInit, + MsgChannelOpenAck, + MsgChannelOpenConfirm, + MsgChannelOpenInit, + MsgChannelOpenTry, + MsgRecvPacket, + MsgTimeout, + MsgTimeoutOnClose, +} from "cosmjs-types/ibc/core/channel/v1/tx"; +import { + MsgCreateClient, + MsgSubmitMisbehaviour, + MsgUpdateClient, + MsgUpgradeClient, +} from "cosmjs-types/ibc/core/client/v1/tx"; +import { + MsgConnectionOpenAck, + MsgConnectionOpenConfirm, + MsgConnectionOpenInit, + MsgConnectionOpenTry, +} from "cosmjs-types/ibc/core/connection/v1/tx"; + +export const ibcTypes: ReadonlyArray<[string, GeneratedType]> = [ + ["/ibc.applications.transfer.v1.MsgTransfer", MsgTransfer], + ["/ibc.core.channel.v1.MsgAcknowledgement", MsgAcknowledgement], + ["/ibc.core.channel.v1.MsgChannelCloseConfirm", MsgChannelCloseConfirm], + ["/ibc.core.channel.v1.MsgChannelCloseInit", MsgChannelCloseInit], + ["/ibc.core.channel.v1.MsgChannelOpenAck", MsgChannelOpenAck], + ["/ibc.core.channel.v1.MsgChannelOpenConfirm", MsgChannelOpenConfirm], + ["/ibc.core.channel.v1.MsgChannelOpenInit", MsgChannelOpenInit], + ["/ibc.core.channel.v1.MsgChannelOpenTry", MsgChannelOpenTry], + ["/ibc.core.channel.v1.MsgRecvPacket", MsgRecvPacket], + ["/ibc.core.channel.v1.MsgTimeout", MsgTimeout], + ["/ibc.core.channel.v1.MsgTimeoutOnClose", MsgTimeoutOnClose], + ["/ibc.core.client.v1.MsgCreateClient", MsgCreateClient], + ["/ibc.core.client.v1.MsgSubmitMisbehaviour", MsgSubmitMisbehaviour], + ["/ibc.core.client.v1.MsgUpdateClient", MsgUpdateClient], + ["/ibc.core.client.v1.MsgUpgradeClient", MsgUpgradeClient], + ["/ibc.core.connection.v1.MsgConnectionOpenAck", MsgConnectionOpenAck], + ["/ibc.core.connection.v1.MsgConnectionOpenConfirm", MsgConnectionOpenConfirm], + ["/ibc.core.connection.v1.MsgConnectionOpenInit", MsgConnectionOpenInit], + ["/ibc.core.connection.v1.MsgConnectionOpenTry", MsgConnectionOpenTry], +]; + +export interface MsgTransferEncodeObject extends EncodeObject { + readonly typeUrl: "/ibc.applications.transfer.v1.MsgTransfer"; + readonly value: Partial; +} + +export function isMsgTransferEncodeObject(object: EncodeObject): object is MsgTransferEncodeObject { + return (object as MsgTransferEncodeObject).typeUrl === "/ibc.applications.transfer.v1.MsgTransfer"; +} diff --git a/packages/finschia/src/modules/ibc/queries.spec.ts b/packages/finschia/src/modules/ibc/queries.spec.ts new file mode 100644 index 000000000..5b522a17b --- /dev/null +++ b/packages/finschia/src/modules/ibc/queries.spec.ts @@ -0,0 +1,559 @@ +import { QueryClient } from "@cosmjs/stargate"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import Long from "long"; + +import { pendingWithoutSimapp, simapp } from "../../testutils.spec"; +import * as ibcTest from "./ibctestdata.spec"; +import { IbcExtension, setupIbcExtension } from "./queries"; + +async function makeClientWithIbc(rpcUrl: string): Promise<[QueryClient & IbcExtension, Tendermint34Client]> { + const tmClient = await Tendermint34Client.connect(rpcUrl); + return [QueryClient.withExtensions(tmClient, setupIbcExtension), tmClient]; +} + +// todo : check ibc unittest +// describe("IbcExtension", () => { +// describe("channel", () => { +// describe("channel", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.channel(ibcTest.portId, ibcTest.channelId); +// expect(response.channel).toEqual(ibcTest.channel); +// expect(response.proofHeight).toBeDefined(); +// expect(response.proofHeight).not.toBeNull(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("channels", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.channels(); +// expect(response.channels).toEqual([ibcTest.identifiedChannel]); +// expect(response.pagination).toBeDefined(); +// expect(response.height).toBeDefined(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("allChannels", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.allChannels(); +// expect(response.channels).toEqual([ibcTest.identifiedChannel]); + +// tmClient.disconnect(); +// }); +// }); + +// describe("connectionChannels", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.connectionChannels(ibcTest.connectionId); +// expect(response.channels).toEqual([ibcTest.identifiedChannel]); +// expect(response.pagination).toBeDefined(); +// expect(response.height).toBeDefined(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("allConnectionChannels", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.allConnectionChannels(ibcTest.connectionId); +// expect(response.channels).toEqual([ibcTest.identifiedChannel]); + +// tmClient.disconnect(); +// }); +// }); + +// describe("clientState", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.clientState(ibcTest.portId, ibcTest.channelId); +// expect(response.identifiedClientState).toEqual({ +// clientId: ibcTest.clientId, +// clientState: { +// typeUrl: "/ibc.lightclients.ostracon.v1.ClientState", +// value: jasmine.any(Uint8Array), +// }, +// }); + +// tmClient.disconnect(); +// }); +// }); + +// describe("consensusState", () => { +// xit("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.consensusState( +// ibcTest.portId, +// ibcTest.channelId, +// // TODO: Find valid values +// 0, +// 0, +// ); +// expect(response.consensusState).toEqual({ +// typeUrl: "/haha", +// value: jasmine.any(Uint8Array), +// }); +// expect(response.clientId).toEqual(ibcTest.clientId); + +// tmClient.disconnect(); +// }); +// }); + +// describe("packetCommitment", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.packetCommitment( +// ibcTest.portId, +// ibcTest.channelId, +// Long.fromInt(ibcTest.commitment.sequence, true), +// ); +// expect(response.commitment).toEqual(ibcTest.commitment.data); +// expect(response.proofHeight).toBeDefined(); +// expect(response.proofHeight).not.toBeNull(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("packetCommitments", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.packetCommitments(ibcTest.portId, ibcTest.channelId); +// expect(response.commitments).toEqual([ibcTest.packetState]); +// expect(response.pagination).toBeDefined(); +// expect(response.height).toBeDefined(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("allPacketCommitments", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.allPacketCommitments(ibcTest.portId, ibcTest.channelId); +// expect(response.commitments).toEqual([ibcTest.packetState]); + +// tmClient.disconnect(); +// }); +// }); + +// describe("packetReceipt", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.packetReceipt(ibcTest.portId, ibcTest.channelId, 1); +// expect(response.received).toEqual(false); + +// tmClient.disconnect(); +// }); +// }); + +// describe("packetAcknowledgement", () => { +// it("works", async () => { +// pending("We don't have an acknowledgement for testing at the moment"); +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.packetAcknowledgement( +// ibcTest.portId, +// ibcTest.channelId, +// ibcTest.commitment.sequence, +// ); +// expect(response.acknowledgement).toEqual(ibcTest.packetAcknowledgements[0].data); +// expect(response.proofHeight).toBeDefined(); +// expect(response.proofHeight).not.toBeNull(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("packetAcknowledgements", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.packetAcknowledgements(ibcTest.portId, ibcTest.channelId); +// expect(response.acknowledgements).toEqual(ibcTest.packetAcknowledgements); +// expect(response.pagination).toBeDefined(); +// expect(response.height).toBeDefined(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("allPacketAcknowledgements", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.allPacketAcknowledgements( +// ibcTest.portId, +// ibcTest.channelId, +// ); +// expect(response.acknowledgements).toEqual(ibcTest.packetAcknowledgements); + +// tmClient.disconnect(); +// }); +// }); + +// describe("unreceivedPackets", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.unreceivedPackets( +// ibcTest.portId, +// ibcTest.channelId, +// [1, 2, 3], +// ); +// expect(response.sequences).toEqual([1, 2, 3].map((n) => Long.fromInt(n, true))); +// expect(response.height).toBeDefined(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("unreceivedAcks", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.unreceivedAcks( +// ibcTest.portId, +// ibcTest.channelId, +// [1, 2, 3, 4, 5, 6, 7], +// ); +// expect(response.sequences).toEqual([Long.fromInt(ibcTest.commitment.sequence, true)]); +// expect(response.height).toBeDefined(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("nextSequenceReceive", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.channel.nextSequenceReceive(ibcTest.portId, ibcTest.channelId); +// expect(response.nextSequenceReceive).toEqual(Long.fromInt(1, true)); +// expect(response.proofHeight).toBeDefined(); +// expect(response.proofHeight).not.toBeNull(); + +// tmClient.disconnect(); +// }); +// }); +// }); + +// describe("client", () => { +// describe("state", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.state(ibcTest.clientId); +// expect(response.clientState).toEqual({ +// typeUrl: "/ibc.lightclients.ostracon.v1.ClientState", +// value: jasmine.any(Uint8Array), +// }); + +// tmClient.disconnect(); +// }); +// }); + +// describe("states", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.states(); +// expect(response.clientStates).toEqual([ +// { +// clientId: ibcTest.clientId, +// clientState: { +// typeUrl: "/ibc.lightclients.ostracon.v1.ClientState", +// value: jasmine.any(Uint8Array), +// }, +// }, +// ]); +// expect(response.pagination).toBeDefined(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("allStates", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.allStates(); +// expect(response.clientStates).toEqual([ +// { +// clientId: ibcTest.clientId, +// clientState: { +// typeUrl: "/ibc.lightclients.ostracon.v1.ClientState", +// value: jasmine.any(Uint8Array), +// }, +// }, +// ]); + +// tmClient.disconnect(); +// }); +// }); + +// describe("consensusState", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.consensusState(ibcTest.clientId); +// expect(response.consensusState).toEqual({ +// typeUrl: "/ibc.lightclients.ostracon.v1.ConsensusState", +// value: jasmine.any(Uint8Array), +// }); + +// tmClient.disconnect(); +// }); +// }); + +// describe("consensusStates", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.consensusStates(ibcTest.clientId); +// expect(response.consensusStates).toEqual( +// jasmine.arrayContaining([ +// { +// height: jasmine.anything(), +// consensusState: { +// typeUrl: "/ibc.lightclients.ostracon.v1.ConsensusState", +// value: jasmine.any(Uint8Array), +// }, +// }, +// ]), +// ); + +// tmClient.disconnect(); +// }); +// }); + +// describe("allConsensusStates", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.allConsensusStates(ibcTest.clientId); +// expect(response.consensusStates).toEqual( +// jasmine.arrayContaining([ +// { +// height: jasmine.anything(), +// consensusState: { +// typeUrl: "/ibc.lightclients.ostracon.v1.ConsensusState", +// value: jasmine.any(Uint8Array), +// }, +// }, +// ]), +// ); + +// tmClient.disconnect(); +// }); +// }); + +// describe("params", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.params(); +// expect(response.params).toEqual({ +// allowedClients: ["06-solomachine", "99-ostracon"], +// }); + +// tmClient.disconnect(); +// }); +// }); + +// describe("stateTm", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.stateTm(ibcTest.clientId); +// expect(response.chainId).toEqual("ibc-1"); +// // TODO: Fill these expectations out + +// tmClient.disconnect(); +// }); +// }); + +// describe("statesTm", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.statesTm(); +// expect(response).toEqual( +// jasmine.arrayContaining([ +// jasmine.objectContaining({ +// chainId: "ibc-1", +// }), +// ]), +// ); + +// tmClient.disconnect(); +// }); +// }); + +// describe("allStatesTm", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.allStatesTm(); +// expect(response).toEqual( +// jasmine.arrayContaining([ +// jasmine.objectContaining({ +// chainId: "ibc-1", +// }), +// ]), +// ); + +// tmClient.disconnect(); +// }); +// }); + +// describe("consensusStateTm", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.client.consensusStateTm(ibcTest.clientId); +// expect(response.nextValidatorsHash).toEqual(jasmine.any(Uint8Array)); +// // TODO: Fill out these expectations + +// tmClient.disconnect(); +// }); +// }); +// }); + +// describe("connection", () => { +// describe("connection", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.connection.connection(ibcTest.connectionId); +// expect(response.connection).toEqual(ibcTest.connection); +// expect(response.proofHeight).toBeDefined(); +// expect(response.proofHeight).not.toBeNull(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("connections", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.connection.connections(); +// expect(response.connections).toEqual([ibcTest.identifiedConnection]); +// expect(response.pagination).toBeDefined(); +// expect(response.height).toBeDefined(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("allConnections", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.connection.allConnections(); +// expect(response.connections).toEqual([ibcTest.identifiedConnection]); + +// tmClient.disconnect(); +// }); +// }); + +// describe("clientConnections", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.connection.clientConnections(ibcTest.clientId); +// expect(response.connectionPaths).toEqual([ibcTest.connectionId]); +// expect(response.proofHeight).toBeDefined(); +// expect(response.proofHeight).not.toBeNull(); + +// tmClient.disconnect(); +// }); +// }); + +// describe("clientState", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// const response = await client.ibc.connection.clientState(ibcTest.connectionId); +// expect(response.identifiedClientState).toEqual({ +// clientId: ibcTest.clientId, +// clientState: { +// typeUrl: "/ibc.lightclients.ostracon.v1.ClientState", +// value: jasmine.any(Uint8Array), +// }, +// }); + +// tmClient.disconnect(); +// }); +// }); + +// describe("consensusState", () => { +// it("works", async () => { +// pendingWithoutSimapp(); +// const [client, tmClient] = await makeClientWithIbc(simapp.tendermintUrl); + +// // TODO: Find valid values +// const response = await client.ibc.connection.consensusState(ibcTest.connectionId, 1, 1); +// expect(response.clientId).toEqual(ibcTest.clientId); +// expect(response.consensusState).toEqual({ +// typeUrl: "/ibc.lightclients.ostracon.v1.ConsensusState", +// value: jasmine.any(Uint8Array), +// }); + +// tmClient.disconnect(); +// }); +// }); +// }); +// }); diff --git a/packages/finschia/src/modules/ibc/queries.ts b/packages/finschia/src/modules/ibc/queries.ts new file mode 100644 index 000000000..1907831a9 --- /dev/null +++ b/packages/finschia/src/modules/ibc/queries.ts @@ -0,0 +1,538 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { toAscii } from "@cosmjs/encoding"; +import { Uint64 } from "@cosmjs/math"; +import { createPagination, createProtobufRpcClient, QueryClient } from "@cosmjs/stargate"; +import { Any } from "cosmjs-types/google/protobuf/any"; +import { + QueryClientImpl as TransferQuery, + QueryDenomTraceResponse, + QueryDenomTracesResponse, + QueryParamsResponse as QueryTransferParamsResponse, +} from "cosmjs-types/ibc/applications/transfer/v1/query"; +import { Channel } from "cosmjs-types/ibc/core/channel/v1/channel"; +import { + QueryChannelClientStateResponse, + QueryChannelConsensusStateResponse, + QueryChannelResponse, + QueryChannelsResponse, + QueryClientImpl as ChannelQuery, + QueryConnectionChannelsResponse, + QueryNextSequenceReceiveResponse, + QueryPacketAcknowledgementResponse, + QueryPacketAcknowledgementsResponse, + QueryPacketCommitmentResponse, + QueryPacketCommitmentsResponse, + QueryPacketReceiptResponse, + QueryUnreceivedAcksResponse, + QueryUnreceivedPacketsResponse, +} from "cosmjs-types/ibc/core/channel/v1/query"; +import { Height } from "cosmjs-types/ibc/core/client/v1/client"; +import { + QueryClientImpl as ClientQuery, + QueryClientParamsResponse, + QueryClientStateResponse, + QueryClientStatesResponse, + QueryConsensusStateRequest, + QueryConsensusStateResponse, + QueryConsensusStatesResponse, +} from "cosmjs-types/ibc/core/client/v1/query"; +import { + QueryClientConnectionsResponse, + QueryClientImpl as ConnectionQuery, + QueryConnectionClientStateResponse, + QueryConnectionConsensusStateRequest, + QueryConnectionConsensusStateResponse, + QueryConnectionResponse, + QueryConnectionsResponse, +} from "cosmjs-types/ibc/core/connection/v1/query"; +import { + ClientState as TendermintClientState, + ConsensusState as TendermintConsensusState, +} from "lbmjs-types/ibc/lightclients/ostracon/v1/ostracon"; +import Long from "long"; + +function decodeTendermintClientStateAny(clientState: Any | undefined): TendermintClientState { + if (clientState?.typeUrl !== "/ibc.lightclients.ostracon.v1.ClientState") { + throw new Error(`Unexpected client state type: ${clientState?.typeUrl}`); + } + return TendermintClientState.decode(clientState.value); +} + +function decodeTendermintConsensusStateAny(clientState: Any | undefined): TendermintConsensusState { + if (clientState?.typeUrl !== "/ibc.lightclients.ostracon.v1.ConsensusState") { + throw new Error(`Unexpected client state type: ${clientState?.typeUrl}`); + } + return TendermintConsensusState.decode(clientState.value); +} + +export interface IbcExtension { + readonly ibc: { + readonly channel: { + readonly channel: (portId: string, channelId: string) => Promise; + readonly channels: (paginationKey?: Uint8Array) => Promise; + readonly allChannels: () => Promise; + readonly connectionChannels: ( + connection: string, + paginationKey?: Uint8Array, + ) => Promise; + readonly allConnectionChannels: (connection: string) => Promise; + readonly clientState: (portId: string, channelId: string) => Promise; + readonly consensusState: ( + portId: string, + channelId: string, + revisionNumber: number, + revisionHeight: number, + ) => Promise; + readonly packetCommitment: ( + portId: string, + channelId: string, + sequence: Long, + ) => Promise; + readonly packetCommitments: ( + portId: string, + channelId: string, + paginationKey?: Uint8Array, + ) => Promise; + readonly allPacketCommitments: ( + portId: string, + channelId: string, + ) => Promise; + readonly packetReceipt: ( + portId: string, + channelId: string, + sequence: number, + ) => Promise; + readonly packetAcknowledgement: ( + portId: string, + channelId: string, + sequence: number, + ) => Promise; + readonly packetAcknowledgements: ( + portId: string, + channelId: string, + paginationKey?: Uint8Array, + ) => Promise; + readonly allPacketAcknowledgements: ( + portId: string, + channelId: string, + ) => Promise; + readonly unreceivedPackets: ( + portId: string, + channelId: string, + packetCommitmentSequences: readonly number[], + ) => Promise; + readonly unreceivedAcks: ( + portId: string, + channelId: string, + packetAckSequences: readonly number[], + ) => Promise; + readonly nextSequenceReceive: ( + portId: string, + channelId: string, + ) => Promise; + }; + readonly client: { + readonly state: (clientId: string) => Promise; + readonly states: (paginationKey?: Uint8Array) => Promise; + readonly allStates: () => Promise; + readonly consensusState: (clientId: string, height?: number) => Promise; + readonly consensusStates: ( + clientId: string, + paginationKey?: Uint8Array, + ) => Promise; + readonly allConsensusStates: (clientId: string) => Promise; + readonly params: () => Promise; + readonly stateTm: (clientId: string) => Promise; + readonly statesTm: (paginationKey?: Uint8Array) => Promise; + readonly allStatesTm: () => Promise; + readonly consensusStateTm: (clientId: string, height?: Height) => Promise; + }; + readonly connection: { + readonly connection: (connectionId: string) => Promise; + readonly connections: (paginationKey?: Uint8Array) => Promise; + readonly allConnections: () => Promise; + readonly clientConnections: (clientId: string) => Promise; + readonly clientState: (connectionId: string) => Promise; + readonly consensusState: ( + connectionId: string, + revisionNumber: number, + revisionHeight: number, + ) => Promise; + }; + readonly transfer: { + readonly denomTrace: (hash: string) => Promise; + readonly denomTraces: (paginationKey?: Uint8Array) => Promise; + readonly allDenomTraces: () => Promise; + readonly params: () => Promise; + }; + readonly verified: { + readonly channel: { + readonly channel: (portId: string, channelId: string) => Promise; + readonly packetCommitment: ( + portId: string, + channelId: string, + sequence: number, + ) => Promise; + readonly packetAcknowledgement: ( + portId: string, + channelId: string, + sequence: number, + ) => Promise; + readonly nextSequenceReceive: (portId: string, channelId: string) => Promise; + }; + }; + }; +} + +export function setupIbcExtension(base: QueryClient): IbcExtension { + const rpc = createProtobufRpcClient(base); + // Use these services to get easy typed access to query methods + // These cannot be used for proof verification + const channelQueryService = new ChannelQuery(rpc); + const clientQueryService = new ClientQuery(rpc); + const connectionQueryService = new ConnectionQuery(rpc); + const transferQueryService = new TransferQuery(rpc); + + return { + ibc: { + channel: { + channel: async (portId: string, channelId: string) => + channelQueryService.Channel({ + portId: portId, + channelId: channelId, + }), + channels: async (paginationKey?: Uint8Array) => + channelQueryService.Channels({ + pagination: createPagination(paginationKey), + }), + allChannels: async () => { + const channels = []; + let response: QueryChannelsResponse; + let key: Uint8Array | undefined; + do { + response = await channelQueryService.Channels({ + pagination: createPagination(key), + }); + channels.push(...response.channels); + key = response.pagination?.nextKey; + } while (key && key.length); + return { + channels: channels, + height: response.height, + }; + }, + connectionChannels: async (connection: string, paginationKey?: Uint8Array) => + channelQueryService.ConnectionChannels({ + connection: connection, + pagination: createPagination(paginationKey), + }), + allConnectionChannels: async (connection: string) => { + const channels = []; + let response: QueryConnectionChannelsResponse; + let key: Uint8Array | undefined; + do { + response = await channelQueryService.ConnectionChannels({ + connection: connection, + pagination: createPagination(key), + }); + channels.push(...response.channels); + key = response.pagination?.nextKey; + } while (key && key.length); + return { + channels: channels, + height: response.height, + }; + }, + clientState: async (portId: string, channelId: string) => + channelQueryService.ChannelClientState({ + portId: portId, + channelId: channelId, + }), + consensusState: async ( + portId: string, + channelId: string, + revisionNumber: number, + revisionHeight: number, + ) => + channelQueryService.ChannelConsensusState({ + portId: portId, + channelId: channelId, + revisionNumber: Long.fromNumber(revisionNumber, true), + revisionHeight: Long.fromNumber(revisionHeight, true), + }), + packetCommitment: async (portId: string, channelId: string, sequence: Long) => + channelQueryService.PacketCommitment({ + portId: portId, + channelId: channelId, + sequence: sequence, + }), + packetCommitments: async (portId: string, channelId: string, paginationKey?: Uint8Array) => + channelQueryService.PacketCommitments({ + channelId: channelId, + portId: portId, + pagination: createPagination(paginationKey), + }), + allPacketCommitments: async (portId: string, channelId: string) => { + const commitments = []; + let response: QueryPacketCommitmentsResponse; + let key: Uint8Array | undefined; + do { + response = await channelQueryService.PacketCommitments({ + channelId: channelId, + portId: portId, + pagination: createPagination(key), + }); + commitments.push(...response.commitments); + key = response.pagination?.nextKey; + } while (key && key.length); + return { + commitments: commitments, + height: response.height, + }; + }, + packetReceipt: async (portId: string, channelId: string, sequence: number) => + channelQueryService.PacketReceipt({ + portId: portId, + channelId: channelId, + sequence: Long.fromNumber(sequence, true), + }), + packetAcknowledgement: async (portId: string, channelId: string, sequence: number) => + channelQueryService.PacketAcknowledgement({ + portId: portId, + channelId: channelId, + sequence: Long.fromNumber(sequence, true), + }), + packetAcknowledgements: async (portId: string, channelId: string, paginationKey?: Uint8Array) => + channelQueryService.PacketAcknowledgements({ + portId: portId, + channelId: channelId, + pagination: createPagination(paginationKey), + }), + allPacketAcknowledgements: async (portId: string, channelId: string) => { + const acknowledgements = []; + let response: QueryPacketAcknowledgementsResponse; + let key: Uint8Array | undefined; + do { + response = await channelQueryService.PacketAcknowledgements({ + channelId: channelId, + portId: portId, + pagination: createPagination(key), + }); + acknowledgements.push(...response.acknowledgements); + key = response.pagination?.nextKey; + } while (key && key.length); + return { + acknowledgements: acknowledgements, + height: response.height, + }; + }, + unreceivedPackets: async ( + portId: string, + channelId: string, + packetCommitmentSequences: readonly number[], + ) => + channelQueryService.UnreceivedPackets({ + portId: portId, + channelId: channelId, + packetCommitmentSequences: packetCommitmentSequences.map((s) => Long.fromNumber(s, true)), + }), + unreceivedAcks: async (portId: string, channelId: string, packetAckSequences: readonly number[]) => + channelQueryService.UnreceivedAcks({ + portId: portId, + channelId: channelId, + packetAckSequences: packetAckSequences.map((s) => Long.fromNumber(s, true)), + }), + nextSequenceReceive: async (portId: string, channelId: string) => + channelQueryService.NextSequenceReceive({ + portId: portId, + channelId: channelId, + }), + }, + client: { + state: async (clientId: string) => clientQueryService.ClientState({ clientId }), + states: async (paginationKey?: Uint8Array) => + clientQueryService.ClientStates({ + pagination: createPagination(paginationKey), + }), + allStates: async () => { + const clientStates = []; + let response: QueryClientStatesResponse; + let key: Uint8Array | undefined; + do { + response = await clientQueryService.ClientStates({ + pagination: createPagination(key), + }); + clientStates.push(...response.clientStates); + key = response.pagination?.nextKey; + } while (key && key.length); + return { + clientStates: clientStates, + }; + }, + consensusState: async (clientId: string, consensusHeight?: number) => + clientQueryService.ConsensusState( + QueryConsensusStateRequest.fromPartial({ + clientId: clientId, + revisionHeight: + consensusHeight !== undefined ? Long.fromNumber(consensusHeight, true) : undefined, + latestHeight: consensusHeight === undefined, + }), + ), + consensusStates: async (clientId: string, paginationKey?: Uint8Array) => + clientQueryService.ConsensusStates({ + clientId: clientId, + pagination: createPagination(paginationKey), + }), + allConsensusStates: async (clientId: string) => { + const consensusStates = []; + let response: QueryConsensusStatesResponse; + let key: Uint8Array | undefined; + do { + response = await clientQueryService.ConsensusStates({ + clientId: clientId, + pagination: createPagination(key), + }); + consensusStates.push(...response.consensusStates); + key = response.pagination?.nextKey; + } while (key && key.length); + return { + consensusStates: consensusStates, + }; + }, + params: async () => clientQueryService.ClientParams({}), + stateTm: async (clientId: string) => { + const response = await clientQueryService.ClientState({ clientId }); + return decodeTendermintClientStateAny(response.clientState); + }, + statesTm: async (paginationKey?: Uint8Array) => { + const { clientStates } = await clientQueryService.ClientStates({ + pagination: createPagination(paginationKey), + }); + return clientStates.map(({ clientState }) => decodeTendermintClientStateAny(clientState)); + }, + allStatesTm: async () => { + const clientStates = []; + let response: QueryClientStatesResponse; + let key: Uint8Array | undefined; + do { + response = await clientQueryService.ClientStates({ + pagination: createPagination(key), + }); + clientStates.push(...response.clientStates); + key = response.pagination?.nextKey; + } while (key && key.length); + return clientStates.map(({ clientState }) => decodeTendermintClientStateAny(clientState)); + }, + consensusStateTm: async (clientId: string, consensusHeight?: Height) => { + const response = await clientQueryService.ConsensusState( + QueryConsensusStateRequest.fromPartial({ + clientId: clientId, + revisionHeight: consensusHeight?.revisionHeight, + revisionNumber: consensusHeight?.revisionNumber, + latestHeight: consensusHeight === undefined, + }), + ); + return decodeTendermintConsensusStateAny(response.consensusState); + }, + }, + connection: { + connection: async (connectionId: string) => + connectionQueryService.Connection({ + connectionId: connectionId, + }), + connections: async (paginationKey?: Uint8Array) => + connectionQueryService.Connections({ + pagination: createPagination(paginationKey), + }), + allConnections: async () => { + const connections = []; + let response: QueryConnectionsResponse; + let key: Uint8Array | undefined; + do { + response = await connectionQueryService.Connections({ + pagination: createPagination(key), + }); + connections.push(...response.connections); + key = response.pagination?.nextKey; + } while (key && key.length); + return { + connections: connections, + height: response.height, + }; + }, + clientConnections: async (clientId: string) => + connectionQueryService.ClientConnections({ + clientId: clientId, + }), + clientState: async (connectionId: string) => + connectionQueryService.ConnectionClientState({ + connectionId: connectionId, + }), + consensusState: async (connectionId: string, revisionHeight: number) => + connectionQueryService.ConnectionConsensusState( + QueryConnectionConsensusStateRequest.fromPartial({ + connectionId: connectionId, + revisionHeight: Long.fromNumber(revisionHeight, true), + }), + ), + }, + transfer: { + denomTrace: async (hash: string) => transferQueryService.DenomTrace({ hash: hash }), + denomTraces: async (paginationKey?: Uint8Array) => + transferQueryService.DenomTraces({ + pagination: createPagination(paginationKey), + }), + allDenomTraces: async () => { + const denomTraces = []; + let response: QueryDenomTracesResponse; + let key: Uint8Array | undefined; + do { + response = await transferQueryService.DenomTraces({ + pagination: createPagination(key), + }); + denomTraces.push(...response.denomTraces); + key = response.pagination?.nextKey; + } while (key && key.length); + return { + denomTraces: denomTraces, + }; + }, + params: async () => transferQueryService.Params({}), + }, + verified: { + channel: { + channel: async (portId: string, channelId: string) => { + // keeper: https://github.com/cosmos/cosmos-sdk/blob/3bafd8255a502e5a9cee07391cf8261538245dfd/x/ibc/04-channel/keeper/keeper.go#L55-L65 + // key: https://github.com/cosmos/cosmos-sdk/blob/ef0a7344af345882729598bc2958a21143930a6b/x/ibc/24-host/keys.go#L117-L120 + const key = toAscii(`channelEnds/ports/${portId}/channels/${channelId}`); + const responseData = await base.queryVerified("ibc", key); + return responseData.length ? Channel.decode(responseData) : null; + }, + packetCommitment: async (portId: string, channelId: string, sequence: number) => { + // keeper: https://github.com/cosmos/cosmos-sdk/blob/3bafd8255a502e5a9cee07391cf8261538245dfd/x/ibc/04-channel/keeper/keeper.go#L128-L133 + // key: https://github.com/cosmos/cosmos-sdk/blob/ef0a7344af345882729598bc2958a21143930a6b/x/ibc/24-host/keys.go#L183-L185 + const key = toAscii(`commitments/ports/${portId}/channels/${channelId}/packets/${sequence}`); + const responseData = await base.queryVerified("ibc", key); + // keeper code doesn't parse, but returns raw + return responseData; + }, + packetAcknowledgement: async (portId: string, channelId: string, sequence: number) => { + // keeper: https://github.com/cosmos/cosmos-sdk/blob/3bafd8255a502e5a9cee07391cf8261538245dfd/x/ibc/04-channel/keeper/keeper.go#L159-L166 + // key: https://github.com/cosmos/cosmos-sdk/blob/ef0a7344af345882729598bc2958a21143930a6b/x/ibc/24-host/keys.go#L153-L156 + const key = toAscii(`acks/ports/${portId}/channels/${channelId}/acknowledgements/${sequence}`); + const responseData = await base.queryVerified("ibc", key); + // keeper code doesn't parse, but returns raw + return responseData; + }, + nextSequenceReceive: async (portId: string, channelId: string) => { + // keeper: https://github.com/cosmos/cosmos-sdk/blob/3bafd8255a502e5a9cee07391cf8261538245dfd/x/ibc/04-channel/keeper/keeper.go#L92-L101 + // key: https://github.com/cosmos/cosmos-sdk/blob/ef0a7344af345882729598bc2958a21143930a6b/x/ibc/24-host/keys.go#L133-L136 + const key = toAscii(`seqAcks/ports/${portId}/channels/${channelId}/nextSequenceAck`); + const responseData = await base.queryVerified("ibc", key); + return responseData.length ? Uint64.fromBytes(responseData).toNumber() : null; + }, + }, + }, + }, + }; +} diff --git a/packages/finschia/src/modules/index.ts b/packages/finschia/src/modules/index.ts new file mode 100644 index 000000000..c2d136517 --- /dev/null +++ b/packages/finschia/src/modules/index.ts @@ -0,0 +1,32 @@ +export { collectionTypes } from "./collection/messages"; +export { CollectionExtension, setupCollectionExtension } from "./collection/queries"; +export { + AminoMsgSubmitEvidence, + createEvidenceAminoConverters, + isAminoMsgSubmitEvidence, +} from "./evidence/aminomessages"; +export { EvidenceExtension, setupEvidenceExtension } from "./evidence/queries"; +export { createFreegrantAminoConverters } from "./feegrant/aminomessages"; +export { feegrantTypes } from "./feegrant/messages"; +export { FeeGrantExtension, setupFeeGrantExtension } from "./feegrant/queries"; +export { + createMsgGrant, + createMsgRevoke, + createMsgSubmitProposal, + createMsgUpdateDecisionPolicy, + createMsgUpdateMembers, + createMsgWithdrawFromTreasury, + createPercentageDecisionPolicy, + createThresholdDecisionPolicy, + foundationTypes, + isPercentageDecisionPolicyEncodeObject, + isThresholdDecisionPolicyEncodeObject, + PercentageDecisionPolicyEncodeObject, + ThresholdDecisionPolicyEncodeObject, +} from "./foundation/messages"; +export { FoundationExtension, FoundationProposalId, setupFoundationExtension } from "./foundation/queries"; +export { AminoMsgTransfer, createIbcAminoConverters, isAminoMsgTransfer } from "./ibc/aminomessages"; +export { ibcTypes, isMsgTransferEncodeObject, MsgTransferEncodeObject } from "./ibc/messages"; +export { IbcExtension, setupIbcExtension } from "./ibc/queries"; +export { tokenTypes } from "./token/messages"; +export { setupTokenExtension, TokenExtension } from "./token/queries"; diff --git a/packages/finschia/src/modules/token/messages.ts b/packages/finschia/src/modules/token/messages.ts new file mode 100644 index 000000000..de5545e92 --- /dev/null +++ b/packages/finschia/src/modules/token/messages.ts @@ -0,0 +1,28 @@ +import { GeneratedType } from "@cosmjs/proto-signing"; +import { + MsgApprove, + MsgBurn, + MsgBurnFrom, + MsgGrantPermission, + MsgIssue, + MsgMint, + MsgModify, + MsgRevokeOperator, + MsgRevokePermission, + MsgSend, + MsgTransferFrom, +} from "lbmjs-types/lbm/token/v1/tx"; + +export const tokenTypes: ReadonlyArray<[string, GeneratedType]> = [ + ["/lbm.token.v1.MsgSend", MsgSend], + ["/lbm.token.v1.MsgTransferFrom", MsgTransferFrom], + ["/lbm.token.v1.MsgRevokeOperator", MsgRevokeOperator], + ["/lbm.token.v1.MsgApprove", MsgApprove], + ["/lbm.token.v1.MsgIssue", MsgIssue], + ["/lbm.token.v1.MsgGrantPermission", MsgGrantPermission], + ["/lbm.token.v1.MsgRevokePermission", MsgRevokePermission], + ["/lbm.token.v1.MsgMint", MsgMint], + ["/lbm.token.v1.MsgBurn", MsgBurn], + ["/lbm.token.v1.MsgBurnFrom", MsgBurnFrom], + ["/lbm.token.v1.MsgModify", MsgModify], +]; diff --git a/packages/finschia/src/modules/token/queries.spec.ts b/packages/finschia/src/modules/token/queries.spec.ts new file mode 100644 index 000000000..cad382287 --- /dev/null +++ b/packages/finschia/src/modules/token/queries.spec.ts @@ -0,0 +1,385 @@ +import { coins, DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing"; +import { assertIsDeliverTxSuccess, QueryClient } from "@cosmjs/stargate"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { assert, sleep } from "@cosmjs/utils"; +import { Permission } from "lbmjs-types/lbm/token/v1/token"; +import { MsgIssue } from "lbmjs-types/lbm/token/v1/tx"; + +import { FinschiaClient } from "../../finschiaClient"; + +import { makeLinkPath } from "../../paths"; +import { + defaultSigningClientOptions, + faucet, + pendingWithoutSimapp, + simapp, + simappEnabled, +} from "../../testutils.spec"; +import { setupTokenExtension, TokenExtension } from "./queries"; + +async function makeClientWithToken( + rpcUrl: string, +): Promise<[QueryClient & TokenExtension, Tendermint34Client]> { + const tmClient = await Tendermint34Client.connect(rpcUrl); + return [QueryClient.withExtensions(tmClient, setupTokenExtension), tmClient]; +} + +describe("TokenExtension(Just Issue)", () => { + const defaultFee = { + amount: coins(250000, "cony"), + gas: "1500000", // 1.5 million + }; + + const owner = faucet.address0; + const toAddress = faucet.address1; + const tokenName = "TestToken"; + const symbol = "ZERO1"; + const amount = "1000"; + let contractId: string | undefined; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await FinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + // Issue + { + const msg: MsgIssue = { + name: tokenName, + symbol: symbol, + imageUri: "", + meta: "", + decimals: 6, + owner: owner, + to: toAddress, + amount: amount, + mintable: true, + }; + const msgAny: EncodeObject = { + typeUrl: "/lbm.token.v1.MsgIssue", + value: msg, + }; + const memo = "Test Token for Stargate"; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee, memo); + const logs = JSON.parse(result.rawLog || ""); + contractId = logs[0].events + .find(({ type }: any) => type === "lbm.token.v1.EventIssued") + .attributes.find(({ key }: any) => key === "contract_id").value; + assert(contractId, "Missing contract ID"); + contractId = contractId.replace(/^"(.*)"$/, "$1"); + assertIsDeliverTxSuccess(result); + } + + await sleep(75); // wait until transactions are indexed + + client.disconnect(); + } + }); + + describe("query", () => { + it("totalBalance", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const totalBalance = await client.token.balance(contractId, toAddress); + expect(totalBalance).toBeDefined(); + expect(totalBalance).not.toBeNull(); + expect(totalBalance).toEqual("1000"); + + tmClient.disconnect(); + }); + it("supply", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const supplyRes = await client.token.supply(contractId); + expect(supplyRes).toEqual(amount); + + const mintSupplyRes = await client.token.minted(contractId); + expect(mintSupplyRes).toEqual(amount); + + const burnSupplyRes = await client.token.burnt(contractId); + expect(burnSupplyRes).toEqual("0"); + + tmClient.disconnect(); + }); + it("tokenClass", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const token = await client.token.tokenClass(contractId); + expect(token).toEqual({ + contractId: contractId, + name: tokenName, + symbol: symbol, + meta: "", + imageUri: "", + decimals: 6, + mintable: true, + }); + + tmClient.disconnect(); + }); + it("grant", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const response = await client.token.granteeGrants(contractId, owner); + expect(response[0]).toEqual({ grantee: owner, permission: Permission.PERMISSION_MODIFY }); + + tmClient.disconnect(); + }); + }); + it("tokenClasses", async () => { + pendingWithoutSimapp(); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const response = await client.token.tokenClasses(); + expect(response.length).toBeGreaterThanOrEqual(1); + + tmClient.disconnect(); + }); +}); + +describe("TokenExtension", () => { + const defaultFee = { + amount: coins(250000, "cony"), + gas: "1500000", // 1.5 million + }; + + const owner = faucet.address0; + const toAddress = faucet.address1; + const otherAddress = faucet.address3; + const tokenName = "TestToken"; + const symbol = "ZERO2"; + const amount = "1000"; + let contractId: string | undefined; + + jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 1000; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await FinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + // Issue + { + const msgAny: EncodeObject = { + typeUrl: "/lbm.token.v1.MsgIssue", + value: { + owner: owner, + to: toAddress, + name: tokenName, + symbol: symbol, + imageUri: "", + meta: "https://test.network", + amount: amount, + mintable: true, + decimals: 6, + }, + }; + const memo = "Test Token for Stargate"; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee, memo); + const logs = JSON.parse(result.rawLog || ""); + contractId = logs[0].events + .find(({ type }: any) => type === "lbm.token.v1.EventIssued") + .attributes.find(({ key }: any) => key === "contract_id").value; + assert(contractId, "Missing contract ID"); + contractId = contractId.replace(/^"(.*)"$/, "$1"); + assertIsDeliverTxSuccess(result); + } + + // Mint + { + const msgAny: EncodeObject = { + typeUrl: "/lbm.token.v1.MsgMint", + value: { + contractId: contractId, + from: owner, + to: owner, + amount: "500", + }, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + assertIsDeliverTxSuccess(result); + } + + // Transfer + { + const msgAny: EncodeObject = { + typeUrl: "/lbm.token.v1.MsgSend", + value: { + contractId: contractId, + from: owner, + to: otherAddress, + amount: "100", + }, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + assertIsDeliverTxSuccess(result); + } + + // Burn + { + const msgAny: EncodeObject = { + typeUrl: "/lbm.token.v1.MsgBurn", + value: { + contractId: contractId, + from: owner, + amount: "200", + }, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + assertIsDeliverTxSuccess(result); + } + + // GrantPermission + { + const msgAny: EncodeObject = { + typeUrl: "/lbm.token.v1.MsgGrantPermission", + value: { + contractId: contractId, + from: owner, + to: toAddress, + permission: "MODIFY", // {MODIFY, MINT, BURN} + }, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + assertIsDeliverTxSuccess(result); + } + + // Revoke + { + const msgAny: EncodeObject = { + typeUrl: "/lbm.token.v1.MsgRevokePermission", + value: { + contractId: contractId, + from: owner, + permission: "BURN", // {MODIFY, MINT, BURN} + }, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + assertIsDeliverTxSuccess(result); + } + + // Approve + { + const msgAny: EncodeObject = { + typeUrl: "/lbm.token.v1.MsgApprove", + value: { + contractId: contractId, + approver: owner, + proxy: toAddress, + }, + }; + const result = await client.signAndBroadcast(owner, [msgAny], defaultFee); + assertIsDeliverTxSuccess(result); + } + + await sleep(75); // wait until transactions are indexed + + client.disconnect(); + } + }); + + describe("query", () => { + it("totalBalance", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const balance1 = await client.token.balance(contractId, toAddress); + expect(balance1).toEqual("1000"); + + const balance2 = await client.token.balance(contractId, owner); + expect(balance2).toEqual("200"); + + const balance3 = await client.token.balance(contractId, otherAddress); + expect(balance3).toEqual("100"); + + tmClient.disconnect(); + }); + it("supply", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const supplyRes = await client.token.supply(contractId); + expect(supplyRes).toEqual("1300"); + + const mintSupplyRes = await client.token.minted(contractId); + expect(mintSupplyRes).toEqual("1500"); + + const burnSupplyRes = await client.token.burnt(contractId); + expect(burnSupplyRes).toEqual("200"); + + tmClient.disconnect(); + }); + it("token", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const token = await client.token.tokenClass(contractId); + expect(token).toEqual({ + contractId: contractId, + name: tokenName, + symbol: symbol, + imageUri: "", + meta: "https://test.network", + decimals: 6, + mintable: true, + }); + + tmClient.disconnect(); + }); + it("granteeGrants", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const response = await client.token.granteeGrants(contractId, toAddress); + expect(response[0]).toEqual({ grantee: toAddress, permission: Permission.PERMISSION_MODIFY }); + + tmClient.disconnect(); + }); + it("approved", async () => { + pendingWithoutSimapp(); + assert(contractId, "Missing contract ID"); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const response = await client.token.approved(contractId, toAddress, owner); + expect(response).toBeTrue(); + + tmClient.disconnect(); + }); + it("approvers", async () => { + pendingWithoutSimapp(); + assert(contractId, "Mission contract ID"); + const [client, tmClient] = await makeClientWithToken(simapp.tendermintUrl); + + const response = await client.token.approvers(contractId, toAddress); + expect(response[0]).toEqual(owner); + + tmClient.disconnect(); + }); + }); +}); diff --git a/packages/finschia/src/modules/token/queries.ts b/packages/finschia/src/modules/token/queries.ts new file mode 100644 index 000000000..9396b5bb8 --- /dev/null +++ b/packages/finschia/src/modules/token/queries.ts @@ -0,0 +1,75 @@ +import { createProtobufRpcClient, QueryClient } from "@cosmjs/stargate"; +import { assert } from "@cosmjs/utils"; +import { QueryClientImpl } from "lbmjs-types/lbm/token/v1/query"; +import { Grant, TokenClass } from "lbmjs-types/lbm/token/v1/token"; + +export interface TokenExtension { + readonly token: { + readonly balance: (contractId: string, address: string) => Promise; + readonly supply: (contractId: string) => Promise; + readonly minted: (contractId: string) => Promise; + readonly burnt: (contractId: string) => Promise; + readonly tokenClass: (contractId: string) => Promise; + readonly tokenClasses: () => Promise; + readonly granteeGrants: (contractId: string, grantee: string) => Promise; + readonly approved: (contractId: string, address: string, approver: string) => Promise; + readonly approvers: (contractId: string, address: string) => Promise; + }; +} + +export function setupTokenExtension(base: QueryClient): TokenExtension { + const rpc = createProtobufRpcClient(base); + // Use this service to get easy typed access to query methods + // This cannot be used for proof verification + const queryService = new QueryClientImpl(rpc); + + return { + token: { + balance: async (contractId: string, address: string) => { + const { amount } = await queryService.Balance({ contractId: contractId, address: address }); + assert(amount); + return amount; + }, + supply: async (contractId: string) => { + const { amount } = await queryService.Supply({ contractId: contractId }); + assert(amount); + return amount; + }, + minted: async (contractId: string) => { + const { amount } = await queryService.Minted({ contractId: contractId }); + assert(amount); + return amount; + }, + burnt: async (contractId: string) => { + const { amount } = await queryService.Burnt({ contractId: contractId }); + assert(amount); + return amount; + }, + tokenClass: async (contractId: string) => { + const { class: cls } = await queryService.TokenClass({ contractId: contractId }); + assert(cls); + return cls; + }, + tokenClasses: async () => { + const { classes } = await queryService.TokenClasses({ pagination: undefined }); + return classes; + }, + granteeGrants: async (contractId: string, grantee: string) => { + const { grants } = await queryService.GranteeGrants({ contractId: contractId, grantee: grantee }); + return grants; + }, + approved: async (contractId: string, address: string, approver: string) => { + const { approved } = await queryService.Approved({ + contractId: contractId, + proxy: address, + approver: approver, + }); + return approved; + }, + approvers: async (contractId: string, address: string) => { + const { approvers } = await queryService.Approvers({ contractId: contractId, address: address }); + return approvers; + }, + }, + }; +} diff --git a/packages/finschia/src/paths.spec.ts b/packages/finschia/src/paths.spec.ts new file mode 100644 index 000000000..da3cc79f4 --- /dev/null +++ b/packages/finschia/src/paths.spec.ts @@ -0,0 +1,26 @@ +import { Slip10RawIndex } from "@cosmjs/crypto"; + +import { makeLinkPath } from "./paths"; + +describe("paths", () => { + describe("makeLinkPath", () => { + it("works", () => { + // m/44'/438'/0'/0/0 + expect(makeLinkPath(0)).toEqual([ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(438), + Slip10RawIndex.hardened(0), + Slip10RawIndex.normal(0), + Slip10RawIndex.normal(0), + ]); + // m/44'/438'/0'/0/123 + expect(makeLinkPath(123)).toEqual([ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(438), + Slip10RawIndex.hardened(0), + Slip10RawIndex.normal(0), + Slip10RawIndex.normal(123), + ]); + }); + }); +}); diff --git a/packages/finschia/src/paths.ts b/packages/finschia/src/paths.ts new file mode 100644 index 000000000..0384eaa18 --- /dev/null +++ b/packages/finschia/src/paths.ts @@ -0,0 +1,15 @@ +import { HdPath, Slip10RawIndex } from "@cosmjs/crypto"; + +/** + * The LINK derivation path in the form `m/44'/438'/0'/0/a` + * with 0-based account index `a`. + */ +export function makeLinkPath(a: number): HdPath { + return [ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(438), + Slip10RawIndex.hardened(0), + Slip10RawIndex.normal(0), + Slip10RawIndex.normal(a), + ]; +} diff --git a/packages/finschia/src/testutils.spec.ts b/packages/finschia/src/testutils.spec.ts new file mode 100644 index 000000000..080a6382c --- /dev/null +++ b/packages/finschia/src/testutils.spec.ts @@ -0,0 +1,233 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { AminoSignResponse, Secp256k1HdWallet, Secp256k1HdWalletOptions, StdSignDoc } from "@cosmjs/amino"; +import { Bip39, EnglishMnemonic, Random } from "@cosmjs/crypto"; +import { toBech32 } from "@cosmjs/encoding"; +import { + coins, + DirectSecp256k1HdWallet, + DirectSecp256k1HdWalletOptions, + DirectSignResponse, + makeAuthInfoBytes, +} from "@cosmjs/proto-signing"; +import { calculateFee, GasPrice, SigningStargateClientOptions } from "@cosmjs/stargate"; +import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; +import { AuthInfo, SignDoc, TxBody } from "cosmjs-types/cosmos/tx/v1beta1/tx"; + +export function simappEnabled(): boolean { + return !!process.env.SIMAPP_ENABLED; +} + +export function pendingWithoutSimapp(): void { + if (!simappEnabled()) { + return pending("Set SIMAPP42_ENABLED or SIMAPP44_ENABLED to enable Simapp based tests"); + } +} + +export function slowSimappEnabled(): boolean { + return !!process.env.SIMAPP_ENABLED; +} + +export function pendingWithoutSlowSimapp(): void { + if (!slowSimappEnabled()) { + return pending("Set SLOW_SIMAPP42_ENABLED or SLOW_SIMAPP44_ENABLED to enable slow Simapp based tests"); + } +} + +export function makeRandomAddressBytes(): Uint8Array { + return Random.getBytes(20); +} + +export function makeRandomAddress(): string { + return toBech32("link", makeRandomAddressBytes()); +} + +/** Returns first element. Throws if array has a different length than 1. */ +export function fromOneElementArray(elements: ArrayLike): T { + if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`); + return elements[0]; +} + +export const defaultGasPrice = GasPrice.fromString("0.025cony"); +export const defaultSendFee = calculateFee(100_000, defaultGasPrice); + +export const simapp = { + tendermintUrl: "localhost:26658", + tendermintUrlWs: "ws://localhost:26658", + tendermintUrlHttp: "http://localhost:26658", + chainId: "simd-testing", + prefix: "link", + denomStaking: "stake", + denomFee: "cony", + blockTime: 1_000, // ms + totalSupply: 1100000000000, // cony + govMinDeposit: coins(10000000, "stake"), +}; + +export const slowSimapp = { + tendermintUrl: "localhost:26660", + tendermintUrlWs: "ws://localhost:26660", + tendermintUrlHttp: "http://localhost:26660", + chainId: "simd-testing", + denomStaking: "ustake", + denomFee: "cony", + blockTime: 10_000, // ms + totalSupply: 21000000000, // cony +}; + +/** Setting to speed up testing */ +export const defaultSigningClientOptions: SigningStargateClientOptions = { + broadcastPollIntervalMs: 300, + broadcastTimeoutMs: 8_000, + gasPrice: GasPrice.fromString("0.01cony"), +}; + +export const faucet = { + mnemonic: + "mind flame tobacco sense move hammer drift crime ring globe art gaze cinnamon helmet cruise special produce notable negative wait path scrap recall have", + pubkey0: { + type: "tendermint/PubKeySecp256k1", + value: "AgT2QPS4Eu6M+cfHeba+3tumsM/hNEBGdM7nRojSZRjF", + }, + pubkey1: { + type: "tendermint/PubKeySecp256k1", + value: "Au7fdDpmcXLbuxH5z6PvvzUaKQI6EeDY5GNt9e17cYxk", + }, + pubkey2: { + type: "tendermint/PubKeySecp256k1", + value: "A45xEMprNuuMDvoSyN35OFzMSjgN3JfU0JrtDigGL1li", + }, + pubkey3: { + type: "tendermint/PubKeySecp256k1", + value: "A++1IDp1lAwqi1/nSxjRwsUAgMuabMHaOaxEgszpHH3O", + }, + pubkey4: { + type: "tendermint/PubKeySecp256k1", + value: "AiprmR/HER1JI4/kF49WNZUND57MygR4myw1HrqlJ8if", + }, + pubkey5: { + type: "tendermint/PubKeySecp256k1", + value: "A/sRpmP7Bk1LIkax7HA6DxegTIxmstJXH6xkmAzSxzXO", + }, + address0: "link146asaycmtydq45kxc8evntqfgepagygelel00h", + address1: "link1aaffxdz4dwcnjzumjm7h89yjw5c5wul88zvzuu", + address2: "link1ey0w0xj9v48vk82ht6mhqdlh9wqkx8enkpjwpr", + address3: "link1dfyywjglcfptn72axxhsslpy8ep6wq7wujasma", + address4: "link1equ4n3uwyhapak5g3leq0avz85k0q6jcdy5w0f", + address5: "link14nvvrk4dz3k695t8740vqzjnvrwszwm69hw0ls", +}; + +/** Unused account */ +export const unused = { + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "AlPkJfV+nWxUCb2mPdMLSb/G9zNvywDir8CgxpAUoPjE", + }, + address: "link1g7gsgktl9yjqatacswlwvns5yzy4u5jehsx2pz", + accountNumber: 8, + sequence: 0, + balanceStaking: "20000000000", // 100000 STAKE + balanceFee: "100000000000", // 1000 CONY +}; + +export const validator = { + /** + * From first gentx's auth_info.signer_infos in scripts/simapp42/template/.simapp/config/genesis.json + * + * ``` + * jq ".app_state.genutil.gen_txs[0].auth_info.signer_infos[0].public_key" scripts/simapp42/template/.simapp/config/genesis.json + * ``` + */ + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "AsVtdRaoc8AsTmzIvRAKWSYgHXf97/HI4ls2wENFk9C8", + }, + /** + * delegator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/simapp42/template/.simapp/config/genesis.json + * + * ``` + * jq ".app_state.genutil.gen_txs[0].body.messages[0].delegator_address" scripts/simapp42/template/.simapp/config/genesis.json + * ``` + */ + delegatorAddress: "link1twsfmuj28ndph54k4nw8crwu8h9c8mh3rtx705", + /** + * validator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/simapp42/template/.simapp/config/genesis.json + * + * ``` + * jq ".app_state.genutil.gen_txs[0].body.messages[0].validator_address" scripts/simapp42/template/.simapp/config/genesis.json + * ``` + */ + validatorAddress: "linkvaloper1twsfmuj28ndph54k4nw8crwu8h9c8mh33lyrp8", + accountNumber: 10, + sequence: 1, +}; + +export const nonExistentAddress = "link1hvuxwh9sp2zlc3ee5nnhngln6auv4ak4kyuspq"; + +export const nonNegativeIntegerMatcher = /^[0-9]+$/; +export const tendermintIdMatcher = /^[0-9A-F]{64}$/; + +/** + * A class for testing clients using an Amino signer which modifies the transaction it receives before signing + */ +export class ModifyingSecp256k1HdWallet extends Secp256k1HdWallet { + public static override async fromMnemonic( + mnemonic: string, + options: Partial = {}, + ): Promise { + const mnemonicChecked = new EnglishMnemonic(mnemonic); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); + return new ModifyingSecp256k1HdWallet(mnemonicChecked, { ...options, seed: seed }); + } + + public override async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise { + const modifiedSignDoc = { + ...signDoc, + fee: { + amount: coins(3000, "cony"), + gas: "333333", + }, + memo: "This was modified", + }; + return super.signAmino(signerAddress, modifiedSignDoc); + } +} + +/** + * A class for testing clients using a direct signer which modifies the transaction it receives before signing + */ +export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet { + public static override async fromMnemonic( + mnemonic: string, + options: Partial = {}, + ): Promise { + const mnemonicChecked = new EnglishMnemonic(mnemonic); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); + return new ModifyingDirectSecp256k1HdWallet(mnemonicChecked, { ...options, seed: seed }); + } + + public override async signDirect(address: string, signDoc: SignDoc): Promise { + const txBody = TxBody.decode(signDoc.bodyBytes); + const modifiedTxBody = TxBody.fromPartial({ + ...txBody, + memo: "This was modified", + }); + const authInfo = AuthInfo.decode(signDoc.authInfoBytes); + const signers = authInfo.signerInfos.map((signerInfo) => ({ + pubkey: signerInfo.publicKey!, + sequence: signerInfo.sequence.toNumber(), + })); + const modifiedFeeAmount = coins(3000, "cony"); + const modifiedGasLimit = 333333; + const modifiedSignDoc = { + ...signDoc, + bodyBytes: Uint8Array.from(TxBody.encode(modifiedTxBody).finish()), + authInfoBytes: makeAuthInfoBytes( + signers, + modifiedFeeAmount, + modifiedGasLimit, + SignMode.SIGN_MODE_DIRECT, + ), + }; + return super.signDirect(address, modifiedSignDoc); + } +} diff --git a/packages/finschia/src/utils.ts b/packages/finschia/src/utils.ts new file mode 100644 index 000000000..f2a4562b0 --- /dev/null +++ b/packages/finschia/src/utils.ts @@ -0,0 +1,7 @@ +import { Uint64 } from "@cosmjs/math"; +import Long from "long"; + +export function longify(value: string | number | Long | Uint64): Long { + const checkedValue = Uint64.fromString(value.toString()); + return Long.fromBytesBE([...checkedValue.toBytesBigEndian()], true); +} From db993af751fd0d13c499108706befc61f574697e Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Tue, 6 Sep 2022 23:44:37 +0900 Subject: [PATCH 03/15] feat: add finschia client --- .pnp.cjs | 28 + ...rgate-npm-0.28.4-6628baefb3-6b1142c45f.zip | 3 + packages/finschia/package.json | 3 + .../finschia/src/cosmwasm-testutils.spec.ts | 240 +++++++ packages/finschia/src/finschiaClient.ts | 590 +++++++++++++--- .../src/modules/collection/queries.spec.ts | 6 +- .../src/modules/feegrant/queries.spec.ts | 4 +- .../src/modules/foundation/queries.spec.ts | 8 +- packages/finschia/src/modules/index.ts | 28 + .../src/modules/token/queries.spec.ts | 7 +- .../src/modules/wasm/aminomessages.spec.ts | 356 ++++++++++ .../src/modules/wasm/aminomessages.ts | 242 +++++++ .../finschia/src/modules/wasm/messages.ts | 92 +++ .../finschia/src/modules/wasm/queries.spec.ts | 450 ++++++++++++ packages/finschia/src/modules/wasm/queries.ts | 136 ++++ .../finschia/src/signingfinschiaclient.ts | 663 ++++++++++++++++++ packages/finschia/src/testdata/contract.json | 4 + yarn.lock | 23 + 18 files changed, 2787 insertions(+), 96 deletions(-) create mode 100644 .yarn/cache/@cosmjs-cosmwasm-stargate-npm-0.28.4-6628baefb3-6b1142c45f.zip create mode 100644 packages/finschia/src/cosmwasm-testutils.spec.ts create mode 100644 packages/finschia/src/modules/wasm/aminomessages.spec.ts create mode 100644 packages/finschia/src/modules/wasm/aminomessages.ts create mode 100644 packages/finschia/src/modules/wasm/messages.ts create mode 100644 packages/finschia/src/modules/wasm/queries.spec.ts create mode 100644 packages/finschia/src/modules/wasm/queries.ts create mode 100644 packages/finschia/src/signingfinschiaclient.ts create mode 100644 packages/finschia/src/testdata/contract.json diff --git a/.pnp.cjs b/.pnp.cjs index c40c6769e..c066578ec 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -156,6 +156,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "@cosmjs/amino", "npm:0.28.4" ], + [ + "@cosmjs/cosmwasm-stargate", + "npm:0.28.4" + ], [ "@cosmjs/crypto", "npm:0.28.4" @@ -2937,6 +2941,27 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@cosmjs/cosmwasm-stargate", [ + ["npm:0.28.4", { + "packageLocation": "./.yarn/cache/@cosmjs-cosmwasm-stargate-npm-0.28.4-6628baefb3-6b1142c45f.zip/node_modules/@cosmjs/cosmwasm-stargate/", + "packageDependencies": [ + ["@cosmjs/cosmwasm-stargate", "npm:0.28.4"], + ["@cosmjs/amino", "npm:0.28.4"], + ["@cosmjs/crypto", "npm:0.28.4"], + ["@cosmjs/encoding", "npm:0.28.4"], + ["@cosmjs/math", "npm:0.28.4"], + ["@cosmjs/proto-signing", "npm:0.28.4"], + ["@cosmjs/stargate", "npm:0.28.4"], + ["@cosmjs/tendermint-rpc", "npm:0.28.4"], + ["@cosmjs/utils", "npm:0.28.4"], + ["cosmjs-types", "npm:0.4.1"], + ["long", "npm:4.0.0"], + ["pako", "npm:2.0.4"], + ["protobufjs", "npm:6.10.2"] + ], + "linkType": "HARD", + }] + ]], ["@cosmjs/crypto", [ ["npm:0.28.4", { "packageLocation": "./.yarn/cache/@cosmjs-crypto-npm-0.28.4-a76962f744-2ea35cc3cc.zip/node_modules/@cosmjs/crypto/", @@ -3271,6 +3296,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [ ["@lbmjs/finschia", "workspace:packages/finschia"], ["@cosmjs/amino", "npm:0.28.4"], + ["@cosmjs/cosmwasm-stargate", "npm:0.28.4"], ["@cosmjs/crypto", "npm:0.28.4"], ["@cosmjs/encoding", "npm:0.28.4"], ["@cosmjs/math", "npm:0.28.4"], @@ -3286,6 +3312,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/karma-jasmine-html-reporter", "npm:1.7.0"], ["@types/long", "npm:4.0.1"], ["@types/node", "npm:15.14.9"], + ["@types/pako", "npm:1.0.3"], ["@typescript-eslint/eslint-plugin", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:5.17.0"], ["@typescript-eslint/parser", "virtual:347c8c0906ec181c138f6225d81c4df52dece4761135b9026ba511abbecae55e3eba9b6cc78fd5c41b05b03d91a1f59c3e7e772c052dd04c102a2b687d6e5011#npm:5.17.0"], ["cosmjs-types", "npm:0.4.0"], @@ -3309,6 +3336,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["lbmjs-types", "npm:0.46.0-rc7"], ["long", "npm:4.0.0"], ["nyc", "npm:15.1.0"], + ["pako", "npm:2.0.4"], ["prettier", "npm:2.5.1"], ["protobufjs", "npm:6.10.2"], ["readonly-date", "npm:1.0.0"], diff --git a/.yarn/cache/@cosmjs-cosmwasm-stargate-npm-0.28.4-6628baefb3-6b1142c45f.zip b/.yarn/cache/@cosmjs-cosmwasm-stargate-npm-0.28.4-6628baefb3-6b1142c45f.zip new file mode 100644 index 000000000..66793de99 --- /dev/null +++ b/.yarn/cache/@cosmjs-cosmwasm-stargate-npm-0.28.4-6628baefb3-6b1142c45f.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4022b674fa5988df111d3ae3b5668aa2f2fd48bc714f937105f73a58a488748 +size 174300 diff --git a/packages/finschia/package.json b/packages/finschia/package.json index a94a6a626..94ef9e2a9 100644 --- a/packages/finschia/package.json +++ b/packages/finschia/package.json @@ -36,6 +36,7 @@ }, "dependencies": { "@cosmjs/amino": "0.28.4", + "@cosmjs/cosmwasm-stargate": "0.28.4", "@cosmjs/encoding": "0.28.4", "@cosmjs/math": "0.28.4", "@cosmjs/proto-signing": "0.28.4", @@ -45,6 +46,7 @@ "cosmjs-types": "0.4.0", "lbmjs-types": "^0.46.0-rc7", "long": "^4.0.0", + "pako": "^2.0.2", "protobufjs": "~6.10.2", "xstream": "^11.14.0" }, @@ -58,6 +60,7 @@ "@types/karma-jasmine-html-reporter": "^1", "@types/long": "^4.0.1", "@types/node": "^15.0.1", + "@types/pako": "^1.0.1", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.13.0", "eslint": "^7.5", diff --git a/packages/finschia/src/cosmwasm-testutils.spec.ts b/packages/finschia/src/cosmwasm-testutils.spec.ts new file mode 100644 index 000000000..4792de7d6 --- /dev/null +++ b/packages/finschia/src/cosmwasm-testutils.spec.ts @@ -0,0 +1,240 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { AminoSignResponse, Secp256k1HdWallet, Secp256k1HdWalletOptions, StdSignDoc } from "@cosmjs/amino"; +import { Bip39, EnglishMnemonic, Random } from "@cosmjs/crypto"; +import { fromBase64, toBech32 } from "@cosmjs/encoding"; +import { + DirectSecp256k1HdWallet, + DirectSecp256k1HdWalletOptions, + DirectSignResponse, + makeAuthInfoBytes, +} from "@cosmjs/proto-signing"; +import { + AuthExtension, + BankExtension, + calculateFee, + coins, + GasPrice, + QueryClient, + setupAuthExtension, + setupBankExtension, +} from "@cosmjs/stargate"; +import { SigningStargateClientOptions } from "@cosmjs/stargate"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; +import { AuthInfo, SignDoc, TxBody } from "cosmjs-types/cosmos/tx/v1beta1/tx"; + +import { setupWasmExtension, WasmExtension } from "./modules"; +import hackatom from "./testdata/contract.json"; + +export const defaultGasPrice = GasPrice.fromString("0.025cony"); +export const defaultSendFee = calculateFee(100_000, defaultGasPrice); +export const defaultUploadFee = calculateFee(1_500_000, defaultGasPrice); +export const defaultInstantiateFee = calculateFee(500_000, defaultGasPrice); +export const defaultUploadAndInstantiateFee = calculateFee(2_000_000, defaultGasPrice); +export const defaultExecuteFee = calculateFee(200_000, defaultGasPrice); +export const defaultMigrateFee = calculateFee(200_000, defaultGasPrice); +export const defaultUpdateAdminFee = calculateFee(80_000, defaultGasPrice); +export const defaultClearAdminFee = calculateFee(80_000, defaultGasPrice); + +/** An internal testing type. SigningCosmWasmClient has a similar but different interface */ +export interface ContractUploadInstructions { + /** The wasm bytecode */ + readonly data: Uint8Array; +} + +export const wasmd = { + blockTime: 1_000, // ms + chainId: "simd-testing", + endpoint: "localhost:26658", + prefix: "link", + validator: { + address: "linkvaloper146asaycmtydq45kxc8evntqfgepagygeddajpy", + }, +}; + +/** Setting to speed up testing */ +export const defaultSigningClientOptions: SigningStargateClientOptions = { + broadcastPollIntervalMs: 300, + broadcastTimeoutMs: 8_000, +}; + +export function getHackatom(): ContractUploadInstructions { + return { + data: fromBase64(hackatom.data), + }; +} + +export function makeRandomAddress(): string { + return toBech32("link", Random.getBytes(20)); +} + +export const tendermintIdMatcher = /^[0-9A-F]{64}$/; +/** @see https://rgxdb.com/r/1NUN74O6 */ +export const base64Matcher = + /^(?:[a-zA-Z0-9+/]{4})*(?:|(?:[a-zA-Z0-9+/]{3}=)|(?:[a-zA-Z0-9+/]{2}==)|(?:[a-zA-Z0-9+/]{1}===))$/; +// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 +export const bech32AddressMatcher = /^[\x21-\x7e]{1,83}1[02-9ac-hj-np-z]{38,58}$/; + +export const alice = { + mnemonic: + "mind flame tobacco sense move hammer drift crime ring globe art gaze cinnamon helmet cruise special produce notable negative wait path scrap recall have", + pubkey0: { + type: "tendermint/PubKeySecp256k1", + value: "AgT2QPS4Eu6M+cfHeba+3tumsM/hNEBGdM7nRojSZRjF", + }, + address0: "link146asaycmtydq45kxc8evntqfgepagygelel00h", + address1: "link1aaffxdz4dwcnjzumjm7h89yjw5c5wul88zvzuu", + address2: "link1ey0w0xj9v48vk82ht6mhqdlh9wqkx8enkpjwpr", + address3: "link1dfyywjglcfptn72axxhsslpy8ep6wq7wujasma", + address4: "link1equ4n3uwyhapak5g3leq0avz85k0q6jcdy5w0f", +}; + +/** Unused account */ +export const unused = { + pubkey: { + type: "tendermint/PubKeySecp256k1", + value: "A7Tvuh48+JzNyBnTeK2Qw987f5FqFHK/QH65pTVsZvuh", + }, + address: "link1g7gsgktl9yjqatacswlwvns5yzy4u5jehsx2pz", + accountNumber: 8, + sequence: 0, +}; + +export const validator = { + /** + * delegator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/wasmd/template/.wasmd/config/genesis.json + * + * `jq ".app_state.genutil.gen_txs[0].body.messages[0].delegator_address" scripts/wasmd/template/.wasmd/config/genesis.json` + */ + delegatorAddress: "link1twsfmuj28ndph54k4nw8crwu8h9c8mh3rtx705", + /** + * validator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/wasmd/template/.wasmd/config/genesis.json + * + * `jq ".app_state.genutil.gen_txs[0].body.messages[0].validator_address" scripts/wasmd/template/.wasmd/config/genesis.json` + */ + validatorAddress: "linkvaloper1twsfmuj28ndph54k4nw8crwu8h9c8mh33lyrp8", + sequence: 1, +}; + +/** Deployed as part of scripts/wasmd/init.sh */ +export const deployedHackatom = { + codeId: 1, + checksum: "470c5b703a682f778b8b088d48169b8d6e43f7f44ac70316692cdbe69e6605e3", + instances: [ + { + beneficiary: alice.address0, + address: "link14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sgf2vn8", + label: "From deploy_hackatom.js (0)", + }, + { + beneficiary: alice.address1, + address: "link1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrshuxemw", + label: "From deploy_hackatom.js (1)", + }, + { + beneficiary: alice.address2, + address: "link1yyca08xqdgvjz0psg56z67ejh9xms6l436u8y58m82npdqqhmmtq6cjue5", + label: "From deploy_hackatom.js (2)", + }, + ], +}; + +/** Deployed as part of scripts/wasmd/init.sh */ +export const deployedIbcReflect = { + codeId: 2, + instances: [ + { + address: "link1aakfpghcanxtc45gpqlx8j3rq0zcpyf49qmhm9mdjrfx036h4z5s782d42", + ibcPortId: "wasm.link1aakfpghcanxtc45gpqlx8j3rq0zcpyf49qmhm9mdjrfx036h4z5s782d42", + }, + ], +}; + +export function wasmdEnabled(): boolean { + return !!process.env.WASMD_ENABLED; +} + +export function pendingWithoutWasmd(): void { + if (!wasmdEnabled()) { + return pending("Set WASMD_ENABLED to enable Wasmd-based tests"); + } +} + +/** Returns first element. Throws if array has a different length than 1. */ +export function fromOneElementArray(elements: ArrayLike): T { + if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`); + return elements[0]; +} + +export async function makeWasmClient( + endpoint: string, +): Promise { + const tmClient = await Tendermint34Client.connect(endpoint); + return QueryClient.withExtensions(tmClient, setupAuthExtension, setupBankExtension, setupWasmExtension); +} + +/** + * A class for testing clients using an Amino signer which modifies the transaction it receives before signing + */ +export class ModifyingSecp256k1HdWallet extends Secp256k1HdWallet { + public static override async fromMnemonic( + mnemonic: string, + options: Partial = {}, + ): Promise { + const mnemonicChecked = new EnglishMnemonic(mnemonic); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); + return new ModifyingSecp256k1HdWallet(mnemonicChecked, { ...options, seed: seed }); + } + + public override async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise { + const modifiedSignDoc = { + ...signDoc, + fee: { + amount: coins(3000, "cony"), + gas: "333333", + }, + memo: "This was modified", + }; + return super.signAmino(signerAddress, modifiedSignDoc); + } +} + +/** + * A class for testing clients using a direct signer which modifies the transaction it receives before signing + */ +export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet { + public static override async fromMnemonic( + mnemonic: string, + options: Partial = {}, + ): Promise { + const mnemonicChecked = new EnglishMnemonic(mnemonic); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); + return new ModifyingDirectSecp256k1HdWallet(mnemonicChecked, { ...options, seed: seed }); + } + + public override async signDirect(address: string, signDoc: SignDoc): Promise { + const txBody = TxBody.decode(signDoc.bodyBytes); + const modifiedTxBody = TxBody.fromPartial({ + ...txBody, + memo: "This was modified", + }); + const authInfo = AuthInfo.decode(signDoc.authInfoBytes); + const signers = authInfo.signerInfos.map((signerInfo) => ({ + pubkey: signerInfo.publicKey!, + sequence: signerInfo.sequence.toNumber(), + })); + const modifiedFeeAmount = coins(3000, "cony"); + const modifiedGasLimit = 333333; + const modifiedSignDoc = { + ...signDoc, + bodyBytes: Uint8Array.from(TxBody.encode(modifiedTxBody).finish()), + authInfoBytes: makeAuthInfoBytes( + signers, + modifiedFeeAmount, + modifiedGasLimit, + SignMode.SIGN_MODE_DIRECT, + ), + }; + return super.signDirect(address, modifiedSignDoc); + } +} diff --git a/packages/finschia/src/finschiaClient.ts b/packages/finschia/src/finschiaClient.ts index 590196d5c..a112c2584 100644 --- a/packages/finschia/src/finschiaClient.ts +++ b/packages/finschia/src/finschiaClient.ts @@ -1,118 +1,542 @@ -import { GeneratedType, OfflineSigner, Registry } from "@cosmjs/proto-signing"; +import { addCoins } from "@cosmjs/amino"; +import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from "@cosmjs/cosmwasm-stargate"; +import { fromAscii, toHex } from "@cosmjs/encoding"; +import { Uint53 } from "@cosmjs/math"; import { - defaultRegistryTypes, + Account, + accountFromAny, + AccountParser, + AuthExtension, + BankExtension, + Block, + DeliverTxResponse, + DistributionExtension, + GovExtension, + IndexedTx, + isSearchByHeightQuery, + isSearchBySentFromOrToQuery, + isSearchByTagsQuery, + MintExtension, QueryClient, - SigningStargateClient, - SigningStargateClientOptions, + SearchTxFilter, + SearchTxQuery, + SequenceResponse, + setupAuthExtension, + setupBankExtension, + setupDistributionExtension, + setupGovExtension, + setupMintExtension, + setupStakingExtension, + setupTxExtension, + StakingExtension, + StargateClientOptions, + TimeoutError, + TxExtension, } from "@cosmjs/stargate"; -import { HttpEndpoint, Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; +import { assert, sleep } from "@cosmjs/utils"; +import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; +import { QueryDelegatorDelegationsResponse } from "cosmjs-types/cosmos/staking/v1beta1/query"; +import { DelegationResponse } from "cosmjs-types/cosmos/staking/v1beta1/staking"; +import { + CodeInfoResponse, + QueryCodesResponse, + QueryContractsByCodeResponse, +} from "cosmjs-types/cosmwasm/wasm/v1/query"; +import { ContractCodeHistoryOperationType } from "cosmjs-types/cosmwasm/wasm/v1/types"; import { CollectionExtension, - collectionTypes, EvidenceExtension, FeeGrantExtension, - feegrantTypes, FoundationExtension, - foundationTypes, IbcExtension, - ibcTypes, + JsonObject, setupCollectionExtension, setupEvidenceExtension, setupFeeGrantExtension, setupFoundationExtension, setupIbcExtension, setupTokenExtension, + setupWasmExtension, TokenExtension, - tokenTypes, + WasmExtension, } from "./modules"; -export const finschiaRegistryTypes: ReadonlyArray<[string, GeneratedType]> = [ - ...defaultRegistryTypes, - ...feegrantTypes, - ...ibcTypes, - ...tokenTypes, - ...foundationTypes, - ...collectionTypes, -]; - -function createDefaultRegistry(): Registry { - return new Registry(finschiaRegistryTypes); +type QueryClientWithExtensions = QueryClient & + AuthExtension & + BankExtension & + CollectionExtension & + DistributionExtension & + EvidenceExtension & + FeeGrantExtension & + FoundationExtension & + GovExtension & + IbcExtension & + MintExtension & + StakingExtension & + TokenExtension & + TxExtension & + WasmExtension; + +function createQueryClientWithExtensions(tmClient: Tendermint34Client): QueryClientWithExtensions { + return QueryClient.withExtensions( + tmClient, + setupAuthExtension, + setupBankExtension, + // setupAuthzExtension, this is omitted in cosmjs export + // setupSlashingExtension, this is omitted in cosmjs export + setupCollectionExtension, + setupDistributionExtension, + setupEvidenceExtension, + setupFeeGrantExtension, + setupFoundationExtension, + setupGovExtension, + setupIbcExtension, + setupMintExtension, + setupStakingExtension, + setupTokenExtension, + setupTxExtension, + setupWasmExtension, + ); +} + +/** Use for testing only */ +export interface PrivateFinschiaClient { + readonly tmClient: Tendermint34Client | undefined; } -export class FinschiaClient extends SigningStargateClient { - public override readonly registry: Registry; - - private readonly finschiaQueryClient: - | (QueryClient & - CollectionExtension & - EvidenceExtension & - FeeGrantExtension & - FoundationExtension & - IbcExtension & - TokenExtension) - | undefined; - - public static override async connectWithSigner( + +export class FinschiaClient { + private readonly tmClient: Tendermint34Client | undefined; + private readonly queryClient: QueryClientWithExtensions | undefined; + private readonly codesCache = new Map(); + private chainId: string | undefined; + private readonly accountParser: AccountParser; + + public static async connect( endpoint: string | HttpEndpoint, - signer: OfflineSigner, - options: SigningStargateClientOptions = {}, - ): Promise { + options: StargateClientOptions = {}, + ): Promise { const tmClient = await Tendermint34Client.connect(endpoint); - return new FinschiaClient(tmClient, signer, options); + return new FinschiaClient(tmClient, options); } - public static override async offline( - signer: OfflineSigner, - options: SigningStargateClientOptions = {}, - ): Promise { - return new FinschiaClient(undefined, signer, options); + protected constructor(tmClient: Tendermint34Client | undefined, options: StargateClientOptions) { + if (tmClient) { + this.tmClient = tmClient; + this.queryClient = createQueryClientWithExtensions(tmClient); + } + const { accountParser = accountFromAny } = options; + this.accountParser = accountParser; } - protected constructor( - tmClient: Tendermint34Client | undefined, - signer: OfflineSigner, - options: SigningStargateClientOptions, - ) { - super(tmClient, signer, options); - - const { registry = createDefaultRegistry() } = options; - this.registry = registry; + protected getTmClient(): Tendermint34Client | undefined { + return this.tmClient; + } - if (tmClient) { - this.finschiaQueryClient = QueryClient.withExtensions( - tmClient, - setupCollectionExtension, - setupEvidenceExtension, - setupFeeGrantExtension, - setupFoundationExtension, - setupIbcExtension, - setupTokenExtension, + protected forceGetTmClient(): Tendermint34Client { + if (!this.tmClient) { + throw new Error( + "Tendermint client not available. You cannot use online functionality in offline mode.", ); } + return this.tmClient; } - protected getFinschiaQueryClient(): - | (QueryClient & - CollectionExtension & - EvidenceExtension & - FeeGrantExtension & - FoundationExtension & - IbcExtension & - TokenExtension) - | undefined { - return this.finschiaQueryClient; - } - - protected forceGetFinschiaQueryClient(): QueryClient & - CollectionExtension & - EvidenceExtension & - FeeGrantExtension & - FoundationExtension & - IbcExtension & - TokenExtension { - if (!this.finschiaQueryClient) { + protected getQueryClient(): QueryClientWithExtensions | undefined { + return this.queryClient; + } + + protected forceGetQueryClient(): QueryClientWithExtensions { + if (!this.queryClient) { throw new Error("Query client not available. You cannot use online functionality in offline mode."); } - return this.finschiaQueryClient; + return this.queryClient; + } + + public async getChainId(): Promise { + if (!this.chainId) { + const response = await this.forceGetTmClient().status(); + const chainId = response.nodeInfo.network; + if (!chainId) throw new Error("Chain ID must not be empty"); + this.chainId = chainId; + } + + return this.chainId; + } + + public async getHeight(): Promise { + const status = await this.forceGetTmClient().status(); + return status.syncInfo.latestBlockHeight; + } + + public async getAccount(searchAddress: string): Promise { + try { + const account = await this.forceGetQueryClient().auth.account(searchAddress); + return account ? this.accountParser(account) : null; + } catch (error: any) { + if (/rpc error: code = NotFound/i.test(error.toString())) { + return null; + } + throw error; + } + } + + public async getSequence(address: string): Promise { + const account = await this.getAccount(address); + if (!account) { + throw new Error( + "Account does not exist on chain. Send some tokens there before trying to query sequence.", + ); + } + return { + accountNumber: account.accountNumber, + sequence: account.sequence, + }; + } + + public async getBlock(height?: number): Promise { + const response = await this.forceGetTmClient().block(height); + return { + id: toHex(response.blockId.hash).toUpperCase(), + header: { + version: { + block: new Uint53(response.block.header.version.block).toString(), + app: new Uint53(response.block.header.version.app).toString(), + }, + height: response.block.header.height, + chainId: response.block.header.chainId, + time: toRfc3339WithNanoseconds(response.block.header.time), + }, + txs: response.block.txs, + }; + } + + public async getBalance(address: string, searchDenom: string): Promise { + return this.forceGetQueryClient().bank.balance(address, searchDenom); + } + + /** + * Queries all balances for all denoms that belong to this address. + * + * Uses the grpc queries (which iterates over the store internally), and we cannot get + * proofs from such a method. + */ + public async getAllBalances(address: string): Promise { + return this.forceGetQueryClient().bank.allBalances(address); + } + + public async getBalanceStaked(address: string): Promise { + const allDelegations = []; + let startAtKey: Uint8Array | undefined = undefined; + do { + const { delegationResponses, pagination }: QueryDelegatorDelegationsResponse = + await this.forceGetQueryClient().staking.delegatorDelegations(address, startAtKey); + + const loadedDelegations = delegationResponses || []; + allDelegations.push(...loadedDelegations); + startAtKey = pagination?.nextKey; + } while (startAtKey !== undefined && startAtKey.length !== 0); + + const sumValues = allDelegations.reduce( + (previousValue: Coin | null, currentValue: DelegationResponse): Coin => { + // Safe because field is set to non-nullable (https://github.com/cosmos/cosmos-sdk/blob/v0.45.3/proto/cosmos/staking/v1beta1/staking.proto#L295) + assert(currentValue.balance); + return previousValue !== null ? addCoins(previousValue, currentValue.balance) : currentValue.balance; + }, + null, + ); + + return sumValues; + } + + public async getDelegation(delegatorAddress: string, validatorAddress: string): Promise { + let delegatedAmount: Coin | undefined; + try { + delegatedAmount = ( + await this.forceGetQueryClient().staking.delegation(delegatorAddress, validatorAddress) + ).delegationResponse?.balance; + } catch (e: any) { + if (e.toString().includes("key not found")) { + // ignore, `delegatedAmount` remains undefined + } else { + throw e; + } + } + return delegatedAmount || null; + } + + public async getTx(id: string): Promise { + const results = await this.txsQuery(`tx.hash='${id}'`); + return results[0] ?? null; + } + + public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise { + const minHeight = filter.minHeight || 0; + const maxHeight = filter.maxHeight || Number.MAX_SAFE_INTEGER; + + if (maxHeight < minHeight) return []; // optional optimization + + function withFilters(originalQuery: string): string { + return `${originalQuery} AND tx.height>=${minHeight} AND tx.height<=${maxHeight}`; + } + + let txs: readonly IndexedTx[]; + + if (isSearchByHeightQuery(query)) { + txs = + query.height >= minHeight && query.height <= maxHeight + ? await this.txsQuery(`tx.height=${query.height}`) + : []; + } else if (isSearchBySentFromOrToQuery(query)) { + const sentQuery = withFilters(`message.module='bank' AND transfer.sender='${query.sentFromOrTo}'`); + const receivedQuery = withFilters( + `message.module='bank' AND transfer.recipient='${query.sentFromOrTo}'`, + ); + const [sent, received] = await Promise.all( + [sentQuery, receivedQuery].map((rawQuery) => this.txsQuery(rawQuery)), + ); + const sentHashes = sent.map((t) => t.hash); + txs = [...sent, ...received.filter((t) => !sentHashes.includes(t.hash))]; + } else if (isSearchByTagsQuery(query)) { + const rawQuery = withFilters(query.tags.map((t) => `${t.key}='${t.value}'`).join(" AND ")); + txs = await this.txsQuery(rawQuery); + } else { + throw new Error("Unknown query type"); + } + + const filtered = txs.filter((tx) => tx.height >= minHeight && tx.height <= maxHeight); + return filtered; + } + + public disconnect(): void { + if (this.tmClient) this.tmClient.disconnect(); + } + + /** + * Broadcasts a signed transaction to the network and monitors its inclusion in a block. + * + * If broadcasting is rejected by the node for some reason (e.g. because of a CheckTx failure), + * an error is thrown. + * + * If the transaction is not included in a block before the provided timeout, this errors with a `TimeoutError`. + * + * If the transaction is included in a block, a `DeliverTxResponse` is returned. The caller then + * usually needs to check for execution success or failure. + */ + public async broadcastTx( + tx: Uint8Array, + timeoutMs = 60_000, + pollIntervalMs = 3_000, + ): Promise { + let timedOut = false; + const txPollTimeout = setTimeout(() => { + timedOut = true; + }, timeoutMs); + + const pollForTx = async (txId: string): Promise => { + if (timedOut) { + throw new TimeoutError( + `Transaction with ID ${txId} was submitted but was not yet found on the chain. You might want to check later. There was a wait of ${ + timeoutMs / 10000 + } seconds.`, + txId, + ); + } + await sleep(pollIntervalMs); + const result = await this.getTx(txId); + return result + ? { + code: result.code, + height: result.height, + rawLog: result.rawLog, + transactionHash: txId, + gasUsed: result.gasUsed, + gasWanted: result.gasWanted, + } + : pollForTx(txId); + }; + + const broadcasted = await this.forceGetTmClient().broadcastTxSync({ tx }); + if (broadcasted.code) { + return Promise.reject( + new Error( + `Broadcasting transaction failed with code ${broadcasted.code} (codespace: ${broadcasted.codeSpace}). Log: ${broadcasted.log}`, + ), + ); + } + const transactionId = toHex(broadcasted.hash).toUpperCase(); + return new Promise((resolve, reject) => + pollForTx(transactionId).then( + (value) => { + clearTimeout(txPollTimeout); + resolve(value); + }, + (error) => { + clearTimeout(txPollTimeout); + reject(error); + }, + ), + ); + } + + /** + * getCodes() returns all codes and is just looping through all pagination pages. + * + * This is potentially inefficient and advanced apps should consider creating + * their own query client to handle pagination together with the app's screens. + */ + public async getCodes(): Promise { + const allCodes = []; + + let startAtKey: Uint8Array | undefined = undefined; + do { + const { codeInfos, pagination }: QueryCodesResponse = + await this.forceGetQueryClient().wasm.listCodeInfo(startAtKey); + const loadedCodes = codeInfos || []; + allCodes.push(...loadedCodes); + startAtKey = pagination?.nextKey; + } while (startAtKey?.length !== 0); + + return allCodes.map((entry: CodeInfoResponse): Code => { + assert(entry.creator && entry.codeId && entry.dataHash, "entry incomplete"); + return { + id: entry.codeId.toNumber(), + creator: entry.creator, + checksum: toHex(entry.dataHash), + }; + }); + } + + public async getCodeDetails(codeId: number): Promise { + const cached = this.codesCache.get(codeId); + if (cached) return cached; + + const { codeInfo, data } = await this.forceGetQueryClient().wasm.getCode(codeId); + assert( + codeInfo && codeInfo.codeId && codeInfo.creator && codeInfo.dataHash && data, + "codeInfo missing or incomplete", + ); + const codeDetails: CodeDetails = { + id: codeInfo.codeId.toNumber(), + creator: codeInfo.creator, + checksum: toHex(codeInfo.dataHash), + data: data, + }; + this.codesCache.set(codeId, codeDetails); + return codeDetails; + } + + /** + * getContracts() returns all contract instances for one code and is just looping through all pagination pages. + * + * This is potentially inefficient and advanced apps should consider creating + * their own query client to handle pagination together with the app's screens. + */ + public async getContracts(codeId: number): Promise { + const allContracts = []; + let startAtKey: Uint8Array | undefined = undefined; + do { + const { contracts, pagination }: QueryContractsByCodeResponse = + await this.forceGetQueryClient().wasm.listContractsByCodeId(codeId, startAtKey); + const loadedContracts = contracts || []; + allContracts.push(...loadedContracts); + startAtKey = pagination?.nextKey; + } while (startAtKey?.length !== 0 && startAtKey !== undefined); + + return allContracts; + } + + /** + * Throws an error if no contract was found at the address + */ + public async getContract(address: string): Promise { + const { address: retrievedAddress, contractInfo } = await this.forceGetQueryClient().wasm.getContractInfo( + address, + ); + if (!contractInfo) throw new Error(`No contract found at address "${address}"`); + assert(retrievedAddress, "address missing"); + assert(contractInfo.codeId && contractInfo.creator && contractInfo.label, "contractInfo incomplete"); + return { + address: retrievedAddress, + codeId: contractInfo.codeId.toNumber(), + creator: contractInfo.creator, + admin: contractInfo.admin || undefined, + label: contractInfo.label, + ibcPortId: contractInfo.ibcPortId || undefined, + }; + } + + /** + * Throws an error if no contract was found at the address + */ + public async getContractCodeHistory(address: string): Promise { + const result = await this.forceGetQueryClient().wasm.getContractCodeHistory(address); + if (!result) throw new Error(`No contract history found for address "${address}"`); + const operations: Record = { + [ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT]: "Init", + [ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_GENESIS]: "Genesis", + [ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_MIGRATE]: "Migrate", + }; + return (result.entries || []).map((entry): ContractCodeHistoryEntry => { + assert(entry.operation && entry.codeId && entry.msg); + return { + operation: operations[entry.operation], + codeId: entry.codeId.toNumber(), + msg: JSON.parse(fromAscii(entry.msg)), + }; + }); + } + + /** + * Returns the data at the key if present (raw contract dependent storage data) + * or null if no data at this key. + * + * Promise is rejected when contract does not exist. + */ + public async queryContractRaw(address: string, key: Uint8Array): Promise { + // just test contract existence + await this.getContract(address); + + const { data } = await this.forceGetQueryClient().wasm.queryContractRaw(address, key); + return data ?? null; + } + + /** + * Makes a smart query on the contract, returns the parsed JSON document. + * + * Promise is rejected when contract does not exist. + * Promise is rejected for invalid query format. + * Promise is rejected for invalid response format. + */ + public async queryContractSmart(address: string, queryMsg: Record): Promise { + try { + return await this.forceGetQueryClient().wasm.queryContractSmart(address, queryMsg); + } catch (error) { + if (error instanceof Error) { + if (error.message.startsWith("not found: contract")) { + throw new Error(`No contract found at address "${address}"`); + } else { + throw error; + } + } else { + throw error; + } + } + } + + private async txsQuery(query: string): Promise { + const results = await this.forceGetTmClient().txSearchAll({ query: query }); + return results.txs.map((tx) => { + return { + height: tx.height, + hash: toHex(tx.hash).toUpperCase(), + code: tx.result.code, + rawLog: tx.result.log || "", + tx: tx.tx, + gasUsed: tx.result.gasUsed, + gasWanted: tx.result.gasWanted, + }; + }); } } diff --git a/packages/finschia/src/modules/collection/queries.spec.ts b/packages/finschia/src/modules/collection/queries.spec.ts index e1b6fa78f..aea6b7b6b 100644 --- a/packages/finschia/src/modules/collection/queries.spec.ts +++ b/packages/finschia/src/modules/collection/queries.spec.ts @@ -15,8 +15,8 @@ import { MsgTransferNFT, } from "lbmjs-types/lbm/collection/v1/tx"; -import { FinschiaClient } from "../../finschiaClient"; import { makeLinkPath } from "../../paths"; +import { SigningFinschiaClient } from "../../signingfinschiaclient"; import { defaultSigningClientOptions, faucet, @@ -59,7 +59,7 @@ describe("CollectionExtension (fungible token)", () => { hdPaths: [makeLinkPath(0)], prefix: simapp.prefix, }); - const client = await FinschiaClient.connectWithSigner( + const client = await SigningFinschiaClient.connectWithSigner( simapp.tendermintUrl, wallet, defaultSigningClientOptions, @@ -276,7 +276,7 @@ describe("CollectionExtension (non-fungible token)", () => { hdPaths: [makeLinkPath(0), makeLinkPath(1)], prefix: simapp.prefix, }); - const client = await FinschiaClient.connectWithSigner( + const client = await SigningFinschiaClient.connectWithSigner( simapp.tendermintUrl, wallet, defaultSigningClientOptions, diff --git a/packages/finschia/src/modules/feegrant/queries.spec.ts b/packages/finschia/src/modules/feegrant/queries.spec.ts index b951bac97..4cea8cede 100644 --- a/packages/finschia/src/modules/feegrant/queries.spec.ts +++ b/packages/finschia/src/modules/feegrant/queries.spec.ts @@ -6,8 +6,8 @@ import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { sleep } from "@cosmjs/utils"; import { BasicAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/feegrant"; -import { FinschiaClient } from "../../finschiaClient"; import { makeLinkPath } from "../../paths"; +import { SigningFinschiaClient } from "../../signingfinschiaclient"; import { defaultSigningClientOptions, faucet, @@ -39,7 +39,7 @@ describe("FeeGrantExtension", () => { hdPaths: [makeLinkPath(0)], prefix: simapp.prefix, }); - const client = await FinschiaClient.connectWithSigner( + const client = await SigningFinschiaClient.connectWithSigner( simapp.tendermintUrl, wallet, defaultSigningClientOptions, diff --git a/packages/finschia/src/modules/foundation/queries.spec.ts b/packages/finschia/src/modules/foundation/queries.spec.ts index 331b714e1..7d4f2249a 100644 --- a/packages/finschia/src/modules/foundation/queries.spec.ts +++ b/packages/finschia/src/modules/foundation/queries.spec.ts @@ -6,8 +6,8 @@ import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { sleep } from "@cosmjs/utils"; import { ThresholdDecisionPolicy } from "lbmjs-types/lbm/foundation/v1/foundation"; -import { FinschiaClient } from "../../finschiaClient"; import { makeLinkPath } from "../../paths"; +import { SigningFinschiaClient } from "../../signingfinschiaclient"; import { defaultSigningClientOptions, faucet, @@ -44,7 +44,7 @@ describe("FoundationExtension", () => { hdPaths: [makeLinkPath(0)], prefix: simapp.prefix, }); - const client = await FinschiaClient.connectWithSigner( + const client = await SigningFinschiaClient.connectWithSigner( simapp.tendermintUrl, wallet, defaultSigningClientOptions, @@ -93,7 +93,7 @@ describe("FoundationExtension grant and withdrawFromTreasury", () => { hdPaths: [makeLinkPath(0)], prefix: simapp.prefix, }); - const client = await FinschiaClient.connectWithSigner( + const client = await SigningFinschiaClient.connectWithSigner( simapp.tendermintUrl, wallet, defaultSigningClientOptions, @@ -153,7 +153,7 @@ describe("FoundationExtension DecisionPolicy", () => { hdPaths: [makeLinkPath(0)], prefix: simapp.prefix, }); - const client = await FinschiaClient.connectWithSigner( + const client = await SigningFinschiaClient.connectWithSigner( simapp.tendermintUrl, wallet, defaultSigningClientOptions, diff --git a/packages/finschia/src/modules/index.ts b/packages/finschia/src/modules/index.ts index c2d136517..d1a350649 100644 --- a/packages/finschia/src/modules/index.ts +++ b/packages/finschia/src/modules/index.ts @@ -30,3 +30,31 @@ export { ibcTypes, isMsgTransferEncodeObject, MsgTransferEncodeObject } from "./ export { IbcExtension, setupIbcExtension } from "./ibc/queries"; export { tokenTypes } from "./token/messages"; export { setupTokenExtension, TokenExtension } from "./token/queries"; +export { + AminoMsgClearAdmin, + AminoMsgExecuteContract, + AminoMsgInstantiateContract, + AminoMsgMigrateContract, + AminoMsgStoreCode, + AminoMsgUpdateAdmin, + cosmWasmTypes, + createWasmAminoConverters, +} from "./wasm/aminomessages"; +export { + isMsgClearAdminEncodeObject, + isMsgExecuteEncodeObject, + isMsgInstantiateContractEncodeObject, + isMsgMigrateEncodeObject, + isMsgStoreCodeAndInstantiateContract, + isMsgStoreCodeEncodeObject, + isMsgUpdateAdminEncodeObject, + MsgClearAdminEncodeObject, + MsgExecuteContractEncodeObject, + MsgInstantiateContractEncodeObject, + MsgMigrateContractEncodeObject, + MsgStoreCodeAndInstantiateContractEncodeObject, + MsgStoreCodeEncodeObject, + MsgUpdateAdminEncodeObject, + wasmTypes, +} from "./wasm/messages"; +export { JsonObject, setupWasmExtension, WasmExtension } from "./wasm/queries"; diff --git a/packages/finschia/src/modules/token/queries.spec.ts b/packages/finschia/src/modules/token/queries.spec.ts index cad382287..02b19fcd3 100644 --- a/packages/finschia/src/modules/token/queries.spec.ts +++ b/packages/finschia/src/modules/token/queries.spec.ts @@ -5,9 +5,8 @@ import { assert, sleep } from "@cosmjs/utils"; import { Permission } from "lbmjs-types/lbm/token/v1/token"; import { MsgIssue } from "lbmjs-types/lbm/token/v1/tx"; -import { FinschiaClient } from "../../finschiaClient"; - import { makeLinkPath } from "../../paths"; +import { SigningFinschiaClient } from "../../signingfinschiaclient"; import { defaultSigningClientOptions, faucet, @@ -43,7 +42,7 @@ describe("TokenExtension(Just Issue)", () => { hdPaths: [makeLinkPath(0)], prefix: simapp.prefix, }); - const client = await FinschiaClient.connectWithSigner( + const client = await SigningFinschiaClient.connectWithSigner( simapp.tendermintUrl, wallet, defaultSigningClientOptions, @@ -174,7 +173,7 @@ describe("TokenExtension", () => { hdPaths: [makeLinkPath(0)], prefix: simapp.prefix, }); - const client = await FinschiaClient.connectWithSigner( + const client = await SigningFinschiaClient.connectWithSigner( simapp.tendermintUrl, wallet, defaultSigningClientOptions, diff --git a/packages/finschia/src/modules/wasm/aminomessages.spec.ts b/packages/finschia/src/modules/wasm/aminomessages.spec.ts new file mode 100644 index 000000000..3c9b5b47e --- /dev/null +++ b/packages/finschia/src/modules/wasm/aminomessages.spec.ts @@ -0,0 +1,356 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { fromBase64, toUtf8 } from "@cosmjs/encoding"; +import { AminoTypes, coins } from "@cosmjs/stargate"; +import { + MsgClearAdmin, + MsgExecuteContract, + MsgInstantiateContract, + MsgMigrateContract, + MsgStoreCode, + MsgUpdateAdmin, +} from "cosmjs-types/cosmwasm/wasm/v1/tx"; +import Long from "long"; + +import { + AminoMsgClearAdmin, + AminoMsgExecuteContract, + AminoMsgInstantiateContract, + AminoMsgMigrateContract, + AminoMsgStoreCode, + AminoMsgUpdateAdmin, + createWasmAminoConverters, +} from "./aminomessages"; + +describe("AminoTypes", () => { + describe("toAmino", () => { + it("works for MsgStoreCode", () => { + const msg: MsgStoreCode = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + wasmByteCode: fromBase64("WUVMTE9XIFNVQk1BUklORQ=="), + instantiatePermission: undefined, + }; + const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ + typeUrl: "/cosmwasm.wasm.v1.MsgStoreCode", + value: msg, + }); + const expected: AminoMsgStoreCode = { + type: "wasm/MsgStoreCode", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + wasm_byte_code: "WUVMTE9XIFNVQk1BUklORQ==", + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgInstantiateContract", () => { + // With admin + { + const msg: MsgInstantiateContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + codeId: Long.fromString("12345"), + label: "sticky", + msg: toUtf8(`{"foo":"bar"}`), + funds: coins(1234, "cony"), + admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }; + const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: msg, + }); + const expected: AminoMsgInstantiateContract = { + type: "wasm/MsgInstantiateContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + code_id: "12345", + label: "sticky", + // todo: we need to fix the msg representation of lbm-sdk's wasm proto. refer https://github.com/CosmWasm/wasmd/pull/658 + msg: { foo: "bar" }, + funds: coins(1234, "cony"), + admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }, + }; + expect(aminoMsg).toEqual(expected); + } + + // Without admin + { + const msg: MsgInstantiateContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + codeId: Long.fromString("12345"), + label: "sticky", + msg: toUtf8(`{"foo":"bar"}`), + funds: coins(1234, "cony"), + admin: "", + }; + const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: msg, + }); + const expected: AminoMsgInstantiateContract = { + type: "wasm/MsgInstantiateContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + code_id: "12345", + label: "sticky", + msg: { foo: "bar" }, + funds: coins(1234, "cony"), + admin: undefined, + }, + }; + expect(aminoMsg).toEqual(expected); + } + }); + + it("works for MsgUpdateAdmin", () => { + const msg: MsgUpdateAdmin = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + newAdmin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }; + const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ + typeUrl: "/cosmwasm.wasm.v1.MsgUpdateAdmin", + value: msg, + }); + const expected: AminoMsgUpdateAdmin = { + type: "wasm/MsgUpdateAdmin", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + new_admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgClearAdmin", () => { + const msg: MsgClearAdmin = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }; + const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ + typeUrl: "/cosmwasm.wasm.v1.MsgClearAdmin", + value: msg, + }); + const expected: AminoMsgClearAdmin = { + type: "wasm/MsgClearAdmin", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgExecuteContract", () => { + const msg: MsgExecuteContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + msg: toUtf8(`{"foo":"bar"}`), + funds: coins(1234, "cony"), + }; + const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: msg, + }); + const expected: AminoMsgExecuteContract = { + type: "wasm/MsgExecuteContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + msg: { foo: "bar" }, + funds: coins(1234, "cony"), + }, + }; + expect(aminoMsg).toEqual(expected); + }); + + it("works for MsgMigrateContract", () => { + const msg: MsgMigrateContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + codeId: Long.fromString("98765"), + msg: toUtf8(`{"foo":"bar"}`), + }; + const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ + typeUrl: "/cosmwasm.wasm.v1.MsgMigrateContract", + value: msg, + }); + const expected: AminoMsgMigrateContract = { + type: "wasm/MsgMigrateContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + code_id: "98765", + msg: { foo: "bar" }, + }, + }; + expect(aminoMsg).toEqual(expected); + }); + }); + + describe("fromAmino", () => { + it("works for MsgStoreCode", () => { + const aminoMsg: AminoMsgStoreCode = { + type: "wasm/MsgStoreCode", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + wasm_byte_code: "WUVMTE9XIFNVQk1BUklORQ==", + }, + }; + const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); + const expectedValue: MsgStoreCode = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + wasmByteCode: fromBase64("WUVMTE9XIFNVQk1BUklORQ=="), + instantiatePermission: undefined, + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1.MsgStoreCode", + value: expectedValue, + }); + }); + + it("works for MsgInstantiateContract", () => { + // With admin + { + const aminoMsg: AminoMsgInstantiateContract = { + type: "wasm/MsgInstantiateContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + code_id: "12345", + label: "sticky", + msg: { foo: "bar" }, + funds: coins(1234, "cony"), + admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }, + }; + const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); + const expectedValue: MsgInstantiateContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + codeId: Long.fromString("12345"), + label: "sticky", + msg: toUtf8(`{"foo":"bar"}`), + funds: coins(1234, "cony"), + admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: expectedValue, + }); + } + + // Without admin + { + const aminoMsg: AminoMsgInstantiateContract = { + type: "wasm/MsgInstantiateContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + code_id: "12345", + label: "sticky", + msg: { foo: "bar" }, + funds: coins(1234, "cony"), + }, + }; + const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); + const expectedValue: MsgInstantiateContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + codeId: Long.fromString("12345"), + label: "sticky", + msg: toUtf8(`{"foo":"bar"}`), + funds: coins(1234, "cony"), + admin: "", + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: expectedValue, + }); + } + }); + + it("works for MsgUpdateAdmin", () => { + const aminoMsg: AminoMsgUpdateAdmin = { + type: "wasm/MsgUpdateAdmin", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + new_admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }, + }; + const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); + const expectedValue: MsgUpdateAdmin = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + newAdmin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1.MsgUpdateAdmin", + value: expectedValue, + }); + }); + + it("works for MsgClearAdmin", () => { + const aminoMsg: AminoMsgClearAdmin = { + type: "wasm/MsgClearAdmin", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }, + }; + const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); + const expectedValue: MsgClearAdmin = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1.MsgClearAdmin", + value: expectedValue, + }); + }); + + it("works for MsgExecuteContract", () => { + const aminoMsg: AminoMsgExecuteContract = { + type: "wasm/MsgExecuteContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + msg: { foo: "bar" }, + funds: coins(1234, "cony"), + }, + }; + const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); + const expectedValue: MsgExecuteContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + msg: toUtf8(`{"foo":"bar"}`), + funds: coins(1234, "cony"), + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: expectedValue, + }); + }); + + it("works for MsgMigrateContract", () => { + const aminoMsg: AminoMsgMigrateContract = { + type: "wasm/MsgMigrateContract", + value: { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + code_id: "98765", + msg: { foo: "bar" }, + }, + }; + const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); + const expectedValue: MsgMigrateContract = { + sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", + contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", + codeId: Long.fromString("98765"), + msg: toUtf8(`{"foo":"bar"}`), + }; + expect(msg).toEqual({ + typeUrl: "/cosmwasm.wasm.v1.MsgMigrateContract", + value: expectedValue, + }); + }); + }); +}); diff --git a/packages/finschia/src/modules/wasm/aminomessages.ts b/packages/finschia/src/modules/wasm/aminomessages.ts new file mode 100644 index 000000000..5ba5cac97 --- /dev/null +++ b/packages/finschia/src/modules/wasm/aminomessages.ts @@ -0,0 +1,242 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding"; +import { AminoConverters, Coin } from "@cosmjs/stargate"; +import { + MsgClearAdmin, + MsgExecuteContract, + MsgInstantiateContract, + MsgMigrateContract, + MsgStoreCode, + MsgUpdateAdmin, +} from "cosmjs-types/cosmwasm/wasm/v1/tx"; +import Long from "long"; + +// TODO: implement +/** + * @see https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/types.proto#L36-L41 + */ +type AccessConfig = never; + +/** + * The Amino JSON representation of [MsgStoreCode]. + * + * [MsgStoreCode]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L28-L39 + */ +export interface AminoMsgStoreCode { + type: "wasm/MsgStoreCode"; + value: { + /** Bech32 account address */ + readonly sender: string; + /** Base64 encoded Wasm */ + readonly wasm_byte_code: string; + readonly instantiate_permission?: AccessConfig; + }; +} + +/** + * The Amino JSON representation of [MsgExecuteContract]. + * + * [MsgExecuteContract]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L73-L86 + */ +export interface AminoMsgExecuteContract { + type: "wasm/MsgExecuteContract"; + value: { + /** Bech32 account address */ + readonly sender: string; + /** Bech32 account address */ + readonly contract: string; + /** Execute message as JavaScript object */ + readonly msg: any; + readonly funds: readonly Coin[]; + }; +} + +/** + * The Amino JSON representation of [MsgInstantiateContract]. + * + * [MsgInstantiateContract]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L46-L64 + */ +export interface AminoMsgInstantiateContract { + type: "wasm/MsgInstantiateContract"; + value: { + /** Bech32 account address */ + readonly sender: string; + /** ID of the Wasm code that was uploaded before */ + readonly code_id: string; + /** Human-readable label for this contract */ + readonly label: string; + /** Instantiate message as JavaScript object */ + readonly msg: any; + readonly funds: readonly Coin[]; + /** Bech32-encoded admin address */ + readonly admin?: string; + }; +} + +/** + * The Amino JSON representation of [MsgMigrateContract]. + * + * [MsgMigrateContract]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L94-L104 + */ +export interface AminoMsgMigrateContract { + type: "wasm/MsgMigrateContract"; + value: { + /** Bech32 account address */ + readonly sender: string; + /** Bech32 account address */ + readonly contract: string; + /** The new code */ + readonly code_id: string; + /** Migrate message as JavaScript object */ + readonly msg: any; + }; +} + +/** + * The Amino JSON representation of [MsgUpdateAdmin]. + * + * [MsgUpdateAdmin]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L113-L121 + */ +export interface AminoMsgUpdateAdmin { + type: "wasm/MsgUpdateAdmin"; + value: { + /** Bech32-encoded sender address. This must be the old admin. */ + readonly sender: string; + /** Bech32-encoded contract address to be updated */ + readonly contract: string; + /** Bech32-encoded address of the new admin */ + readonly new_admin: string; + }; +} + +/** + * The Amino JSON representation of [MsgClearAdmin]. + * + * [MsgClearAdmin]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L126-L132 + */ +export interface AminoMsgClearAdmin { + type: "wasm/MsgClearAdmin"; + value: { + /** Bech32-encoded sender address. This must be the old admin. */ + readonly sender: string; + /** Bech32-encoded contract address to be updated */ + readonly contract: string; + }; +} + +export function createWasmAminoConverters(): AminoConverters { + return { + "/cosmwasm.wasm.v1.MsgStoreCode": { + aminoType: "wasm/MsgStoreCode", + toAmino: ({ sender, wasmByteCode }: MsgStoreCode): AminoMsgStoreCode["value"] => ({ + sender: sender, + wasm_byte_code: toBase64(wasmByteCode), + }), + fromAmino: ({ sender, wasm_byte_code }: AminoMsgStoreCode["value"]): MsgStoreCode => ({ + sender: sender, + wasmByteCode: fromBase64(wasm_byte_code), + instantiatePermission: undefined, + }), + }, + "/cosmwasm.wasm.v1.MsgInstantiateContract": { + aminoType: "wasm/MsgInstantiateContract", + toAmino: ({ + sender, + codeId, + label, + msg, + funds, + admin, + }: MsgInstantiateContract): AminoMsgInstantiateContract["value"] => ({ + sender: sender, + code_id: codeId.toString(), + label: label, + msg: JSON.parse(fromUtf8(msg)), + funds: funds, + admin: admin || undefined, + }), + fromAmino: ({ + sender, + code_id, + label, + msg, + funds, + admin, + }: AminoMsgInstantiateContract["value"]): MsgInstantiateContract => ({ + sender: sender, + codeId: Long.fromString(code_id), + label: label, + msg: toUtf8(JSON.stringify(msg)), + funds: [...funds], + admin: admin ?? "", + }), + }, + "/cosmwasm.wasm.v1.MsgUpdateAdmin": { + aminoType: "wasm/MsgUpdateAdmin", + toAmino: ({ sender, newAdmin, contract }: MsgUpdateAdmin): AminoMsgUpdateAdmin["value"] => ({ + sender: sender, + new_admin: newAdmin, + contract: contract, + }), + fromAmino: ({ sender, new_admin, contract }: AminoMsgUpdateAdmin["value"]): MsgUpdateAdmin => ({ + sender: sender, + newAdmin: new_admin, + contract: contract, + }), + }, + "/cosmwasm.wasm.v1.MsgClearAdmin": { + aminoType: "wasm/MsgClearAdmin", + toAmino: ({ sender, contract }: MsgClearAdmin): AminoMsgClearAdmin["value"] => ({ + sender: sender, + contract: contract, + }), + fromAmino: ({ sender, contract }: AminoMsgClearAdmin["value"]): MsgClearAdmin => ({ + sender: sender, + contract: contract, + }), + }, + "/cosmwasm.wasm.v1.MsgExecuteContract": { + aminoType: "wasm/MsgExecuteContract", + toAmino: ({ sender, contract, msg, funds }: MsgExecuteContract): AminoMsgExecuteContract["value"] => ({ + sender: sender, + contract: contract, + msg: JSON.parse(fromUtf8(msg)), + funds: funds, + }), + fromAmino: ({ + sender, + contract, + msg, + funds, + }: AminoMsgExecuteContract["value"]): MsgExecuteContract => ({ + sender: sender, + contract: contract, + msg: toUtf8(JSON.stringify(msg)), + funds: [...funds], + }), + }, + "/cosmwasm.wasm.v1.MsgMigrateContract": { + aminoType: "wasm/MsgMigrateContract", + toAmino: ({ sender, contract, codeId, msg }: MsgMigrateContract): AminoMsgMigrateContract["value"] => ({ + sender: sender, + contract: contract, + code_id: codeId.toString(), + msg: JSON.parse(fromUtf8(msg)), + }), + fromAmino: ({ + sender, + contract, + code_id, + msg, + }: AminoMsgMigrateContract["value"]): MsgMigrateContract => ({ + sender: sender, + contract: contract, + codeId: Long.fromString(code_id), + msg: toUtf8(JSON.stringify(msg)), + }), + }, + }; +} + +/** @deprecated use `createWasmAminoConverters()` */ +export const cosmWasmTypes: AminoConverters = createWasmAminoConverters(); diff --git a/packages/finschia/src/modules/wasm/messages.ts b/packages/finschia/src/modules/wasm/messages.ts new file mode 100644 index 000000000..ace12c8eb --- /dev/null +++ b/packages/finschia/src/modules/wasm/messages.ts @@ -0,0 +1,92 @@ +import { EncodeObject, GeneratedType } from "@cosmjs/proto-signing"; +import { + MsgClearAdmin, + MsgExecuteContract, + MsgInstantiateContract, + MsgMigrateContract, + MsgStoreCode, + MsgUpdateAdmin, +} from "cosmjs-types/cosmwasm/wasm/v1/tx"; +import { MsgStoreCodeAndInstantiateContract } from "lbmjs-types/cosmwasm/wasm/v1/tx"; + +export const wasmTypes: ReadonlyArray<[string, GeneratedType]> = [ + ["/cosmwasm.wasm.v1.MsgClearAdmin", MsgClearAdmin], + ["/cosmwasm.wasm.v1.MsgExecuteContract", MsgExecuteContract], + ["/cosmwasm.wasm.v1.MsgMigrateContract", MsgMigrateContract], + ["/cosmwasm.wasm.v1.MsgStoreCode", MsgStoreCode], + ["/cosmwasm.wasm.v1.MsgInstantiateContract", MsgInstantiateContract], + ["/cosmwasm.wasm.v1.MsgStoreCodeAndInstantiateContract", MsgStoreCodeAndInstantiateContract], + ["/cosmwasm.wasm.v1.MsgUpdateAdmin", MsgUpdateAdmin], +]; + +export interface MsgStoreCodeEncodeObject extends EncodeObject { + readonly typeUrl: "/cosmwasm.wasm.v1.MsgStoreCode"; + readonly value: Partial; +} + +export function isMsgStoreCodeEncodeObject(object: EncodeObject): object is MsgStoreCodeEncodeObject { + return (object as MsgStoreCodeEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgStoreCode"; +} + +export interface MsgInstantiateContractEncodeObject extends EncodeObject { + readonly typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract"; + readonly value: Partial; +} + +export function isMsgInstantiateContractEncodeObject( + object: EncodeObject, +): object is MsgInstantiateContractEncodeObject { + return ( + (object as MsgInstantiateContractEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgInstantiateContract" + ); +} + +export interface MsgStoreCodeAndInstantiateContractEncodeObject extends EncodeObject { + readonly typeUrl: "/cosmwasm.wasm.v1.MsgStoreCodeAndInstantiateContract"; + readonly value: Partial; +} + +export function isMsgStoreCodeAndInstantiateContract( + object: EncodeObject, +): object is MsgStoreCodeAndInstantiateContractEncodeObject { + return ( + (object as MsgStoreCodeAndInstantiateContractEncodeObject).typeUrl === + "/cosmwasm.wasm.v1.MsgStoreCodeAndInstantiateContract" + ); +} + +export interface MsgUpdateAdminEncodeObject extends EncodeObject { + readonly typeUrl: "/cosmwasm.wasm.v1.MsgUpdateAdmin"; + readonly value: Partial; +} + +export function isMsgUpdateAdminEncodeObject(object: EncodeObject): object is MsgUpdateAdminEncodeObject { + return (object as MsgUpdateAdminEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgUpdateAdmin"; +} + +export interface MsgClearAdminEncodeObject extends EncodeObject { + readonly typeUrl: "/cosmwasm.wasm.v1.MsgClearAdmin"; + readonly value: Partial; +} + +export function isMsgClearAdminEncodeObject(object: EncodeObject): object is MsgClearAdminEncodeObject { + return (object as MsgClearAdminEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgClearAdmin"; +} + +export interface MsgMigrateContractEncodeObject extends EncodeObject { + readonly typeUrl: "/cosmwasm.wasm.v1.MsgMigrateContract"; + readonly value: Partial; +} + +export function isMsgMigrateEncodeObject(object: EncodeObject): object is MsgMigrateContractEncodeObject { + return (object as MsgMigrateContractEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgMigrateContract"; +} + +export interface MsgExecuteContractEncodeObject extends EncodeObject { + readonly typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract"; + readonly value: Partial; +} + +export function isMsgExecuteEncodeObject(object: EncodeObject): object is MsgExecuteContractEncodeObject { + return (object as MsgExecuteContractEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgExecuteContract"; +} diff --git a/packages/finschia/src/modules/wasm/queries.spec.ts b/packages/finschia/src/modules/wasm/queries.spec.ts new file mode 100644 index 000000000..da2ae82d4 --- /dev/null +++ b/packages/finschia/src/modules/wasm/queries.spec.ts @@ -0,0 +1,450 @@ +import { sha256 } from "@cosmjs/crypto"; +import { fromAscii, fromHex, toAscii, toHex } from "@cosmjs/encoding"; +import { DirectSecp256k1HdWallet, OfflineDirectSigner, Registry } from "@cosmjs/proto-signing"; +import { + assertIsDeliverTxSuccess, + Coin, + coin, + coins, + DeliverTxResponse, + logs, + SigningStargateClient, + StdFee, +} from "@cosmjs/stargate"; +import { assert } from "@cosmjs/utils"; +import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from "cosmjs-types/cosmwasm/wasm/v1/tx"; +import { ContractCodeHistoryOperationType } from "cosmjs-types/cosmwasm/wasm/v1/types"; +import { ContractStatus } from "lbmjs-types/cosmwasm/wasm/v1/types"; +import Long from "long"; + +import { + alice, + bech32AddressMatcher, + ContractUploadInstructions, + defaultSigningClientOptions, + getHackatom, + makeRandomAddress, + makeWasmClient, + pendingWithoutWasmd, + wasmd, + wasmdEnabled, +} from "../../cosmwasm-testutils.spec"; +import { makeLinkPath } from "../../paths"; +import { SigningFinschiaClient } from "../../signingfinschiaclient"; +import { + MsgExecuteContractEncodeObject, + MsgInstantiateContractEncodeObject, + MsgStoreCodeEncodeObject, + wasmTypes, +} from "./messages"; + +const registry = new Registry(wasmTypes); + +const eventTypeInstantiateContract = "instantiate"; +const attributeTypeContractAddress = "_contract_address"; + +async function uploadContract( + signer: OfflineDirectSigner, + contract: ContractUploadInstructions, +): Promise { + const memo = "My first contract on chain"; + const theMsg: MsgStoreCodeEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgStoreCode", + value: MsgStoreCode.fromPartial({ + sender: alice.address0, + wasmByteCode: contract.data, + }), + }; + const fee: StdFee = { + amount: coins(5000000, "cony"), + gas: "89000000", + }; + const firstAddress = (await signer.getAccounts())[0].address; + const client = await SigningStargateClient.connectWithSigner(wasmd.endpoint, signer, { + ...defaultSigningClientOptions, + registry, + }); + return client.signAndBroadcast(firstAddress, [theMsg], fee, memo); +} + +async function instantiateContract( + signer: OfflineDirectSigner, + codeId: number, + beneficiaryAddress: string, + funds?: readonly Coin[], +): Promise { + const memo = "Create an escrow instance"; + const theMsg: MsgInstantiateContractEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: MsgInstantiateContract.fromPartial({ + sender: alice.address0, + codeId: Long.fromNumber(codeId), + label: "my escrow", + msg: toAscii( + JSON.stringify({ + verifier: alice.address0, + beneficiary: beneficiaryAddress, + }), + ), + funds: funds ? [...funds] : [], + }), + }; + const fee: StdFee = { + amount: coins(5000000, "cony"), + gas: "89000000", + }; + + const firstAddress = (await signer.getAccounts())[0].address; + const client = await SigningStargateClient.connectWithSigner(wasmd.endpoint, signer, { + ...defaultSigningClientOptions, + registry, + }); + return client.signAndBroadcast(firstAddress, [theMsg], fee, memo); +} + +async function executeContract( + signer: OfflineDirectSigner, + contractAddress: string, + msg: Record, +): Promise { + const memo = "Time for action"; + const theMsg: MsgExecuteContractEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: alice.address0, + contract: contractAddress, + msg: toAscii(JSON.stringify(msg)), + funds: [], + }), + }; + const fee: StdFee = { + amount: coins(5000000, "cony"), + gas: "89000000", + }; + + const firstAddress = (await signer.getAccounts())[0].address; + const client = await SigningFinschiaClient.connectWithSigner(wasmd.endpoint, signer, { + ...defaultSigningClientOptions, + registry, + }); + return client.signAndBroadcast(firstAddress, [theMsg], fee, memo); +} + +describe("WasmExtension", () => { + const hackatom = getHackatom(); + const hackatomConfigKey = toAscii("config"); + let hackatomCodeId: number | undefined; + let hackatomContractAddress: string | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: wasmd.prefix, + }); + const result = await uploadContract(wallet, hackatom); + assertIsDeliverTxSuccess(result); + hackatomCodeId = Number.parseInt( + JSON.parse(result.rawLog!)[0] + .events.find((event: any) => event.type === "store_code") + .attributes.find((attribute: any) => attribute.key === "code_id").value, + 10, + ); + + const instantiateResult = await instantiateContract(wallet, hackatomCodeId, makeRandomAddress()); + assertIsDeliverTxSuccess(instantiateResult); + hackatomContractAddress = JSON.parse(instantiateResult.rawLog!)[0] + .events.find((event: any) => event.type === eventTypeInstantiateContract) + .attributes.find((attribute: any) => attribute.key === attributeTypeContractAddress).value; + } + }); + + describe("listCodeInfo", () => { + it("has recently uploaded contract as last entry", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const client = await makeWasmClient(wasmd.endpoint); + const { codeInfos } = await client.wasm.listCodeInfo(); + assert(codeInfos); + const lastCode = codeInfos[codeInfos.length - 1]; + expect(lastCode.codeId.toNumber()).toEqual(hackatomCodeId); + expect(lastCode.creator).toEqual(alice.address0); + expect(toHex(lastCode.dataHash)).toEqual(toHex(sha256(hackatom.data))); + }); + }); + + describe("getCode", () => { + it("contains fill code information", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const client = await makeWasmClient(wasmd.endpoint); + const { codeInfo, data } = await client.wasm.getCode(hackatomCodeId); + assert(codeInfo); + expect(codeInfo.codeId.toNumber()).toEqual(hackatomCodeId); + expect(codeInfo.creator).toEqual(alice.address0); + expect(toHex(codeInfo.dataHash)).toEqual(toHex(sha256(hackatom.data))); + expect(data).toEqual(hackatom.data); + }); + }); + + // TODO: move listContractsByCodeId tests out of here + describe("getContractInfo", () => { + it("works", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: wasmd.prefix, + }); + const client = await makeWasmClient(wasmd.endpoint); + + // create new instance and compare before and after + const { contracts: existingContracts } = await client.wasm.listContractsByCodeId(hackatomCodeId); + assert(existingContracts); + for (const address of existingContracts) { + expect(address).toMatch(bech32AddressMatcher); + } + + const beneficiaryAddress = makeRandomAddress(); + const funds = coins(707707, "cony"); + const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, funds); + assertIsDeliverTxSuccess(result); + const myAddress = JSON.parse(result.rawLog!)[0] + .events.find((event: any) => event.type === eventTypeInstantiateContract) + .attributes!.find((attribute: any) => attribute.key === attributeTypeContractAddress).value; + + const { contracts: newContracts } = await client.wasm.listContractsByCodeId(hackatomCodeId); + assert(newContracts); + expect(newContracts.length).toEqual(existingContracts.length + 1); + const newContract = newContracts[newContracts.length - 1]; + expect(newContract).toMatch(bech32AddressMatcher); + + const { contractInfo } = await client.wasm.getContractInfo(myAddress); + assert(contractInfo); + expect({ ...contractInfo }).toEqual({ + codeId: Long.fromNumber(hackatomCodeId, true), + creator: alice.address0, + label: "my escrow", + admin: "", + ibcPortId: "", + status: ContractStatus.CONTRACT_STATUS_ACTIVE, + created: undefined, + extension: undefined, + }); + expect(contractInfo.admin).toEqual(""); + }); + + it("rejects for non-existent address", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const client = await makeWasmClient(wasmd.endpoint); + const nonExistentAddress = makeRandomAddress(); + await expectAsync(client.wasm.getContractInfo(nonExistentAddress)).toBeRejectedWithError(/not found/i); + }); + }); + + describe("getContractCodeHistory", () => { + it("can list contract history", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: wasmd.prefix, + }); + const client = await makeWasmClient(wasmd.endpoint); + + // create new instance and compare before and after + const beneficiaryAddress = makeRandomAddress(); + const funds = coins(707707, "cony"); + const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, funds); + assertIsDeliverTxSuccess(result); + + const myAddress = JSON.parse(result.rawLog!)[0] + .events.find((event: any) => event.type === eventTypeInstantiateContract) + .attributes!.find((attribute: any) => attribute.key === attributeTypeContractAddress).value; + + const history = await client.wasm.getContractCodeHistory(myAddress); + assert(history.entries); + expect(history.entries).toContain( + jasmine.objectContaining({ + codeId: Long.fromNumber(hackatomCodeId, true), + operation: ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT, + msg: toAscii( + JSON.stringify({ + verifier: alice.address0, + beneficiary: beneficiaryAddress, + }), + ), + }), + ); + }); + + it("returns empty list for non-existent address", async () => { + pendingWithoutWasmd(); + assert(hackatomCodeId); + const client = await makeWasmClient(wasmd.endpoint); + const nonExistentAddress = makeRandomAddress(); + const history = await client.wasm.getContractCodeHistory(nonExistentAddress); + expect(history.entries).toEqual([]); + }); + }); + + describe("getAllContractState", () => { + it("can get all state", async () => { + pendingWithoutWasmd(); + assert(hackatomContractAddress); + const client = await makeWasmClient(wasmd.endpoint); + const { models } = await client.wasm.getAllContractState(hackatomContractAddress); + assert(models); + expect(models.length).toEqual(1); + const data = models[0]; + expect(data.key).toEqual(hackatomConfigKey); + const value = JSON.parse(fromAscii(data.value)); + expect(value.verifier).toMatch(bech32AddressMatcher); + expect(value.beneficiary).toMatch(bech32AddressMatcher); + }); + + it("rejects for non-existent address", async () => { + pendingWithoutWasmd(); + const client = await makeWasmClient(wasmd.endpoint); + const nonExistentAddress = makeRandomAddress(); + await expectAsync(client.wasm.getAllContractState(nonExistentAddress)).toBeRejectedWithError( + /not found/i, + ); + }); + }); + + describe("queryContractRaw", () => { + it("can query by key", async () => { + pendingWithoutWasmd(); + assert(hackatomContractAddress); + const client = await makeWasmClient(wasmd.endpoint); + const raw = await client.wasm.queryContractRaw(hackatomContractAddress, hackatomConfigKey); + assert(raw.data, "must get result"); + const model = JSON.parse(fromAscii(raw.data)); + expect(model.verifier).toMatch(bech32AddressMatcher); + expect(model.beneficiary).toMatch(bech32AddressMatcher); + }); + + it("returns empty for missing key", async () => { + pendingWithoutWasmd(); + assert(hackatomContractAddress); + const client = await makeWasmClient(wasmd.endpoint); + const { data } = await client.wasm.queryContractRaw(hackatomContractAddress, fromHex("cafe0dad")); + expect(data).toEqual(new Uint8Array()); + }); + + it("returns null for non-existent address", async () => { + pendingWithoutWasmd(); + const client = await makeWasmClient(wasmd.endpoint); + const nonExistentAddress = makeRandomAddress(); + await expectAsync( + client.wasm.queryContractRaw(nonExistentAddress, hackatomConfigKey), + ).toBeRejectedWithError(/not found/i); + }); + }); + + describe("queryContractSmart", () => { + it("can make smart queries", async () => { + pendingWithoutWasmd(); + assert(hackatomContractAddress); + const client = await makeWasmClient(wasmd.endpoint); + const request = { verifier: {} }; + const result = await client.wasm.queryContractSmart(hackatomContractAddress, request); + expect(result).toEqual({ verifier: alice.address0 }); + }); + + it("throws for invalid query requests", async () => { + pendingWithoutWasmd(); + assert(hackatomContractAddress); + const client = await makeWasmClient(wasmd.endpoint); + const request = { nosuchkey: {} }; + await expectAsync( + client.wasm.queryContractSmart(hackatomContractAddress, request), + ).toBeRejectedWithError(/Error parsing into type hackatom::msg::QueryMsg: unknown variant/i); + }); + + it("throws for non-existent address", async () => { + pendingWithoutWasmd(); + const client = await makeWasmClient(wasmd.endpoint); + const nonExistentAddress = makeRandomAddress(); + const request = { verifier: {} }; + await expectAsync(client.wasm.queryContractSmart(nonExistentAddress, request)).toBeRejectedWithError( + /not found/i, + ); + }); + }); + + describe("broadcastTx", () => { + it("can upload, instantiate and execute wasm", async () => { + pendingWithoutWasmd(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: wasmd.prefix, + }); + const client = await makeWasmClient(wasmd.endpoint); + + const funds = [coin(1234, "cony"), coin(321, "stake")]; + const beneficiaryAddress = makeRandomAddress(); + + let codeId: number; + + // upload + { + const result = await uploadContract(wallet, getHackatom()); + assertIsDeliverTxSuccess(result); + const parsedLogs = logs.parseLogs(logs.parseRawLog(result.rawLog)); + const codeIdAttr = logs.findAttribute(parsedLogs, "store_code", "code_id"); + codeId = Number.parseInt(codeIdAttr.value, 10); + expect(codeId).toBeGreaterThanOrEqual(1); + expect(codeId).toBeLessThanOrEqual(200); + const actionAttr = logs.findAttribute(parsedLogs, "message", "module"); + expect(actionAttr.value).toEqual("wasm"); + } + + let contractAddress: string; + + // instantiate + { + const result = await instantiateContract(wallet, codeId, beneficiaryAddress, funds); + assertIsDeliverTxSuccess(result); + const parsedLogs = logs.parseLogs(logs.parseRawLog(result.rawLog)); + const contractAddressAttr = logs.findAttribute( + parsedLogs, + eventTypeInstantiateContract, + attributeTypeContractAddress, + ); + contractAddress = contractAddressAttr.value; + const amountAttr = logs.findAttribute(parsedLogs, "transfer", "amount"); + expect(amountAttr.value).toEqual("1234cony,321stake"); + const actionAttr = logs.findAttribute(parsedLogs, "message", "module"); + expect(actionAttr.value).toEqual("wasm"); + + const balanceCony = await client.bank.balance(contractAddress, "cony"); + expect(balanceCony).toEqual(funds[0]); + const balanceUstake = await client.bank.balance(contractAddress, "stake"); + expect(balanceUstake).toEqual(funds[1]); + } + + // execute + { + const result = await executeContract(wallet, contractAddress, { release: {} }); + assertIsDeliverTxSuccess(result); + const parsedLogs = logs.parseLogs(logs.parseRawLog(result.rawLog)); + const wasmEvent = parsedLogs.find(() => true)?.events.find((e) => e.type === "wasm"); + assert(wasmEvent, "Event of type wasm expected"); + expect(wasmEvent.attributes).toContain({ key: "action", value: "release" }); + expect(wasmEvent.attributes).toContain({ + key: "destination", + value: beneficiaryAddress, + }); + + // Verify token transfer from contract to beneficiary + const beneficiaryBalanceCony = await client.bank.balance(beneficiaryAddress, "cony"); + expect(beneficiaryBalanceCony).toEqual(funds[0]); + const beneficiaryBalanceUstake = await client.bank.balance(beneficiaryAddress, "stake"); + expect(beneficiaryBalanceUstake).toEqual(funds[1]); + } + }); + }); +}); diff --git a/packages/finschia/src/modules/wasm/queries.ts b/packages/finschia/src/modules/wasm/queries.ts new file mode 100644 index 000000000..bb50b77f2 --- /dev/null +++ b/packages/finschia/src/modules/wasm/queries.ts @@ -0,0 +1,136 @@ +import { fromUtf8, toAscii } from "@cosmjs/encoding"; +import { createPagination, createProtobufRpcClient, QueryClient } from "@cosmjs/stargate"; +import { + QueryAllContractStateResponse, + QueryCodeResponse, + QueryCodesResponse, + QueryContractHistoryResponse, + QueryContractsByCodeResponse, + QueryRawContractStateResponse, +} from "cosmjs-types/cosmwasm/wasm/v1/query"; +import { QueryClientImpl, QueryContractInfoResponse } from "lbmjs-types/cosmwasm/wasm/v1/query"; +import Long from "long"; + +/** + * An object containing a parsed JSON document. The result of JSON.parse(). + * This doesn't provide any type safety over `any` but expresses intent in the code. + * + * This type is returned by `queryContractSmart`. + */ +export type JsonObject = any; + +export interface WasmExtension { + readonly wasm: { + readonly listCodeInfo: (paginationKey?: Uint8Array) => Promise; + /** + * Downloads the original wasm bytecode by code ID. + * + * Throws an error if no code with this id + */ + readonly getCode: (id: number) => Promise; + readonly listContractsByCodeId: ( + id: number, + paginationKey?: Uint8Array, + ) => Promise; + /** + * Returns null when contract was not found at this address. + */ + readonly getContractInfo: (address: string) => Promise; + /** + * Returns null when contract history was not found for this address. + */ + readonly getContractCodeHistory: ( + address: string, + paginationKey?: Uint8Array, + ) => Promise; + /** + * Returns all contract state. + * This is an empty array if no such contract, or contract has no data. + */ + readonly getAllContractState: ( + address: string, + paginationKey?: Uint8Array, + ) => Promise; + /** + * Returns the data at the key if present (unknown decoded json), + * or null if no data at this (contract address, key) pair + */ + readonly queryContractRaw: (address: string, key: Uint8Array) => Promise; + /** + * Makes a smart query on the contract and parses the response as JSON. + * Throws error if no such contract exists, the query format is invalid or the response is invalid. + */ + readonly queryContractSmart: (address: string, query: Record) => Promise; + }; +} + +export function setupWasmExtension(base: QueryClient): WasmExtension { + const rpc = createProtobufRpcClient(base); + // Use this service to get easy typed access to query methods + // This cannot be used for proof verification + const queryService = new QueryClientImpl(rpc); + + return { + wasm: { + listCodeInfo: async (paginationKey?: Uint8Array) => { + const request = { + pagination: createPagination(paginationKey), + }; + return queryService.Codes(request); + }, + getCode: async (id: number) => { + const request = { codeId: Long.fromNumber(id) }; + return queryService.Code(request); + }, + listContractsByCodeId: async (id: number, paginationKey?: Uint8Array) => { + const request = { + codeId: Long.fromNumber(id), + pagination: createPagination(paginationKey), + }; + return queryService.ContractsByCode(request); + }, + getContractInfo: async (address: string) => { + const request = { address: address }; + return queryService.ContractInfo(request); + }, + + getContractCodeHistory: async (address: string, paginationKey?: Uint8Array) => { + const request = { + address: address, + pagination: createPagination(paginationKey), + }; + return queryService.ContractHistory(request); + }, + + getAllContractState: async (address: string, paginationKey?: Uint8Array) => { + const request = { + address: address, + pagination: createPagination(paginationKey), + }; + return queryService.AllContractState(request); + }, + + queryContractRaw: async (address: string, key: Uint8Array) => { + const request = { address: address, queryData: key }; + return queryService.RawContractState(request); + }, + + queryContractSmart: async (address: string, query: Record) => { + const request = { address: address, queryData: toAscii(JSON.stringify(query)) }; + const { data } = await queryService.SmartContractState(request); + // By convention, smart queries must return a valid JSON document (see https://github.com/CosmWasm/cosmwasm/issues/144) + let responseText: string; + try { + responseText = fromUtf8(data); + } catch (error) { + throw new Error(`Could not UTF-8 decode smart query response from contract: ${error}`); + } + try { + return JSON.parse(responseText); + } catch (error) { + throw new Error(`Could not JSON parse smart query response from contract: ${error}`); + } + }, + }, + }; +} diff --git a/packages/finschia/src/signingfinschiaclient.ts b/packages/finschia/src/signingfinschiaclient.ts new file mode 100644 index 000000000..d84d9afd2 --- /dev/null +++ b/packages/finschia/src/signingfinschiaclient.ts @@ -0,0 +1,663 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { encodeSecp256k1Pubkey, makeSignDoc as makeSignDocAmino } from "@cosmjs/amino"; +import { + ChangeAdminResult, + ExecuteResult, + InstantiateOptions, + InstantiateResult, + MigrateResult, + UploadResult, +} from "@cosmjs/cosmwasm-stargate"; +import { sha256 } from "@cosmjs/crypto"; +import { fromBase64, toHex, toUtf8 } from "@cosmjs/encoding"; +import { Int53, Uint53 } from "@cosmjs/math"; +import { + EncodeObject, + encodePubkey, + GeneratedType, + isOfflineDirectSigner, + makeAuthInfoBytes, + makeSignDoc, + OfflineSigner, + Registry, + TxBodyEncodeObject, +} from "@cosmjs/proto-signing"; +import { + AminoConverters, + AminoTypes, + calculateFee, + Coin, + createAuthzAminoConverters, + createBankAminoConverters, + createDistributionAminoConverters, + createGovAminoConverters, + createStakingAminoConverters, + defaultRegistryTypes, + DeliverTxResponse, + GasPrice, + isDeliverTxFailure, + logs, + MsgDelegateEncodeObject, + MsgSendEncodeObject, + MsgUndelegateEncodeObject, + MsgWithdrawDelegatorRewardEncodeObject, + SignerData, + SigningStargateClientOptions, + StdFee, +} from "@cosmjs/stargate"; +import { HttpEndpoint, Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { assert, assertDefined } from "@cosmjs/utils"; +import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; +import { MsgDelegate, MsgUndelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; +import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; +import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; +import { + MsgClearAdmin, + MsgExecuteContract, + MsgInstantiateContract, + MsgMigrateContract, + MsgStoreCode, + MsgUpdateAdmin, +} from "cosmjs-types/cosmwasm/wasm/v1/tx"; +import { AccessType } from "cosmjs-types/cosmwasm/wasm/v1/types"; +import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; +import { Height } from "cosmjs-types/ibc/core/client/v1/client"; +import { MsgStoreCodeAndInstantiateContract } from "lbmjs-types/cosmwasm/wasm/v1/tx"; +import Long from "long"; +import pako from "pako"; + +import { FinschiaClient } from "./finschiaclient"; +import { + collectionTypes, + createFreegrantAminoConverters, + createIbcAminoConverters, + createWasmAminoConverters, + feegrantTypes, + foundationTypes, + ibcTypes, + MsgClearAdminEncodeObject, + MsgExecuteContractEncodeObject, + MsgInstantiateContractEncodeObject, + MsgMigrateContractEncodeObject, + MsgStoreCodeEncodeObject, + MsgTransferEncodeObject, + MsgUpdateAdminEncodeObject, + tokenTypes, + wasmTypes, +} from "./modules"; + +export interface UploadAndInstantiateResult { + /** Size of the original wasm code in bytes */ + readonly originalSize: number; + /** A hex encoded sha256 checksum of the original wasm code (that is stored on chain) */ + readonly originalChecksum: string; + /** Size of the compressed wasm code in bytes */ + readonly compressedSize: number; + /** A hex encoded sha256 checksum of the compressed wasm code (that stored in the transaction) */ + readonly compressedChecksum: string; + readonly codeId: number; + /** The address of the newly instantiated contract */ + readonly contractAddress: string; + readonly logs: readonly logs.Log[]; + /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ + readonly transactionHash: string; + readonly gasWanted: number; + readonly gasUsed: number; +} + +function createDeliverTxResponseErrorMessage(result: DeliverTxResponse): string { + return `Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`; +} + +export const finschiaRegistryTypes: ReadonlyArray<[string, GeneratedType]> = [ + ...defaultRegistryTypes, + ...feegrantTypes, + ...ibcTypes, + ...tokenTypes, + ...foundationTypes, + ...collectionTypes, + ...wasmTypes, +]; + +function createDefaultRegistry(): Registry { + return new Registry(finschiaRegistryTypes); +} + +function createDefaultTypes(prefix: string): AminoConverters { + return { + ...createAuthzAminoConverters(), + ...createBankAminoConverters(), + ...createDistributionAminoConverters(), + ...createFreegrantAminoConverters(), + ...createGovAminoConverters(), + ...createIbcAminoConverters(), + ...createStakingAminoConverters(prefix), + // ...createVestingAminoConverters(), this is omitted in cosmjs export + ...createWasmAminoConverters(), + }; +} + +export class SigningFinschiaClient extends FinschiaClient { + public readonly registry: Registry; + public readonly broadcastTimeoutMs: number | undefined; + public readonly broadcastPollIntervalMs: number | undefined; + + private readonly signer: OfflineSigner; + private readonly aminoTypes: AminoTypes; + private readonly gasPrice: GasPrice | undefined; + + public static async connectWithSigner( + endpoint: string | HttpEndpoint, + signer: OfflineSigner, + options: SigningStargateClientOptions = {}, + ): Promise { + const tmClient = await Tendermint34Client.connect(endpoint); + return new SigningFinschiaClient(tmClient, signer, options); + } + + /** + * Creates a client in offline mode. + * + * This should only be used in niche cases where you know exactly what you're doing, + * e.g. when building an offline signing application. + * + * When you try to use online functionality with such a signer, an + * exception will be raised. + */ + public static async offline( + signer: OfflineSigner, + options: SigningStargateClientOptions = {}, + ): Promise { + return new SigningFinschiaClient(undefined, signer, options); + } + + protected constructor( + tmClient: Tendermint34Client | undefined, + signer: OfflineSigner, + options: SigningStargateClientOptions, + ) { + super(tmClient, options); + const prefix = options.prefix ?? "link"; + const { + registry = createDefaultRegistry(), + aminoTypes = new AminoTypes({ ...createDefaultTypes(prefix) }), + } = options; + this.registry = registry; + this.aminoTypes = aminoTypes; + this.signer = signer; + this.broadcastTimeoutMs = options.broadcastTimeoutMs; + this.broadcastPollIntervalMs = options.broadcastPollIntervalMs; + this.gasPrice = options.gasPrice; + } + + public async simulate( + signerAddress: string, + messages: readonly EncodeObject[], + memo: string | undefined, + ): Promise { + const anyMsgs = messages.map((m) => this.registry.encodeAsAny(m)); + const accountFromSigner = (await this.signer.getAccounts()).find( + (account) => account.address === signerAddress, + ); + if (!accountFromSigner) { + throw new Error("Failed to retrieve account from signer"); + } + const pubkey = encodeSecp256k1Pubkey(accountFromSigner.pubkey); + const { sequence } = await this.getSequence(signerAddress); + const { gasInfo } = await this.forceGetQueryClient().tx.simulate(anyMsgs, memo, pubkey, sequence); + assertDefined(gasInfo); + return Uint53.fromString(gasInfo.gasUsed.toString()).toNumber(); + } + + public async sendTokens( + senderAddress: string, + recipientAddress: string, + amount: readonly Coin[], + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + const sendMsg: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: senderAddress, + toAddress: recipientAddress, + amount: [...amount], + }, + }; + return this.signAndBroadcast(senderAddress, [sendMsg], fee, memo); + } + + public async delegateTokens( + delegatorAddress: string, + validatorAddress: string, + amount: Coin, + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + const delegateMsg: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: MsgDelegate.fromPartial({ delegatorAddress: delegatorAddress, validatorAddress, amount }), + }; + return this.signAndBroadcast(delegatorAddress, [delegateMsg], fee, memo); + } + + public async undelegateTokens( + delegatorAddress: string, + validatorAddress: string, + amount: Coin, + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + const undelegateMsg: MsgUndelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgUndelegate", + value: MsgUndelegate.fromPartial({ delegatorAddress: delegatorAddress, validatorAddress, amount }), + }; + return this.signAndBroadcast(delegatorAddress, [undelegateMsg], fee, memo); + } + + public async withdrawRewards( + delegatorAddress: string, + validatorAddress: string, + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + const withdrawDelegatorRewardMsg: MsgWithdrawDelegatorRewardEncodeObject = { + typeUrl: "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + value: MsgWithdrawDelegatorReward.fromPartial({ delegatorAddress: delegatorAddress, validatorAddress }), + }; + return this.signAndBroadcast(delegatorAddress, [withdrawDelegatorRewardMsg], fee, memo); + } + + public async sendIbcTokens( + senderAddress: string, + recipientAddress: string, + transferAmount: Coin, + sourcePort: string, + sourceChannel: string, + timeoutHeight: Height | undefined, + /** timeout in seconds */ + timeoutTimestamp: number | undefined, + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + const timeoutTimestampNanoseconds = timeoutTimestamp + ? Long.fromNumber(timeoutTimestamp).multiply(1_000_000_000) + : undefined; + const transferMsg: MsgTransferEncodeObject = { + typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", + value: MsgTransfer.fromPartial({ + sourcePort: sourcePort, + sourceChannel: sourceChannel, + sender: senderAddress, + receiver: recipientAddress, + token: transferAmount, + timeoutHeight: timeoutHeight, + timeoutTimestamp: timeoutTimestampNanoseconds, + }), + }; + return this.signAndBroadcast(senderAddress, [transferMsg], fee, memo); + } + + /** Uploads code and returns a receipt, including the code ID */ + public async upload( + senderAddress: string, + wasmCode: Uint8Array, + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + const compressed = pako.gzip(wasmCode, { level: 9 }); + const storeCodeMsg: MsgStoreCodeEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgStoreCode", + value: MsgStoreCode.fromPartial({ + sender: senderAddress, + wasmByteCode: compressed, + }), + }; + + const result = await this.signAndBroadcast(senderAddress, [storeCodeMsg], fee, memo); + if (isDeliverTxFailure(result)) { + throw new Error(createDeliverTxResponseErrorMessage(result)); + } + const parsedLogs = logs.parseRawLog(result.rawLog); + const codeIdAttr = logs.findAttribute(parsedLogs, "store_code", "code_id"); + return { + originalSize: wasmCode.length, + originalChecksum: toHex(sha256(wasmCode)), + compressedSize: compressed.length, + compressedChecksum: toHex(sha256(compressed)), + codeId: Number.parseInt(codeIdAttr.value, 10), + logs: parsedLogs, + height: result.height, + transactionHash: result.transactionHash, + gasWanted: result.gasWanted, + gasUsed: result.gasUsed, + }; + } + + public async instantiate( + senderAddress: string, + codeId: number, + msg: Record, + label: string, + fee: StdFee | "auto" | number, + options: InstantiateOptions = {}, + ): Promise { + const instantiateContractMsg: MsgInstantiateContractEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", + value: MsgInstantiateContract.fromPartial({ + sender: senderAddress, + codeId: Long.fromString(new Uint53(codeId).toString()), + label: label, + msg: toUtf8(JSON.stringify(msg)), + funds: [...(options.funds || [])], + admin: options.admin, + }), + }; + const result = await this.signAndBroadcast(senderAddress, [instantiateContractMsg], fee, options.memo); + if (isDeliverTxFailure(result)) { + throw new Error(createDeliverTxResponseErrorMessage(result)); + } + const parsedLogs = logs.parseRawLog(result.rawLog); + const contractAddressAttr = logs.findAttribute(parsedLogs, "instantiate", "_contract_address"); + return { + contractAddress: contractAddressAttr.value, + logs: parsedLogs, + height: result.height, + transactionHash: result.transactionHash, + gasWanted: result.gasWanted, + gasUsed: result.gasUsed, + }; + } + + public async uploadAndInstantiate( + signerAddress: string, + wasmCode: Uint8Array, + msg: Record, + labal: string, + fee: StdFee | "auto" | number, + options: InstantiateOptions = {}, + ): Promise { + const compressed = pako.gzip(wasmCode, { level: 9 }); + const storeCodeAndInstantiateMsg: EncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgStoreCodeAndInstantiateContract", + value: MsgStoreCodeAndInstantiateContract.fromPartial({ + sender: signerAddress, + wasmByteCode: compressed, + instantiatePermission: { + permission: AccessType.ACCESS_TYPE_EVERYBODY, + }, + admin: options.admin, + label: labal, + msg: toUtf8(JSON.stringify(msg)), + funds: [...(options.funds || [])], + }), + }; + const result = await this.signAndBroadcast( + signerAddress, + [storeCodeAndInstantiateMsg], + fee, + options.memo, + ); + if (isDeliverTxFailure(result)) { + throw new Error(createDeliverTxResponseErrorMessage(result)); + } + const parsedLogs = logs.parseRawLog(result.rawLog); + const codeIdAttr = logs.findAttribute(parsedLogs, "store_code", "code_id"); + const contractAddressAttr = logs.findAttribute(parsedLogs, "instantiate", "_contract_address"); + return { + originalSize: wasmCode.length, + originalChecksum: toHex(sha256(wasmCode)), + compressedSize: compressed.length, + compressedChecksum: toHex(sha256(compressed)), + codeId: Number.parseInt(codeIdAttr.value, 10), + contractAddress: contractAddressAttr.value, + logs: parsedLogs, + transactionHash: result.transactionHash, + gasWanted: result.gasWanted, + gasUsed: result.gasUsed, + }; + } + + public async updateAdmin( + senderAddress: string, + contractAddress: string, + newAdmin: string, + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + const updateAdminMsg: MsgUpdateAdminEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgUpdateAdmin", + value: MsgUpdateAdmin.fromPartial({ + sender: senderAddress, + contract: contractAddress, + newAdmin: newAdmin, + }), + }; + const result = await this.signAndBroadcast(senderAddress, [updateAdminMsg], fee, memo); + if (isDeliverTxFailure(result)) { + throw new Error(createDeliverTxResponseErrorMessage(result)); + } + return { + logs: logs.parseRawLog(result.rawLog), + height: result.height, + transactionHash: result.transactionHash, + gasWanted: result.gasWanted, + gasUsed: result.gasUsed, + }; + } + + public async clearAdmin( + senderAddress: string, + contractAddress: string, + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + const clearAdminMsg: MsgClearAdminEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgClearAdmin", + value: MsgClearAdmin.fromPartial({ + sender: senderAddress, + contract: contractAddress, + }), + }; + const result = await this.signAndBroadcast(senderAddress, [clearAdminMsg], fee, memo); + if (isDeliverTxFailure(result)) { + throw new Error(createDeliverTxResponseErrorMessage(result)); + } + return { + logs: logs.parseRawLog(result.rawLog), + height: result.height, + transactionHash: result.transactionHash, + gasWanted: result.gasWanted, + gasUsed: result.gasUsed, + }; + } + + public async migrate( + senderAddress: string, + contractAddress: string, + codeId: number, + migrateMsg: Record, + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + const migrateContractMsg: MsgMigrateContractEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgMigrateContract", + value: MsgMigrateContract.fromPartial({ + sender: senderAddress, + contract: contractAddress, + codeId: Long.fromString(new Uint53(codeId).toString()), + msg: toUtf8(JSON.stringify(migrateMsg)), + }), + }; + const result = await this.signAndBroadcast(senderAddress, [migrateContractMsg], fee, memo); + if (isDeliverTxFailure(result)) { + throw new Error(createDeliverTxResponseErrorMessage(result)); + } + return { + logs: logs.parseRawLog(result.rawLog), + height: result.height, + transactionHash: result.transactionHash, + gasWanted: result.gasWanted, + gasUsed: result.gasUsed, + }; + } + + public async execute( + senderAddress: string, + contractAddress: string, + msg: Record, + fee: StdFee | "auto" | number, + memo = "", + funds?: readonly Coin[], + ): Promise { + const executeContractMsg: MsgExecuteContractEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: senderAddress, + contract: contractAddress, + msg: toUtf8(JSON.stringify(msg)), + funds: [...(funds || [])], + }), + }; + const result = await this.signAndBroadcast(senderAddress, [executeContractMsg], fee, memo); + if (isDeliverTxFailure(result)) { + throw new Error(createDeliverTxResponseErrorMessage(result)); + } + return { + logs: logs.parseRawLog(result.rawLog), + height: result.height, + transactionHash: result.transactionHash, + gasWanted: result.gasWanted, + gasUsed: result.gasUsed, + }; + } + + /** + * Creates a transaction with the given messages, fee and memo. Then signs and broadcasts the transaction. + * + * @param signerAddress The address that will sign transactions using this instance. The signer must be able to sign with this address. + * @param messages + * @param fee + * @param memo + */ + public async signAndBroadcast( + signerAddress: string, + messages: readonly EncodeObject[], + fee: StdFee | "auto" | number, + memo = "", + ): Promise { + let usedFee: StdFee; + if (fee == "auto" || typeof fee === "number") { + assertDefined(this.gasPrice, "Gas price must be set in the client options when auto gas is used."); + const gasEstimation = await this.simulate(signerAddress, messages, memo); + const multiplier = typeof fee === "number" ? fee : 1.4; + usedFee = calculateFee(Math.round(gasEstimation * multiplier), this.gasPrice); + } else { + usedFee = fee; + } + const txRaw = await this.sign(signerAddress, messages, usedFee, memo); + const txBytes = TxRaw.encode(txRaw).finish(); + return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); + } + + public async sign( + signerAddress: string, + messages: readonly EncodeObject[], + fee: StdFee, + memo: string, + explicitSignerData?: SignerData, + ): Promise { + let signerData: SignerData; + if (explicitSignerData) { + signerData = explicitSignerData; + } else { + const { accountNumber, sequence } = await this.getSequence(signerAddress); + const chainId = await this.getChainId(); + signerData = { + accountNumber: accountNumber, + sequence: sequence, + chainId: chainId, + }; + } + + return isOfflineDirectSigner(this.signer) + ? this.signDirect(signerAddress, messages, fee, memo, signerData) + : this.signAmino(signerAddress, messages, fee, memo, signerData); + } + + private async signAmino( + signerAddress: string, + messages: readonly EncodeObject[], + fee: StdFee, + memo: string, + { accountNumber, sequence, chainId }: SignerData, + ): Promise { + assert(!isOfflineDirectSigner(this.signer)); + const accountFromSigner = (await this.signer.getAccounts()).find( + (account) => account.address === signerAddress, + ); + if (!accountFromSigner) { + throw new Error("Failed to retrieve account from signer"); + } + const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey)); + const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON; + const msgs = messages.map((msg) => this.aminoTypes.toAmino(msg)); + const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence); + const { signature, signed } = await this.signer.signAmino(signerAddress, signDoc); + const signedTxBody: TxBodyEncodeObject = { + typeUrl: "/cosmos.tx.v1beta1.TxBody", + value: { + messages: signed.msgs.map((msg) => this.aminoTypes.fromAmino(msg)), + memo: signed.memo, + }, + }; + const signedTxBodyBytes = this.registry.encode(signedTxBody); + const signedGasLimit = Int53.fromString(signed.fee.gas).toNumber(); + const signedSequence = Int53.fromString(signed.sequence).toNumber(); + const signedAuthInfoBytes = makeAuthInfoBytes( + [{ pubkey, sequence: signedSequence }], + signed.fee.amount, + signedGasLimit, + signMode, + ); + return TxRaw.fromPartial({ + bodyBytes: signedTxBodyBytes, + authInfoBytes: signedAuthInfoBytes, + signatures: [fromBase64(signature.signature)], + }); + } + + private async signDirect( + signerAddress: string, + messages: readonly EncodeObject[], + fee: StdFee, + memo: string, + { accountNumber, sequence, chainId }: SignerData, + ): Promise { + assert(isOfflineDirectSigner(this.signer)); + const accountFromSigner = (await this.signer.getAccounts()).find( + (account) => account.address === signerAddress, + ); + if (!accountFromSigner) { + throw new Error("Failed to retrieve account from signer"); + } + const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey)); + const txBody: TxBodyEncodeObject = { + typeUrl: "/cosmos.tx.v1beta1.TxBody", + value: { + messages: messages, + memo: memo, + }, + }; + const txBodyBytes = this.registry.encode(txBody); + const gasLimit = Int53.fromString(fee.gas).toNumber(); + const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], fee.amount, gasLimit); + const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber); + const { signature, signed } = await this.signer.signDirect(signerAddress, signDoc); + return TxRaw.fromPartial({ + bodyBytes: signed.bodyBytes, + authInfoBytes: signed.authInfoBytes, + signatures: [fromBase64(signature.signature)], + }); + } +} diff --git a/packages/finschia/src/testdata/contract.json b/packages/finschia/src/testdata/contract.json new file mode 100644 index 000000000..4ffcbbbe0 --- /dev/null +++ b/packages/finschia/src/testdata/contract.json @@ -0,0 +1,4 @@ +{ + "// source": "https://github.com/CosmWasm/cosmwasm/releases/download/v1.0.0-beta/hackatom.wasm", + "data": "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IAJFIQFBiI3AACgCACEKIANBQGtBBHIhCANAIAFBAXEEQCAFKAIIIgEgBUEEaigCAEYEQCAFIAFBARANIAUoAgghAQsgBSABQQFqNgIIIAUoAgAgAWpBLDoAAAsgA0FAayAFEO0BIAMoAkBBAUYNAiADIAMtAEg6ABwgAyADKAJENgIYIANBQGsgA0EYakH0gMAAQQUgBkEQahAOIAMoAkBBAUYNBSADKAIYIQECQCADLQAcBEAgAUEIaigCACECDAELIAEoAggiBCABQQRqKAIARgRAIAEgBEEBEA0gASgCCCEECyABIARBAWoiAjYCCCABKAIAIARqQSw6AAALIANBADoAHCABQQRqIgkoAgAgAkYEQCABIAJBARANIAFBCGooAgAhAgsgASgCACACakEiOgAAIAFBCGoiBCACQQFqIgI2AgAgCSgCACACa0EFTQRAIAEgAkEGEA0gBCgCACECCyABKAIAIAJqIgtB+YDAACgAADYAACALQQRqQf2AwAAvAAA7AAAgBCACQQZqIgI2AgAgCSgCACACa0EBTQRAIAEgAkECEA0gBCgCACECCyABKAIAIAJqQaL0ADsAACAEIAJBAmo2AgAgA0IANwI0IAMgCjYCMCADQUBrIANBMGpB6IXAABD6AiAGKQMAIAZBCGopAwAgA0FAaxDzAg0EIANBIGogASADKAIwIAMoAjgQ6QECQCADKAIwIgJFDQAgAygCNEUNACACEJUCCyADKAIgQQFGBEAgA0EUaiADQSxqKAIANgIAIAMgAykCJDcCDAwHCyADQQhqIAFBABDjASADKAIIQQFGDQYgBkEgaiEGQQEhASAHQWBqIgcNAAtBAAUgAgtB/wFxQQBHEOIBDAULIANBFGogCEEIaigCADYCACADIAgpAgA3AgwMAwsgAEEBNgIAIAAgA0FAa0EEciIBKQIANwIEIABBDGogAUEIaigCADYCAAwDC0GAhsAAQTcgA0HoAGpBrIzAAEGEh8AAEOgCAAsgA0EUaiADQcwAaigCADYCACADIAMpAkQ3AgwLIAAgAykCDDcCBCAAQQxqIANBFGooAgA2AgAgAEEBNgIACyADQfAAaiQAC+4NAQ5/IwBB8ABrIgMkACADQTBqIAEgAhDYASADQShqIANBMGoQ4AFBASENAkACQCADLQAoQQFxRQRAQQQhAgwBCyAD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CQsgAkHoAGogARDbASACKAJoIgFBFUYEQCAAQRU2AgAMDQsgAkHYAGogAkH0AGooAgAiAzYCACACIAIpAmwiBTcDUCAAQQxqIAM2AgAgACAFNwIEIAAgATYCAAwMCyACQTBqIAEQ4AEgAi0AMEEBcUUNCCACLQAxQfsARw0JIAEQ2QEgAkEoaiABENcBIAIoAighAyACIAItACxBAXE6AGQgAiADNgJgIAJB6ABqIAJB4ABqEEACQCACLQBoQQFHBEADQCACLQBpRQ0CIAJB6ABqIAJB4ABqEEAgAi0AaEEBRw0ACwsgAkHYAGoiAyACQfgAaigCADYCACACIAJB8ABqKQMANwNQIAIoAmwiBEEVRw0LCyACQegAaiABENwBIAIoAmgiAUEVRgRAIABBFTYCAAwMCyACQdgAaiACQfQAaigCACIDNgIAIAIgAikCbCIFNwNQIABBDGogAzYCACAAIAU3AgQgACABNgIADAsLIABBCzYCAAwKCyAAQRU2AgAMCQsgAEEENgIADAgLIABBDjYCAAwHCyAAQQxqIAJB+ABqKAIANgIAIABBCGogAzYCACAAIAE2AgQgACAENgIADAYLIABBBDYCAAwFCyAAQQ42AgAMBAsgAkHIAGogAygCACIBNgIAIAIgAikDUCIFNwNAIABBDGogATYCACAAIAU3AgQgACAENgIADAMLIABBBDYCAAwCCyAAQQ42AgAMAQsgAkHIAGogAygCACIBNgIAIAIgAikDUCIFNwNAIABBDGogATYCACAAIAU3AgQgACAENgIACyACQYABaiQAC4oCAQN/IwBBQGoiBCQAAkACQAJAIAJBAE4EQCACDQFBASEFDAILEMwCAAsgAiEGIAJBARBVIgVFDQELIAUgASACEJwDIQEgBEIANwIEIARBiI3AACgCADYCACAEQRBqIARB6IXAABD6AiADIARBEGoQ1gFFBEAgAEEMaiACNgIAIABBCGogBjYCACAAIAE2AgQgAEEQaiAEKQMANwIAIABBBzYCACAAQRhqIARBCGooAgA2AgACQCADKAIAQRRJDQAgA0EEaigCACIARQ0AIANBCGooAgBFDQAgABCVAgsgBEFAayQADwtBgIbAAEE3IARBOGpBrIzAAEGEh8AAEOgCAAsgAkEBEMsCAAvJLAIUfwV+IwBB8AJrIgMkACADQfgB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KAJIIgFBFUYNAQsgA0HUAGooAgAhBSADQdAAaigCACEEIAMoAkwhAkEBIQggBw0EDAULIANBCGogBhDgAUEAIQIgAy0ACSEBIAMtAAhBAXENAAtBAgwBC0EECyEBQQEhCCAHRQ0BCyAORQ0AIAcQlQILIAtFIA1FIApFcnJFBEAgChCVAgsgDEUgCUEARyAIcUVyDQAgCRCVAgsgA0HkAGogBTYCACADQeAAaiAENgIAIAMgAjYCXCADIAE2AlggAEEIakGViMAAQRYgA0HYAGoQGQsgACAPNgIAIANB8ABqJAALlS8CD38CfiMAQaABayICJAAgAhDmASAAAn8CQAJAAkACQAJAAkACQAJAAkACQCABKAIAQQFHBEAgAigCCCIDIAIoAgRGBEAgAiADQQEQDSACKAIIIQMLIAIoAgAgA2pB+wA6AAAgAiADQQFqNgIIIAJBkAFqIAJBnYXAAEECEOkBIAIoApABQQFGDQMgAigCCCIDIAIoAgRGBEAgAiADQQEQDSACKAIIIQMLIAIoAgAgA2pBOjoAACACIANBAWo2AgggAkGQAWogAhDtASACKAKQAUEBRg0BIAIgAigClAEiAzYCMAJAIAJBmAFqLQAABEAgA0EIaigCACEEDAELIAMoAggiBSADQQRqKAIARgRAIAMgBUEBEA0gAygCCCEFCyADIAVBAWoiBDYCCCADKAIAIAVqQSw6AAALIAJBADoANCADQQRqIgYoAgAgBEYEQCADIARBARANIANBCGooAgAhBAsgAygCACAEakEiOgAAIANBCGoiBSAEQQFqIgQ2AgAgBigCACAEa0EHTQRAIAMgBEEIEA0gBSgCACEECyADKAIAIARqQu3KzZuX7Nmy8wA3AAAgBSAEQQhqIgQ2AgAgA0EEaigCACAEa0EBTQRAIAMgBEECEA0gA0EIaigCACEECyADKAIAIARqQaL0ADsAACADQQhqIARBAmo2AgAgASgCBCEFIAJBkAFqIAMgAUEMaigCACIDEOwBIAIoApABQQFGDQYgAkGYAWotAAAhBAJAAkAgAkE4aiACKAKUASILIAMEfyAFIANB6ABsaiEPIAVByABqIQYgBEUhAyACQZABakEEciEMIAJBgAFqQQRyIQoDQCADQQFxBEAgCygCCCIDIAtBBGooAgBGBEAgCyADQQEQDSALKAIIIQMLIAsgA0EBajYCCCALKAIAIANqQSw6AAALIAJBkAFqIAsQ7QECQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCACKAKQAUEBRwRAIAIoApQBIQUgBkG4f2oiDSkDACERAkAgAi0AmAEEQCAFQQhqKAIAIQMMAQsgBSgCCCIEIAVBBGooAgBGBEAgBSAEQQEQDSAFKAIIIQQLIAUgBEEBaiIDNgIIIAUoAgAgBGpBLDoAAAsgBUEEaiIIKAIAIANGBEAgBSADQQEQDSAFQQhqKAIAIQMLIAUoAgAgA2pBIjoAACAFQQhqIgQgA0EBaiIDNgIAIAgoAgAgA2tBAU0EQCAFIANBAhANIAQoAgAhAwsgBSgCACADakHpyAE7AAAgBCADQQJqIgM2AgAgCCgCACADa0EBTQRAIAUgA0ECEA0gBCgCACEDCyAFKAIAIANqQaL0ADsAACAEIANBAmo2AgAgAkGQAWogBSAREOgBIAIoApABQQFGDQIgBCgCACIDIAgoAgBGBEAgBSADQQEQDSAEKAIAIQMLIAUoAgAgA2pBLDoAACAEIANBAWoiAzYCACAIKAIAIANGBEAgBSADQQEQDSAEKAIAIQMLIAUoAgAgA2pBIjoAACAEIANBAWoiAzYCACAIKAIAIANrQQJNBEAgBSADQQMQDSAEKAIAIQMLIAUoAgAgA2oiB0HwgcAALwAAOwAAIAdBAmpB8oHAAC0AADoAACAEIANBA2oiAzYCACAIKAIAIANrQQFNBEAgBSADQQIQDSAEKAIAIQMLIAUoAgAgA2pBovQAOwAAIAQgA0ECaiIDNgIAAkACQAJAAkAgDUEIaigCAEEBaw4CAgEACyAIKAIAIANGBEAgBSADQQEQDSAEKAIAIQMLIAQgA0EBajYCACAFKAIAIANqQfsAOgAAIAJBkAFqIAVBiYHAAEEEEOkBIAIoApABQQFGDQIgBCgCACIDIAgoAgBGBEAgBSADQQEQDSAEKAIAIQMLIAQgA0EBajYCACAFKAIAIANqQTo6AAACQCAGQURqKAIAQQFHBEAgAkGQAWogBUGPhMAAQQQQ7gEgAigCkAFBAUYNCiACIAItAJgBOgBsIAIgAigClAE2AmggAkGQAWogAkHoAGpBk4TAAEEKIAZBSGoQDiACKAKQAUEBRg0KIAJBkAFqIAJB6ABqQfmAwABBBiAGQVRqEBEgAigCkAFBAUYNASACQYABaiACKAJoIAItAGwQ5AEMCwsgAkGQAWogBUGLhMAAQQQQ7gEgAigCkAFBAUYNCSACIAItAJgBOgBsIAIgAigClAE2AmggAkGQAWogAkHoAGpB+YDAAEEGIAZBSGoQESACKAKQAUEBRwRAIAJBgAFqIAIoAmggAi0AbBDkAQwLCwwJCwwICyAIKAIAIANGBEAgBSADQQEQDSAEKAIAIQMLIAQgA0EBajYCACAFKAIAIANqQfsAOgAAIAJBkAFqIAVB/4DAAEEEEOkBIAIoApABQQFGDQMgBCgCACIDIAgoAgBGBEAgBSADQQEQDSAEKAIAIQMLIAQgA0EBajYCACAFKAIAIANqQTo6AAACQAJAAkACQAJAIAZBSGooAgBBAWsOBAMCAQAECyACQZABaiAFQaSEwABBCxDuASACKAKQAUEBRg0JIAIgAi0AmAE6AGwgAiACKAKUATYCaCACQZABaiACQegAakHYgcAAQQ0gBkFMahAOIAIoApABQQFHBEAgAkGAAWogAigCaCACLQBsEOQBDAsLDAkLIAJBkAFqIAVBr4TAAEEMEO4BIAIoApABQQFGDQggAiACLQCYAToAbCACIAIoApQBNgJoIAJBkAFqIAJB6ABqQdiBwABBDSAGQUxqEA4gAigCkAFBAUYNCCACQZABaiACQegAakG7hMAAQQUgBkFYahAOIAIoApABQQFHBEAgAkGAAWogAigCaCACLQBsEOQBDAoLDAgLIAJBkAFqIAVBwITAAEEHEO4BIAIoApABQQFGDQcgAiACLQCYAToAbCACIAIoApQBNgJoIAJBkAFqIAJB6ABqQdiBwABBDSAGQUxqEA4gAigCkAFBAUYNByACQZABaiACQegAakHHhMAAQQsgBkFoaikDABAQIAIoApABQQFGDQcgAkGQAWogAkHoAGpB8IHAACAGQVhqEAwgAigCkAFBAUcEQCACQYABaiACKAJoIAItAGwQ5AEMCQsMBwsgAkGQAWogBUHShMAAQQsQ7gEgAigCkAFBAUYNBiACIAIoApQBIgM2AmgCQCACLQCYAQRAIANBCGooAgAhBwwBCyADKAIIIgkgA0EEaigCAEYEQCADIAlBARANIAMoAgghCQsgAyAJQQFqIgc2AgggAygCACAJakEsOgAACyACQQA6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KAIAIARqQeTC0YsGNgAAIAUgBEEEaiIENgIAIAYoAgAgBGtBAU0EQCADIARBAhANIANBCGooAgAhBAsgAygCACAEakGi9AA7AAAgA0EIaiAEQQJqNgIAIAEoAgANASACQZABaiADEOoBDAILIAJBjAFqIAhBCGooAgA2AgAgAiAIKQIANwKEAQwKCyACQYABaiABELgBIAJBkAFqIAMgAigCgAEiASACKAKIARDpASACKAKEAUUNACABEJUCCyACKAKQAUEBRw0BIAJBLGogAkGcAWooAgA2AgAgAiACKQKUATcCJAwNCyACQfwAaiACQZABakEEciIBQQhqKAIANgIAIAIgASkCADcCdAwICyACQSBqIANBABDjASACKAIgQQFGDQsgAigCCCIDIAIoAgRGBEAgAiADQQEQDSACKAIIIQMLIAIoAgAgA2pB/QA6AAAgAiADQQFqNgIIDAQLIAJB1ABqIAJBnAFqKAIANgIAIAIgAikClAE3AkwMCAsgAkGMAWogAkGcAWooAgA2AgAgAiACKQKUATcChAEMBAsgAkEQaiACIAFBBGoQISACKAIQQQFHDQEMCQsgAkEsaiACQZABakEEciIBQQhqKAIANgIAIAIgASkCADcCJAwHCyACQYgBaiACQQhqKAIAIgE2AgAgAiACKQMAIhE3A4ABIABBDGogATYCACAAIBE3AgRBAAwICyACQRxqIAJBnAFqKAIANgIAIAIgAikClAE3AhQMBgsgAkH8AGogAkGMAWooAgA2AgAgAiACKQKEATcCdAsgAkEsaiACQfwAaigCADYCACACIAIpAnQ3AiQMAwsgAkHEAGogAkGQAWpBBHIiAUEIaigCADYCACACIAEpAgA3AjwMAQsgAkHEAGogAkHUAGooAgA2AgAgAiACKQJMNwI8CyACQSxqIAJBxABqKAIANgIAIAIgAikCPDcCJAsgAkEcaiACQSxqKAIANgIAIAIgAikCJDcCFAsgAkGIAWoiASACQRxqKAIANgIAIAIgAikCFDcDgAECQCACKAIAIgNFDQAgAigCBEUNACADEJUCCyACQZgBaiABKAIANgIAIAIgAikDgAE3A5ABIABBCGpB+YjAAEHhACACQZABahAiQQELNgIAIAJBoAFqJAALuQMCAn8BfiMAQTBrIgIkACABKAIIIgMgAUEEaigCAEYEQCABIANBARANIAEoAgghAwsgASADQQFqNgIIIAEoAgAgA2pB+wA6AAAgAkEgaiABQYOBwABBBhDpAQJAAkACQAJAIAIoAiBBAUcEQCABKAIIIgMgAUEEaigCAEYEQCABIANBARANIAEoAgghAwsgASADQQFqNgIIIAEoAgAgA2pBOjoAACACQSBqIAEQ7QEgAigCIEEBRw0BIAJBHGogAkEgakEEciIBQQhqKAIANgIAIAIgASkCADcCFAwCCyACQRhqIAJBLGooAgAiATYCACACIAIpAiQiBDcDECAAQQxqIAE2AgAgACAENwIEIABBATYCAAwDCyACQRBqIAIoAiQgAkEoai0AABDjASACKAIQQQFHDQELIAJBCGogAkEcaigCACIBNgIAIAIgAikCFCIENwMAIABBDGogATYCACAAIAQ3AgQgAEEBNgIADAELIAEoAggiAyABQQRqKAIARgRAIAEgA0EBEA0gASgCCCEDCyAAQQA2AgAgASADQQFqNgIIIAEoAgAgA2pB/QA6AAALIAJBMGokAAv6BgIFfwF+IwBB0ABrIgMkACABKAIAIQUCQCABLQAEBEAgBUEIaigCACEEDAELIAUoAggiBiAFQQRqKAIARgRAIAUgBkEBEA0gBSgCCCEGCyAFIAZBAWoiBDYCCCAFKAIAIAZqQSw6AAALIAFBADoABCAFQQRqIgYoAgAgBEYEQCAFIARBARANIAVBCGooAgAhBAsgBSgCACAEakEiOgAAIAVBCGoiASAEQQFqIgQ2AgAgBigCACAEa0EJTQRAIAUgBEEKEA0gASgCACEECyAFKAIAIARqIgZBrYXAACkAADcAACAGQQhqQbWFwAAvAAA7AAAgASAEQQpqIgQ2AgAgBUEEaigCACAEa0EBTQRAIAUgBEECEA0gBUEIaigCACEECyAFKAIAIARqQaL0ADsAACAFQQhqIARBAmo2AgAgAigCACEEIANBQGsgBSACKAIIIgEQ7AECQAJAAkACQCADKAJAQQFHBEAgA0HIAGotAAAhBiADKAJEIQICQCABBEAgBCABQRhsaiEHIAZB/wFxRSEBIANBQGtBBHIhBQNAIAFBAXEEQCACKAIIIgEgAkEEaigCAEYEQCACIAFBARANIAIoAgghAQsgAiABQQFqNgIIIAIoAgAgAWpBLDoAAAsgA0FAayACEO0BIAMoAkBBAUYNAiADIAMtAEg6ADwgAyADKAJENgI4IANBQGsgA0E4akHogcAAQQMgBBAOIAMoAkBBAUYNBCADQUBrIANBOGpBwIXAAEEFIARBDGoiBBAOIAMoAkBBAUYEQCADQTRqIANBzABqKAIANgIAIAMgAykCRDcCLAwGCyADQShqIAMoAjggAy0APBDjASADKAIoQQFGDQVBASEBIARBDGoiBCAHRw0AC0EAIQYLQQAhBCADQRhqIAIgBkH/AXFBAEcQ4gEgAygCGEEBRg0EDAULIANBNGogBUEIaigCADYCACADIAUpAgA3AiwMAgsgA0EkaiADQUBrQQRyIgFBCGooAgA2AgAgAyABKQIANwIcDAILIANBNGogA0HMAGooAgA2AgAgAyADKQJENwIsCyADQSRqIANBNGooAgA2AgAgAyADKQIsNwIcCyADQRBqIANBJGooAgAiATYCACADIAMpAhwiCDcDCCAAQQxqIAE2AgAgACAINwIEQQEhBAsgACAENgIAIANB0ABqJAAL+gICAn8BfiMAQSBrIgQkACABKAIIIgMgAUEEaigCAEYEQCABIANBARANIAEoAgghAwsgASADQQFqNgIIIAEoAgAgA2pB+wA6AAAgBEEQaiABQZKFwABBBRDpAQJAAkAgBCgCEEEBRwRAIAEoAggiAyABQQRqKAIARgRAIAEgA0EBEA0gASgCCCEDCyABIANBAWo2AgggASgCACADakE6OgAAIARBEGogASACKAIAIAIoAggQ6QEgBCgCEEEBRg0BIAEoAggiAyABQQRqKAIARgRAIAEgA0EBEA0gASgCCCEDCyAAQQA2AgAgASADQQFqNgIIIAEoAgAgA2pB/QA6AAAMAgsgBEEIaiAEQRxqKAIAIgE2AgAgBCAEKQIUIgU3AwAgAEEMaiABNgIAIAAgBTcCBCAAQQE2AgAMAQsgBEEIaiAEQRxqKAIAIgE2AgAgBCAEKQIUIgU3AwAgAEEMaiABNgIAIAAgBTcCBCAAQQE2AgALIARBIGokAAv9AQEDfyMAQUBqIgQkAAJAAkACQCACQQBOBEAgAg0BQQEhBQwCCxDMAgALIAIhBiACQQEQVSIFRQ0BCyAFIAEgAhCcAyEBIARCADcCBCAEQYiNwAAoAgA2AgAgBEEQaiAEQeiFwAAQ+gIgAyAEQRBqEOUBRQRAIABBDGogAjYCACAAQQhqIAY2AgAgACABNgIEIABBEGogBCkDADcCACAAQQg2AgAgAEEYaiAEQQhqKAIANgIAAkAgAygCACIARQ0AIANBBGooAgBFDQAgABCVAgsgBEFAayQADwtBgIbAAEE3IARBOGpBrIzAAEGEh8AAEOgCAAsgAkEBEMsCAAudDwICfwF+IwBB4ABrIgIkACACQQhqEOYBIAACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAEoAgBBAWsOAgIBAAsgAigCECIDIAIoAgxGBEAgAkEIaiADQQEQDSACKAIQIQMLIAIoAgggA2pB+wA6AAAgAiADQQFqNgIQIAJB0ABqIAJBCGpBiYHAAEEEEOkBIAIoAlBBAUYNAyACKAIQIgMgAigCDEYEQCACQQhqIANBARANIAIoAhAhAwsgAigCCCADakE6OgAAIAIgA0EBajYCEAJAAkACQCABKAIEQQFHBEAgAkHQAGogAkEIakGpgcAAQQcQ7gEgAigCUEEBRg0BIAIgAigCVDYCSCACIAJB2ABqLQAAOgBMIAJB0ABqIAJByABqQaKBwABBByABQQhqEA4gAigCUEEBRg0CIAJB0ABqIAJByABqQfSAwABBBSABQRRqEA4gAigCUEEBRg0DIAJBOGogAigCSCACLQBMEOQBDA8LIAJB0ABqIAJBCGpBloHAAEEMEO4BIAIoAlBBAUYNBSACIAIoAlQ2AkggAiACQdgAai0AADoATCACQdAAaiACQcgAakGigcAAQQcgAUEIahAOIAIoAlBBAUcEQCACQThqIAIoAkggAi0ATBDkAQwPCyACQcQAaiACQdwAaigCADYCACACIAIpAlQ3AjwgAkEBNgI4DA4LIAJBxABqIAJB3ABqKAIANgIAIAIgAikCVDcCPCACQQE2AjgMDQsgAkHEAGogAkHcAGooAgA2AgAgAiACKQJUNwI8IAJBATYCOAwMCyACQcQAaiACQdwAaigCADYCACACIAIpAlQ3AjwgAkEBNgI4DAsLIAIoAhAiAyACKAIMRgRAIAJBCGogA0EBEA0gAigCECEDCyACKAIIIANqQfsAOgAAIAIgA0EBajYCECACQdAAaiACQQhqQf+AwABBBBDpASACKAJQQQFGDQggAigCECIDIAIoAgxGBEAgAkEIaiADQQEQ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AUYNAyADQegAaiADQcgAaigCADYCACADIANBQGspAwA3A2AgA0EwaiADQeAAahC5ASADQagBaiADKAIwIAMoAjQQ2AEgA0EoaiADQagBahDgASADLQAoQQFxDQJBBCEBDAgLIANBiAFqIANB2ABqKQMANwMAIANBgAFqIANB0ABqKQMANwMAIANB+ABqIANByABqKQMANwMAIAMgA0FAaykDADcDcCADQaQBakEBNgIAIANCATcClAEgA0GIg8AANgKQASADQQM2AqwBIAMgA0GoAWo2AqABIAMgA0HwAGo2AqgBIANBwAFqIANBkAFqEM0CIANB8ABqECYgAygCwAEhASAAQRBqIAMpAsQBNwIAIABBDGogATYCACAAQQhqQQI2AgAgAEEBNgIADAoLIANBiAFqIANB1ABqKAIANgIAIANBgAFqIANBzABqKQIANwMAIANB+ABqIAQpAgA3AwAgAyADKQI8NwNwIANBpAFqQQE2AgAgA0IBNwKUASADQcSCwAA2ApABIANBBDYCrAEgAyADQagBajYCoAEgAyADQfAAajYCqAEgA0HAAWogA0GQAWoQzQIgAEEIakECNgIAIABBDGogAykDwAE3AgAgAEEUaiADQcgBaigCADYCACAAQQE2AgACQAJAAkACQCADKAJwDgQBAgMMAAsgAygCdCIARQ0LIANB+ABqKAIARQ0LIAAQlQIMCwsCQCADKAJ0IgBFDQAgA0H4AGooAgBFDQAgABCVAgsgA0GAAWooAgAiAEUNCiADQYQBaigCAEUNCiAAEJUCDAoLAkAgAygCdCIARQ0AIANB+ABqKAIARQ0AIAAQlQILIANBgAFqKAIAIgBFDQkgA0GEAWooAgBFDQkgABCVAgwJCyADKAJ0IgBFDQggA0H4AGooAgBFDQggABCVAgwICyADLQApQfsARwRAQQ4hAQwGCyADQagBahDZASADQSBqIANBqAFqENcBIAMoAiAhBSADIAMtACRBAXEiAjoAvAEgAyAFNgK4ASADQRhqIAUQ4AFBAiEBIAMtABhBAXFFDQIgAy0AGSEEIANB8ABqQQRyIQwgA0HAAWpBBHIhDSACIQYDQAJAAkACQAJAAkACQAJAAkAgBEH/AXEiCUEsRwRAIAlB/QBGDQMgAkH/AXENAUEJIQEMDAsgBkH/AXENACAFENkBIANBEGog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IABBDGooAgBFDQMMBAsCQCAAQQhqKAIAIgFFDQAgAEEMaigCAEUNACABEJUCCyAAQRRqKAIAIgFFDQIgAEEYaigCAEUNAgwDCwJAIABBCGooAgAiAUUNACAAQQxqKAIARQ0AIAEQlQILIABBFGooAgAiAUUNASAAQRhqKAIARQ0BDAILIABBCGooAgAiAUUgAEEMaigCAEVyIQIgAEEEaigCAEUEQCACRQRAIAEQlQILIABBFGooAgAiAUUNASAAQRhqKAIARQ0BDAILIAINACABEJUCCw8LIAEQlQILDgAgACgCACABEDQaQQAL2AIBA38jAEEQayICJAACQAJ/AkACQCABQYABTwRAIAJBADYCDCABQYAQSQ0BIAFBgIAETw0CIAIgAUE/cUGAAXI6AA4gAiABQQx2QeABcjoADCACIAFBBnZBP3FBgAFyOgANQQMMAwsgACgCCCIDIABBBGooAgBGBEAgACADQQEQDSAAKAIIIQMLIAAgA0EBajYCCCAAKAIAIANqIAE6AAAMAwsgAiABQT9xQYABcjoADSACIAFBBnZBwAFyOgAMQQIMAQsgAiABQT9xQYABcjoADyACIAFBEnZB8AFyOgAMIAIgAUEGdkE/cUGAAXI6AA4gAiABQQx2QT9xQYABcjoADUEECyEBIABBBGooAgAgAEEIaiIEKAIAIgNrIAFJBEAgACADIAEQDSAEKAIAIQMLIAAoAgAgA2ogAkEMaiABEJwDGiAEIAEgA2o2AgALIAJBEGokAEEAC1oBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBxIvAACACQQhqEN0CIAJBIGokAAtPAQJ/IAAoAgAiA0EEaigCACADQQhqIgQoAgAiAGsgAkkEQCADIAAgAhANIAQoAgAhAAsgAygCACAAaiABIAIQnAMaIAQgACACajYCAEEAC8AEAQF/IwBBsAFrIgIkACACQQhqIAFBDGopAgA3AwAgAkEQaiABQRRqKQIANwMAIAJBGGogAUEcaikCADcDACACQSBqIAFBJGopAgA3AwAgAkEoaiABQSxqKQIANwMAIAIgASkCBDcDAAJAAkAgASgCAEEBRwRAIABBADYCACAAIAFBBGoiASkCADcCBCAAQSxqIAFBKGopAgA3AgAgAEEkaiABQSBqKQIANwIAIABBHGogAUEYaikCADcCACAAQRRqIAFBEGopAgA3AgAgAEEMaiABQQhqKQIANwIADAELIAJByABqIAJBHGopAgA3AwAgAkFAayACQRRqKQIANwMAIAJBOGogAkEMaikCADcDACACIAIpAgQ3AzAgAkIANwJUIAJBiI3AACgCADYCUCACQeAAaiACQdAAakHohcAAEPoCAkAgAigCMEEMRgRAIAJBrAFqQQA2AgAgAkH4jMAANgKoASACQgE3ApwBIAJBjJPAADYCmAEgAkHgAGogAkGYAWoQ/AJFDQEMAwsgAkGsAWpBATYCACACQgE3ApwBIAJBlJPAADYCmAEgAkEGNgKMASACIAJBiAFqNgKoASACIAJBlAFqNgKIASACIAJBMGo2ApQBIAJB4ABqIAJBmAFqEPwCDQILIAAgAikDUDcCBCAAQQE2AgAgAEEMaiACQdgAaigCADYCACACKAIwQQxGDQAgAkEwahAmCyACQbABaiQADwtBgIbAAEE3IAJBmAFqQayMwABBhIfAABDoAgALSgECfyAAQQRqKAIAIABBCGoiBCgCACIDayACSQRAIAAgAyACEA0gBCgCACEDCyAAKAIAIANqIAEgAhCcAxogBCACIANqNgIAQQALogEBAn8CQAJ/AkACfwJAAkACQCACBEBBASEFIAFBAEgNByADKAIAIgRFDQIgAygCBCIDDQEgAQ0DDAULIAAgATYCBEEBIQUMBgsgBCADIAIgARBWDAILIAFFDQILIAEgAhBVCyEDIAEMAQsgAiEDQQALIQQgAwRAIAAgAzYCBEEAIQUMAQsgACABNgIEIAIhBAsgACAFNgIAIABBCGogBDYCAAvgAQIDfwF+IwBBIGsiAiQAAkAgAUEBaiIDIAFJDQAgAEEEaigCACIBQQF0IgQgAyAEIANLGyIDQQQgA0EESxutQhh+IgVCIIinRUECdCEDIAWnIQQCQCABBEAgAkEYakEENgIAIAIgAUEYbDYCFCACIAAoAgA2AhAMAQsgAkEANgIQCyACIAQgAyACQRBqEDkgAigCAEEBRgRAIAJBCGooAgAiAEUNASACKAIEIAAQywIACyACKAIEIQEgAEEEaiACQQhqKAIAQRhuNgIAIAAgATYCACACQSBqJAAPCxDMAgAL4wECA38BfiMAQSBrIgIkAAJAIAFBAWoiAyABSQ0AIABBBGooAgAiAUEBdCIEIAMgBCADSxsiA0EEIANBBEsbrULoAH4iBUIgiKdFQQN0IQMgBachBAJAIAEEQCACQRhqQQg2AgAgAiABQegAbDYCFCACIAAoAgA2AhAMAQsgAkEANgIQCyACIAQgAyACQRBqEDkgAigCAEEBRgRAIAJBCGooAgAiAEUNASACKAIEIAAQywIACyACKAIEIQEgAEEEaiACQQhqKAIAQegAbjYCACAAIAE2AgAgAkEgaiQADwsQzAIAC+ABAQN/IwBBIGsiAiQAAkAgAUEBaiIDIAFJDQAgAEEEaigCACIBQQF0IgQgAyAEIANLGyIDQQQgA0EESxsiA0H///8/cSADRkEDdCEEIANBBXQhAwJAIAEEQCACQRhqQQg2AgAgAiABQQV0NgIUIAIgACgCADYCEAwBCyACQQA2AhALIAIgAyAEIAJBEGoQOSACKAIAQQFGBEAgAkEIaigCACIARQ0BIAIoAgQgABDLAgALIAIoAgQhASAAQQRqIAJBCGooAgBBBXY2AgAgACABNgIAIAJBIGokAA8LEMwCAAvhAQEDfyMAQSBrIgIkAAJAIAFBAWoiAyABSQ0AIABBBGooAgAiAUEBdCIEIAMgBCADSxsiA0EEIANBBEsbIgNB/////wNxIANGQQJ0IQQgA0ECdCEDAkAgAQRAIAJBGGpBBDYCACACIAFBAnQ2AhQgAiAAKAIANgIQDAELIAJBADYCEAsgAiADIAQgAkEQahA5IAIoAgBBAUYEQCACQQhqKAIAIgBFDQEgAigCBCAAEMsCAAsgAigCBCEBIABBBGogAkEIaigCAEECdjYCACAAIAE2AgAgAkEgaiQADwsQzAIAC/EBAQF/IwBBgAFrIgUkACAFIAI2AgwgBSABNgIIIAVBJGpBAjYCACAFQTRqQQc2AgAgBUICNwIUIAVB9I3AADYCECAFQQE2AiwgBSAENgI8IAUgAzYCOCAFIAVBKGo2AiAgBSAFQThqNgIwIAUgBUEIajYCKCAFQgA3AkQgBUGIjcAAKAIANgJAIAVB0ABqIAVBQGtB6IXAABD6AiAFQRBqIAVB0ABqEOcCBEBBgIbAAEE3IAVB+ABqQayMwABBhIfAABDoAgALIAAgBSkDQDcCBCAAQRQ2AgAgAEEMaiAFQcgAaigCADYCACAFQYABaiQAC5cCAQF/IwBB4ABrIgMkACADIAI2AgQgAyABNgIAIANBCGogASACEJADAkAgAy0ACEEBRwRAIABBADYCACAAQQhqIAMpAxA3AwAgAEEQaiADQRhqKQMANwMADAELIAMgAy0ACToAJyADQcwAakECNgIAIANB3ABqQQI2AgAgA0ICNwI8IANB1I7AADYCOCADQQE2AlQgAyADQdAAajYCSCADIANBJ2o2AlggAyADNgJQIANBKGogA0E4ahDNAiADQThqQQRyIANBKGoQzwIgA0EUNgI4AkAgAygCKCIBRQ0AIAMoAixFDQAgARCVAgsgACADKQM4NwIEIABBATYCACAAQQxqIANBQGspAwA3AgALIANB4ABqJAAL/wMBBH8jAEHQAGsiAiQAIAJBEGogASgCACIDEOABAkACQCACLQAQQQFxRQRAQQIhAQwBCwJAAkACQAJAAkAgAi0AESIEIgVBLEcEQCAFQf0ARg0DIAEtAAQNAUEJIQEMBgsgAS0ABA0AIAMQ2QEgAkEIaiADEOABIAItAAhBAXFFDQQgAi0ACSEEDAELIAFBADoABAsgBEH/AXEiAUH9AEcEQCABQSJHBEBBECEBDAULIAIgAxDgASACLQAAQQFxRQ0DIAItAAFBIkcEQEEOIQEMBQsgAxDZASACQShqIAMQ3wEgAkE0aigCACEEIAJBMGooAgAhBSACKAIsIQEgAigCKEEBRwRAIARFIAFFIAVFcnINAyAFEJUCDAMLIAFBFUYNAiACQThqKAIAIQMMBAtBEyEBDAMLIABBADsBAAwDCyACQShqIAMQ3gECQCACKAIoIgFBFUcEQCACQSRqIAJBNGooAgA2AgAgAiACKQIsNwIcIAIgATYCGAwBCyACQRhqIAMQGCACKAIYQRVHDQAgAEGAAjsBAAwDCyAAQQE6AAAgAEEEaiACKQMYNwIAIABBDGogAkEgaikDADcCAAwCC0EEIQELIABBAToAACAAQRBqIAM2AgAgAEEMaiAENgIAIABBCGogBTYCACAAQQRqIAE2AgALIAJB0ABqJAAL/QEBAX8jAEHgAGsiAyQAIAMgAjYCBCADIAE2AgAgA0EIaiABIAIQtwECQCADKAIIQQFHBEAgACADKQIMNwIEIABBADYCACAAQQxqIANBFGooAgA2AgAMAQsgA0HUAGpBATYCACADQgE3AkQgA0GUjsAANgJAIANBATYCXCADIANB2ABqNgJQIAMgAzYCWCADQTBqIANBQGsQzQIgA0FAa0EEciADQTBqEM8CIANBFDYCQAJAIAMoAjAiAUUNACADKAI0RQ0AIAEQlQILIAAgAykDQDcCBCAAQQE2AgAgAEEMaiADQcgAaikDADcCACADQRBqECYLIANB4ABqJAALwgICBH8BfiMAQTBrIgIkACACQQhqIAEoAgAiAxDgAQJAAkAgAi0ACEEBcQRAIAItAAkiBCIFQSxHBEACQCAFQd0ARwRAIAEtAAQNASAAQQE6AAAgAEEEakEHNgIADAULIABBADsBAAwECyABQQA6AAQMAgsgAxDZASACIAMQ4AEgAi0AAEEBcQRAIAItAAEhBAwCCyAAQQE6AAAgAEEEakEENgIADAILIABBAToAACAAQQRqQQE2AgAMAQsgBEHdAEYEQCAAQQE6AAAgAEEEakETNgIADAELIAJBIGogAxAYIAIoAiAiAUEVRgRAIABBgAI7AQAMAQsgAkEYaiACQSxqKAIAIgM2AgAgAiACKQIkIgY3AxAgAEEQaiADNgIAIABBCGogBjcCACAAQQRqIAE2AgAgAEEBOgAACyACQTBqJAAL8AEBBH8jAEEgayICJAAgAiABEOABAkACQAJAIAItAABBAXEEQCACLQABQSJHDQEgARDZASACQQhqIAEQ3wEgAkEYaigCACEEIAJBFGooAgAhAyACQRBqKAIAIQEgAigCDCEFIAIoAghBAUYNAiAFRQRAIAAgASADEEQMBAsgACABIAQQRCADRQ0DIAEQlQIMAwsgAEEBOgAAIABBBGpBBDYCAAwCCyAAQQE6AAAgAEEEakEONgIADAELIABBAToAACAAQRBqIAQ2AgAgAEEMaiADNgIAIABBCGogATYCACAAQQRqIAU2AgALIAJBIGokAAumAgACQAJAAkACQAJAAkACQAJAAkACQAJAIAJBe2oOFAUABgEICAMCCAgICAgICAgECAgHCAsgAUHNk8AAQQYQnwMNByAAQQE6AAEMCQsgASkAAELj4NX7xe3bt/AAUg0GIABBAjoAAQwICyABQduTwABBDBCfAw0FIABBAzoAAQwHCyABQeeTwABBCxCfAw0EIABBBDoAAQwGCyABQfKTwABB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KAIAIQUgA0GIAmooAgAhACADKAKEAiEEIAMoAoACIQIgCg0LDAwLIANBMGogARDeAQJAIAMoAjAiAkEVRwRAIAsgCCkCADcCACALQQhqIAhBCGooAgA2AgAMAQsgA0GAAmogARAXIAMoAoACQQFHBEAgAygCjAIhGiADKAKIAiEKIAMoAoQCIQcMBwsgAygChAIhAgsgA0GQAmooAgAhBSADKAKMAiEAIAMoAogCIQQMCwsgA0EwaiABEN4BAkAgAygCMCICQRVHBEAgCyAIKQIANwIAIAtBCGogCEEIaigCADYCAAwBCyADQYACaiABEBcgAygCgAJBAUcEQCADKAKMAiEbIAMoAogCIQkgAygChAIhBgwGCyADKAKEAiECCyADQZACaigCACEFIAMoAogCIQQgAygCjAIMAgsgA0GAAmogA0HQAWoQ3AEgAygCgAIiAkEVRwRAIANBjAJqKAIAIQUgA0GIAmooAgAhACADKAKEAiEEIAkEQCAGEJUCCyAKRQ0LIAcQlQIMCwsgA0GAAmogA0HQAWoQ2gEgAygCgAIiAkEVRwRAIANBjAJqKAIAIQUgA0GIAmooAgAhACADKAKEAiEEIAkEQCAGEJUCCyAKRQ0LIAcQlQIMCwsgA0HIAmogBzYCACADQcQCaiIBIBs2AgAgA0HQAmoiAiAaNgIAIANBzAJqIgAgCjYCACADQcABaiIEIAEpAgA3AwAgA0HIAWoiASAAKQIANwMAIAMgCTYCwAIgAyAGNgK8AiADIAMpArwCNwO4ASADQYQDakHggMAANgIAIANB/AJqQbSAwAA2AgAgA0H0AmpBnIDAADYCACADQcACaiADQbABaikDADcDACAAIBM2AgAgAiADKQOYATcDACADQdgCaiADQaABaikDADcDACADQZwDaiAONgIAIANBmANqIA82AgAgA0GUA2ogEDYCACADQZADaiAYNgIAIANBjANqIBU2AgAgA0HoAmogHDcDACADQeQCaiAUNgIAIANBgANqIANBuANqNgIAIANB+AJqIANBuANqNgIAIAMgAykDqAE3A7gCIAMgDDYCyAIgAyARNgKIAyADIA02AuACIAMgA0G4A2o2AvACIANBsANqIAEpAwA3AwAgA0GoA2ogBCkDADcDACADIAMpA7gBNwOgAyADQYACaiADQfACaiADQbgCaiADQYgDaiADQaADahBMIANBMGogA0GAAmoQNyADKAKMAQRAIBkQlQILIAMoAnwEQCAXEJUCCyADKAJsRQ0NIBIQlQIMDQsgA0GAAmpBBHJBnJPAAEEIEBQgA0GQAmooAgAhBSADQYgCaigCACEEIAMoAoQCIQIgA0GMAmooAgALIQBBACEGDAULIANBgAJqIAEQ3gECQCADKAKAAiICQRVHBEAgCCAWKQIANwIAIAhBCGogFkEIaigCADYCAAwBCyADQTBqIAEQGCADKAIwIgJBFUYNAQsgA0E8aigCACEFIANBOGooAgAhACADKAI0IQQMBAsgAyABEOABQQAhACADLQABIQIgAy0AAEEBcQ0AC0ECIQIMAgsgA0HoAWogACkDADcDACADQeABaiACKQMANwMAIANB2AFqIAEpAwA3AwAgAyADKQPAAjcD0AEgA0IANwK8ASADQYiNwAAoAgA2ArgBIANBgAJqIANBuAFqQeiFwAAQ+gIgA0HQAWogA0GAAmoQygFFBEAgA0E8aiADQcABaigCADYCACADIAMpA7gBNwI0IANBATYCMCADQdABahAmDAYLDAgLQQQhAgsgCkUgB0VyDQELIAcQlQILIAlFIAZFcg0AIAYQlQILIANBjAJqIAU2AgAgA0GIAmogADYCACADIAQ2AoQCIAMgAjYCgAIgA0HAAmpBq4jAAEEdIANBgAJqEBkgA0HoAWogA0HYAmopAwA3AwAgA0HgAWogA0HQAmopAwA3AwAgA0HYAWogA0HIAmopAwA3AwAgAyADKQPAAjcD0AEgA0IANwL0ASADQYiNwAAoAgA2AvABIANBgAJqIANB8AFqQeiFwAAQ+gIgA0HQAWogA0GAAmoQygENAyADQTxqIANB+AFqKAIANgIAIAMgAykD8AE3AjQgA0EBNgIwIANB0AFqECYgEUUgFUVyRQRAIBEQlQILIA4EQCAOQQV0IQEgEEEUaiECA0ACQCACQXxqKAIAIgBFDQAgAigCAEUNACAAEJUCCyACQSBqIQIgAUFgaiIBDQALCyAPRSAPQQV0RXINACAQEJUCCyAMRSATRXJFBEAgDBCVAgsgDUUgFEVyDQAgDRCVAgsCQCADKAKIASIARQ0AIAMoAowBRQ0AIAAQlQILAkAgAygCeCIARQ0AIAMoAnxFDQAgABCVAgsgAygCbEUNACASEJUCCyADQbgCaiADQTBqEB4gAygCuAJBAUcEQCADQYgCaiADQcQCaigCACIANgIAIAMgAykCvAIiHDcDgAIgA0HAAmogADYCACADIBw3A7gCIANBuAJqEMsBIANBMGoQLyADQcADaiQADwsgA0GYAmogA0HYAmopAwA3AwAgA0GQAmogA0HQAmopAwA3AwAgA0GIAmogA0HIAmopAwA3AwAgAyADQcACaikDADcDgAJBvIzAAEErIANBgAJqQeiMwABB1IPAABDoAgALQYCGwABBNyADQbgDakGsjMAAQYSHwAAQ6AIAC64KAgd/BH4jAEGQAWsiBCQAIARB6ABqIAEoAgAiBkH4jsAAQQYgASgCBCIKKAIMEQYAAkACQAJAAkACQCAEKAJoIgdFBEBBBUEBEFUiAUUNASABQQRqQZ2PwAAtAAA6AAAgAUGZj8AAKAAANgAAIABBEGpChYCAgNAANwMAIABBDGogATYCACAAQQhqQQY2AgAgAEEBNgIADAQLIAQoAmwhCSAEQegAaiAHIARB8ABqIgUoAgAQHQJAAkAgBCgCaEEBRwRAIARB4ABqIARBjAFqKAIAIgU2AgAgBEEQaiAEQfQAaiIIKQIANwMAIARBGGogBEH8AGopAgA3AwAgBEEgaiAEQYQBaikCADcDACAEQShqIAU2AgAgBCAEKQJsNwMIIARB6ABqIAEoAgggAygCACIFIAMoAgggAUEMaigCACgCDBEGACAEKAJoQQFGDQEgBEHIAGogCCgCACIBNgIAIARBOGoiCCABNgIAIAQgBCkCbCILNwNAIAQgCzcDMAJAIAQoAggiAUUNACAEKAIMRQ0AIAEQlQILIARBEGogCCgCADYCACAEIAQpAzA3AwggBEHoAGogBEEIahAkIAQoAmhBAUYNAiAEQfAAaigCACAGQfiOwABBBiAEKAJsIgYgBEH0AGooAgAgCigCEBEHAARAIAYQlQILIABBADYCACAAQShqQQA2AgAgAEEgakIANwIAIABBHGpBgI3AACgCACIBNgIAIABBFGpCADcCACAAQRBqIAE2AgAgAEEIakIANwIAIABB+IzAACgCADYCBAJAIAQoAggiAEUNACAEKAIMRQ0AIAAQlQILAkAgBCgCFCIARQ0AIARBGGooAgBFDQAgABCVAgsCQCAEKAIgIgBFDQAgBEEkaigC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AWooAgAhAyACKALkASEFIAdFDQogBhCVAgwKCyACQbgBaiIAIBQ2AgAgAiAHNgKwAiACIAY2AqwCIAIgAikCrAI3A7ABIAJB9AJqQeCAwAA2AgAgAkHsAmpBtIDAADYCACACQeQCakGcgMAANgIAIAJBsAJqIAJBqAFqKQMANwMAIAJBvAJqIA02AgAgAkHAAmogAikDkAE3AwAgAkHIAmogAkGYAWopAwA3AwAgAkHYAmogFTcDACACQdQCaiAONgIAIAJB8AJqIAJBiANqNgIAIAJB6AJqIAJBiANqNgIAIAIgAikDoAE3A6gCIAIgCjYCuAIgAiALNgLQAiACIAJBiANqNgLgAiACQYADaiAAKAIANgIAIAIgAikDsAE3A/gCIAJB4AFqIAJB4AJqIAJBqAJqIAJB+AJqEE4gAkE4aiACQeABahA3IAIoAoQBBEAgARCVAgsgAigCdEUNCyAMEJUCDAsLIAIoAuwBIRQgAigC6AEhByACKALkASEGCyACQQhqIAAQ4AFBACEDIAItAAkhBCACLQAIQQFxDQALQQIhAAwBCyACQdgBaiACQcgCaikDADcDACACQdABaiACQcACaikDADcDACACQcgBaiACQbgCaikDADcDACACIAIpA7ACNwPAASACQgA3ApwCIAJBiI3AACgCADYCmAIgAkHgAWogAkGYAmpB6IXAABD6AiACQcABaiACQeABahDKAUUEQCACQcQAaiACQaACaigCADYCACACIAIpA5gCNwI8IAJBATYCOCACQcABahAmIAIoAoABIQEMBwsMCAsgAAshACACQcgAaiAQNgIAIAJBxABqIAQ2AgAgAkFAayAFNgIAIAIgADYCPAsgB0UgBkVyDQELIAYQlQILIAJByABqKAIAIQQgAkHEAGooAgAhAyACQUBrKAIAIQUgAigCPCEACyACQewBaiAENgIAIAJB6AFqIAM2AgAgAiAFNgLkASACIAA2AuABIAJBsAJqQdqJwABBGSACQeABahAZIAJB2AFqIBIpAwA3AwAgAkHQAWogEykDADcDACACQcgBaiARKQMANwMAIAIgAikDsAI3A8ABIAJCADcCnAIgAkGIjcAAKAIANgKYAiACQeABaiACQZgCakHohcAAEPoCIAJBwAFqIAJB4AFqEMoBDQIgAkHEAGogAkGgAmooAgA2AgAg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ESADQfQFaigCACEZIAMoAvAFIRYgAygC7AUhEiADQcAFaiADKAKoBCIaIAMoArAEENgBIANByANqIANBwAVqEOABQQAhAiADLQDIA0EBcQ0BQQQhAAwUCyADQYAFaiADQYgGaikDADcDACADQfgEaiADQYAGaikDADcDACADQfAEaiADQfgFaikDADcDACADIAMpA/AFNwPoBCADQgA3AtQFIANBiI3AACgCADYC0AUgA0GIBWogA0HQBWpB6IXAABD6AiADQegEaiADQYgFahDKAQ0YIANB3ANqIANB2AVqKAIANgIAIAMgAykD0AU3AtQDIANBATYC0AMgA0HoBGoQJgwWCyADLQDJAyIAQfsARwRAIABBIkcEQEEKIQAMFAsgA0GIBWogA0HABWoQQyADLQCIBUEBRg0CQQ4hAAwTCyADQcAFahDZASADQYgFaiADQcAFaiIGEEMCQAJ/IAMtAIgFQQFHBEAgAy0AiQUhASADQYgFaiAGEN4BIAMoAogFIgBBFUcEQCADQZAFaikDACEcIAMoAowFDAILIAFBAWsOBwsKCQgHBgUCCyADKAKMBSIAQQh2IQIgA0GUBWopAgAhHCADQZAFaigCAAshASADIBw3AtQFIAMgATYC0AUMEwsgA0E4aiAGEOABIAMtADhBAXFFDRAgAy0AOUH7AEcNDyAGENkBIANBMGogBhDXASADLQA0IQQgA0EoaiADKAIwIgEQ4AEgAy0AKEEBcUUNDiADLQApIQAgBEEBcSEEIANB0ANqQQRyIQcgA0GIBWpBBHIhCANAAkACQAJAIABB/wFxIgVBLEcEQCAFQf0ARg0CIARB/wFxDQFBCSEADBYLIARB/wFxBEBBECEADBYLIAEQ2QEgA0EgaiABEOABIAMtACBBAXFFDRQgAy0AISEACyAAQf8BcSIJQSJHBEBBECEAIAlB/QBHDRVBEyEADBULIANBEGogARDgASADLQAQQQFxRQ0TIAMtABFBIkcNEiABENkBIANBiAVqIAEQ3wEgAygClAUhBSADKAKQBSEEIAMoAowFIQAgAygCiAVBAUcEQCAFRSAARSAERXJyDQIgBBCVAgwCCyAAQRVGDQEgAygCmAUhAgwUCyADQYgFaiAGENwBIAMoAogFIgBBFUcEQCADQZQFaigCACECIANBkAVqKAIAIQUgAygCjAUh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BEUNACATEJUCCyADQegFaiADQdADahAeIAMoAugFQQFHBEAgA0GQBWogA0H0BWooAgAiADYCACADIAMpAuwFIhw3A4gFIANB8AVqIAA2AgAgAyAcNwPoBSADQegFahDLASADQdADahAvIANB4AZqJAAPCyADQaAFaiADQYgGaikDADcDACADQZgFaiADQYAGaikDADcDACADQZAFaiADQfgFaikDADcDACADIANB8AVqKQMANwOIBUG8jMAAQSsgA0GIBWpB6IzAAEG0g8AAEOgCAAtBgIbAAEE3IANBwAVqQayMwABBhIfAABDoAgALmzcCDn8EfiMAQdAEayIEJAACQAJAAkACQAJAIAQCfwJAAkACQAJAAkACQAJAAkACQAJAAkACQCAAAn8CQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAygCAEEBaw4CAgEACyAEQbgDaiABKAIAQfiOwABBBiABKAIEKAIMEQYAIAQoArgDIgVFBEBBBSEIQQVBARBVIgMNBEEFQQEQywIACyAEKAK8AyEMIARBuANqIAUgBEHAA2oiASgCABAdIAQoArgDQQFHBEAgBEHYA2ooAgAhCCAEQdQDaigCACEDIARBzANqKAIAIQkgBEHIA2ooAgAhBiAEKAK8AyEHIAQgASkDADcCvAMgBCAHNgK4AyAEQcgCaiAEQbgDahC2ASAEKALIAiEBIAQoAswCIQcgBCgC0AIhCiAGRSAJRXJFBEAgBhCVAgsgA0UgCEVyRQRAIAMQlQILIAwEQCAFEJUCCyAEIAo2AugBIAQgBzYC5AEgBCABNgLgASAEQbgBahDmASAEQbgDaiAEQbgBahDtASAEKAK4A0EBRg0PIAQgBCgCvAM2ApABIAQgBEHAA2otAAA6AJQBIARBuANqIARBkAFqQZyTwABBCCAEQeABahAOIAQoArgDQQFHDQMgBEHUAmogBEHEA2ooAgA2AgAgBCAEKQK8AzcCzAIMEQsgBEHcA2ooAgAhCSAEQdgDaigCACEKIARB1ANqKAIAIQsgBEHQA2ooAgAhByAEQcwDaigCACEIIARByANqKAIAIQYgBEHEA2ooAgAhAyABKAIAIQEgDEUNBCAFEJUCDAQLIANBCGooAgAhBiADKAIEIQsgAkEsaigCACEMIAFBFGooAgAhDyABKAIQIRAgBEGoAmogAigCKCINIAJBMGooAgAiERDQAiAGBEAgBCgCqAIhAyAEKAKsAiEFIAQoArACIQggBEH4AWohCSAEQfABaiEKIARB6AFqIQ4gBiEHA0AgAyEBIARByAJqEP4BIARByAJqIAMgCBD7ASAJQgA3AwAgCkIANwMAIA5CADcDACAEQgA3A+ABIARBuANqIARByAJqQfAAEJwDGiAEQbgDaiAEQeABahD/AUEgQQEQVSIDRQ0GIAMgBCkD4AE3AAAgA0EYaiAJKQMANwAAIANBEGogCikDADcAACADQQhqIA4pAwA3AAAgAUUgBUVyRQRAIAEQlQILQSAhBUEgIQggB0F/aiIHDQALIAQgAzYCqAIgBEKggICAgAQ3AqwCCyALRQRAIARBwANqIARBsAJqKAIANgIAIAQgBCkDqAI3A7gDIARByAJqIARBuANqELYBIARBxAFqIARB0AJqKAIANgIAIARBADYCuAEgBCAEKQPIAjcCvAEgDEUNIiANEJUCDCILIAQgETYCwAMgBCAMNgK8AyAEIA02ArgDIARBuAJqIARBuANqELYBIARBwARqEOYBIARBiAJqIARBwARqQYKVwABBBxDuASAEKAKIAkEBRg0FIAQgBCgCjAI2ArgEIAQgBEGQAmotAAA6ALwEIARBiAJqIARBuARqQYmVwABBBSALQX9qEA8gBCgCiAJBAUYNBiAEQYgCaiAEQbgEakGOlcAAQQQgBhAPIAQoAogCQQFGBEAgBEH0AGogBEGUAmooAgA2AgAgBCAEKQKMAjcCbAwhCyAEQegAaiAEKAK4BCAELQC8BBDkASAEKAJoQQFGDSAgBEHoAWogBEHIBGooAgAiATYCACAEQcgAaiAEKQO4AjcDACAEQdAAaiAEQcACaigCADYCACAEQdQAaiAEKQPABCISNwIAIARB3ABqIAE2AgAgBCASNwPgASAEQQA2AkQgBEECNgJAIARBuANqIARBQGsQIyAEKAK4A0EBRwRAIARBwANqKAIAIREgBEG4A2ogECAEKAK8AyIQIARBxANqIgMoAgAgDygCDBEGAAJAAkAgBCgCuANBAUcEQCAEKAK8A0EBRg0CIARByARqIARByANqKAIANgIAIAQgBEHAA2opAwA3A8AEIARBOGogBEHABGoQuQEgBEHoAGogBCgCOCAEKAI8ENgB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DAYLIAJBrANqKAIAIQUgAkGoA2ooAgAhBiACKAKkAyEDDAMLQQIhAAwCC0EOIQAMAQtBBCEACyAAQQh2IQQgBq0gBa1CIIaEIRUMAQtBBCEACyAAQf8BcSAEQQh0ciEECyACQaACaiAVNwMAIAIgAzYCnAIgAiAENgKYAiACQcgCakHmisAAQRcgAkGYAmoQGSACQbgDaiACQeACaikDADcDACACQbADaiACQdgCaikDADcDACACQagDaiACQdACaikDADcDACACIAIpA8gCNwOgAyACQgA3AswDIAJBiI3AACgCADYCyAMgAkGYAmogAkHIA2pB6IXAABD6AiACQaADaiACQZgCahDKAQ0CIAJBxAFqIAJB0ANqKAIANgIAIAIgAikDyAM3ArwBIAJBATYCuAEgAkGgA2oQJiAPRSASRXJFBEAgDxCVAgsgEEUgE0VyDQAgEBCVAgsCQCABRQ0AIAIoAtwBRQ0AIAEQlQILIAIoAswBRQ0AIBEQlQILIAJByANqEOYBIAJBuAFqQQRyIQECQAJAAkAgAigCuAFBAUcEQCACKALQAyIAIAIoAswDRgRAIAJByANqIABBARANIAIoAtADIQALIAIoAsgDIABqQfsAOgAAIAIgAEEBajYC0AMgAkHAAmogAkHIA2pBnYXAAEECEOkBIAIoAsACQQFGDQEgAigC0AMiACACKALMA0YEQCACQcgDaiAAQQEQDSACKALQAyEACyACKALIAyAAakE6OgAAIAIgAEEBajYC0AMgAkGgA2ogARC4ASACQcACaiACQcgDaiACKAKgAyIAIAIoAqgDEOkBIAIoAqQDBEAgABCVAgsgAigCwAJBAUcEQCACKALQAyIBIAIoAswDRgRAIAJByANqIAFBARANIAIoAtADIQELIAIoAsgDIAFqQf0AOgAAIAIgAUEBajYC0AMMBAsgAkGkAmogAkHMAmooAgA2AgAgAiACKQLEAjcCnAIMAgsgAkGYAmogAkHIA2ogARAhIAIoApgCQQFHDQIMAQsgAkGkAmogAkHMAmooAgA2AgAgAiACKQLEAjcCnAILIAJBkAJqIgAgAkGkAmooAgA2AgAgAiACKQKcAjcDiAICQCACKALIAyIBRQ0AIAIoAswDRQ0AIAEQlQILIAJBoAJqIgEgACgCADYCACACIAIpA4gCNwOYAiACQcgC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IAE2AqQDAkAgAUF/akHAAEkEQCAEIAE6APQBIARBEGogBEHoAWoQgQEgAwRAIAIgA0EDdGohAwNAIARBEGogAigCACACQQRqKAIAEIMBIAMgAkEIaiICRw0ACwsgBEHoAWogBEEQahCEASAEQQhqIARB6AFqEIUBIAQoAgwiAiABRw0BIAAgBCgCCCABEJwDGiAEQbADaiQADwsgBEEkakEBNgIAIARCATcCFCAEQaCWwAA2AhAgBEEmNgKsAyAEIARBqANqNgIgIAQgBEGkA2o2AqgDIARBEGoQmgIACyABIAJB0JvAABCHAwAL2wUBDn8jAEGgAWsiBCQAAkACQAJAAkAgAUHBAE8EQCAEQUBrIglCADcDACAEQThqIgpCADcDACAEQTBqIgtCADcDAEEgIQUgBEEoakIANwMAIARBIGoiBkIANwMAIARBGGoiB0IANwMAIARBEGoiCEIANwMAIARCADcDCCAEQYABaiIMQgA3AwAgBEH4AGoiDUIANwMAIARB8ABqIg5CADcDACAEQegAakIANwMAIARB4ABqIg9CADcDACAEQdgAaiIQQgA3AwAgBEHQAGoiEUIANwMAIARCADcDSCAEQZQBaiADNgIAIAQgAjYCkAEgBEEENgKMASAEIARBnAFqNgKIASAEIAE2ApwBIARBCGpBwAAgBEGIAWpBAhB1IABBGGogBikDADcAACAAQRBqIAcpAwA3AAAgAEEIaiAIKQMANwAAIAAgBCkDCDcAACABQWBqIgNBwQBPBEBBICECA0AgDCAJKQMANwMAIA0gCikDADcDACAOIAspAwA3AwAgBEHoAGogBEEoaikDADcDACAPIAYpAwA3AwAgECAHKQMANwMAIBEgCCkDADcDACAEIAQpAwg3A0ggBEHAADYCjAEgBCAEQcgAajYCiAEgBEEIakHAACAEQYgBakEBEHUgAkEgaiIFIAJJDQQgBSABSw0GIAAgAmoiAiAEKQMINwAAIAJBGGogBikDADcAACACQRBqIAcpAwA3AAAgAkEIaiAIKQMANwAAIAUhAiADQWBqIgNBwABLDQALCyAFIAFLDQMgBEHAADYCjAEgBCAEQQhqNgKIASAAIAVqIAEgBWsgBEGIAWpBARB1DAELIARB1ABqIAM2AgAgBCACNgJQIARBBDYCTCAEIAE2AgggBCAEQQhqNgJIIAAgASAEQcgAakECEHULIARBoAFqJAAPC0HwlcAAQRxB3KPAABDTAgALIAUgAUH8o8AAENgCAAsgBSABQeyjwAAQ1QIAC44YAg5/FH4jAEGAEGsiBCQAIAQgAUGACBCcAyEEQQAhAQNAIAEgBGoiBSAFKQMAIAAgAWopAwCFNwMAIAFBCGoiAUGACEcNAAsgBEGACGogBEGACBCcAxogAwRAQQAhAQNAIARBgAhqIAFqIgAgACkDACABIAJqKQMAhTcDACABQQhqIgFBgAhHDQALC0EAIQEDQCABIARqIgBBGGoiAyAAQThqIgUpAwAiFSADKQMAIhJ8IBVC/////w+DIBJCAYZC/v///x+DfnwiEiAAQfgAaiIDKQMAhUIgiSIaIABB2ABqIgYpAwAiFnwgGkL/////D4MgFkIBhkL+////H4N+fCIWIBWFQiiJIhUgEnwgFUL/////D4MgEkIBhkL+////H4N+fCISIABBIGoiBykDACIYIAApAwAiE3wgGEL/////D4MgE0IBhkL+////H4N+fCITIABB4ABqIggpAwCFQiCJIhsgAEFAayIJKQMAIhl8IBtC/////w+DIBlCAYZC/v///x+DfnwiGSAYhUIoiSIYIBN8IBhC/////w+DIBNCAYZC/v///x+DfnwiEyAbhUIwiSIbIBl8IBtC/////w+DIBlCAYZC/v///x+DfnwiGSAYhUIBiSIYfCASQgGGQv7///8fgyAYQv////8Pg358IhwgAEEwaiIKKQMAIhQgAEEQaiILKQMAIhd8IBRC/////w+DIBdCAYZC/v///x+DfnwiFyAAQfAAaiIMKQMAhUIgiSIdIABB0ABqIg0pAwAiIXwgHUL/////D4MgIUIBhkL+////H4N+fCIhIBSFQiiJIhQgF3wgFEL/////D4MgF0IBhkL+////H4N+fCIXIB2FQjCJIh2FQiCJIiMgAEEoaiIOKQMAIiIgAEEIaiIPKQMAIh58ICJC/////w+DIB5CAYZC/v///x+DfnwiHiAAQegAaiIQKQMAhUIgiSIfIABByABqIhEpAwAiIHwgH0L/////D4MgIEIBhkL+////H4N+fCIgICKFQiiJIiIgHnwgIkL/////D4MgHkIBhkL+////H4N+fCIeIB+FQjCJIh8gIHwgH0L/////D4MgIEIBhkL+////H4N+fCIgfCAjQv////8PgyAgQgGGQv7///8fg358IiQgGIVCKIkiGCAcfCAYQv////8PgyAcQgGGQv7///8fg358IiU3AwAgCyAVIBIgGoVCMIkiFSAWfCAVQv////8PgyAWQgGGQv7///8fg358IhKFQgGJIhogF3wgGkL/////D4MgF0IBhkL+////H4N+fCIWIB+FQiCJIhwgGXwgHEL/////D4MgGUIBhkL+////H4N+fCIZIBqFQiiJIhogFnwgGkL/////D4MgFkIBhkL+////H4N+fCIfNwMAIA8gGyAdICF8IB1C/////w+DICFCAYZC/v///x+DfnwiFiAUhUIBiSIbIB58IBtC/////w+DIB5CAYZC/v///x+DfnwiFIVCIIkiFyASfCAXQv////8PgyASQgGGQv7///8fg358IhIgG4VCKIkiGyAUfCAbQv////8PgyAUQgGGQv7///8fg358Ih03AwAgACAVICAgIoVCAYkiFCATfCAUQv////8PgyATQgGGQv7///8fg358IhOFQiCJIhUgFnwgFkIBhkL+////H4MgFUL/////D4N+fCIWIBSFQiiJIhQgE3wgFEL/////D4MgE0IBhkL+////H4N+fCIhNwMAIAwgIyAlhUIwiSITNwMAIAMgFSAhhUIwiSIVNwMAIBAgHCAfhUIwiSIcNwMAIAggFyAdhUIwiSIXNwMAIBEgEyAkfCATQv////8PgyAkQgGGQv7///8fg358IhM3AwAgBiASIBd8IBdC/////w+DIBJCAYZC/v///x+DfnwiEjcDACANIBUgFnwgFUL/////D4MgFkIBhkL+////H4N+fCIVNwMAIAkgGSAcfCAcQv////8PgyAZQgGGQv7///8fg358IhY3AwAgByATIBiFQgGJNwMAIAUgFiAahUIBiTcDACAKIBIgG4VCAYk3AwAgDiAUIBWFQgGJNwMAIAFBgAFqIgFBgAhHDQALQQAhAQNAIAEgBGoiAEGIAWoiAyAAQYACaiIFKQMAIhUgACkDACISfCAVQv////8PgyASQgGGQv7///8fg358IhIgAEGABmoiBikDAIVCIIkiGiAAQYAEaiIHKQMAIhZ8IBpC/////w+DIBZCAYZC/v///x+DfnwiFiAVhUIoiSIVIBJ8IBVC/////w+DIBJCAYZC/v///x+DfnwiEiAahUIwiSIaIBZ8IBpC/////w+DIBZCAYZC/v///x+DfnwiFiAVhUIBiSIVIABBiANqIggpAwAiGCADKQMAIhN8IBhC/////w+DIBNCAYZC/v///x+DfnwiEyAAQYgHaiIDKQMAhUIgiSIbIABBiAVqIgkpAwAiGXwgG0L/////D4MgGUIBhkL+////H4N+fCIZIBiFQiiJIhggE3wgGEL/////D4MgE0IBhkL+////H4N+fCITfCAVQv////8PgyATQgGGQv7///8fg358IhwgAEGAA2oiCikDACIUIABBgAFqIgspAwAiF3wgFEL/////D4MgF0IBhkL+////H4N+fCIXIABBgAdqIgwpAwCFQiCJIh0gAEGABWoiDSkDACIhfCAdQv////8PgyAhQgGGQv7///8fg358IiEgFIVCKIkiFCAXfCAUQv////8PgyAXQgGGQv7///8fg358IhcgHYVCMIkiHYVCIIkiIyAAQYgCaiIOKQMAIiIgAEEIaiIPKQMAIh58ICJC/////w+DIB5CAYZC/v///x+DfnwiHiAAQYgGaiIQKQMAhUIgiSIfIABBiARqIhEpAwAiIHwgH0L/////D4MgIEIBhkL+////H4N+fCIgICKFQiiJIiIgHnwgIkL/////D4MgHkIBhkL+////H4N+fCIeIB+FQjCJIh8gIHwgH0L/////D4MgIEIBhkL+////H4N+fCIgfCAjQv////8PgyAgQgGGQv7///8fg358IiQgFYVCKIkiFSAcfCAVQv////8PgyAcQgGGQv7///8fg358IiU3AwAgCyAYIBMgG4VCMIkiGCAZfCAYQv////8PgyAZQgGGQv7///8fg358IhOFQgGJIhsgF3wgG0L/////D4MgF0IBhkL+////H4N+fCIZIB+FQiCJIhwgFnwgHEL/////D4MgFkIBhkL+////H4N+fCIWIBuFQiiJIhsgGXwgG0L/////D4MgGUIBhkL+////H4N+fCIfNwMAIA8gGiAdICF8IB1C/////w+DICFCAYZC/v///x+DfnwiGiAUhUIBiSIZIB58IBlC/////w+DIB5CAYZC/v///x+DfnwiFIVCIIkiFyATfCAXQv////8PgyATQgGGQv7///8fg358IhMgGYVCKIkiGSAUfCAZQv////8PgyAUQgGGQv7///8fg358Ih03AwAgACAYICAgIoVCAYkiGCASfCAYQv////8PgyASQgGGQv7///8fg358IhKFQiCJIhQgGnwgFEL/////D4MgGkIBhkL+////H4N+fCIaIBiFQiiJIhggEnwgGEL/////D4MgEkIBhkL+////H4N+fCIhNwMAIAwgIyAlhUIwiSISNwMAIAMgFCAhhUIwiSIUNwMAIBAgHCAfhUIwiSIcNwMAIAYgFyAdhUIwiSIXNwMAIBEgEiAkfCASQv////8PgyAkQgGGQv7///8fg358IhI3AwAgCSATIBd8IBdC/////w+DIBNCAYZC/v///x+DfnwiEzcDACANIBQgGnwgFEL/////D4MgGkIBhkL+////H4N+fCIaNwMAIAcgFiAcfCAcQv////8PgyAWQgGGQv7///8fg358IhY3AwAgBSASIBWFQgGJNwMAIAggFiAbhUIBiTcDACAKIBMgGYVCAYk3AwAgDiAYIBqFQgGJNwMAIAFBEGoiAUGAAUcNAAsgAiAEQYAIakGACBCcAyEAQQAhAQNAIAAgAWoiAiACKQMAIAEgBGopAwCFNwMAIAFBCGoiAUGACEcNAAsgBEGAEGokAAunAwEDfyMAQTBrIgQkAEH7qMAAIQJBEyEDAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAC0AAEEBaw4SAAECAwQFBgcICQoLDA0ODxAREgtB6ajAACECQRIhAwwRC0HUqMAAIQJBFSEDDBALQcCowAAhAkEUIQMMDwtBr6jAACECQREhAwwOC0GfqMAAIQJBECEDDA0LQYOowAAhAkEcIQMMDAtB6KfAACECQRshAwwLC0HVp8AAIQIMCgtBw6fAACECQRIhAwwJC0Gtp8AAIQJBFiEDDAgLQZenwAAhAkEWIQMMBwtB/6bAACECQRghAwwGC0HnpsAAIQJBGCEDDAULQdqmwAAhAkENIQMMBAtBzKbAACECQQ4hAwwDC0GtpsAAIQJBHyEDDAILQYumwAAhAkEiIQMMAQtB/KXAACECQQ8hAwsgBEEcakEBNgIAIAQgAzYCLCAEIAI2AiggBEEpNgIkIARCATcCDCAEQaylwAA2AgggBCAEQShqNgIgIAQgBEEgajYCGCABIARBCGoQ/AIgBEEwaiQAC2cBAX8jAEEgayICJAAgAkGOqsAANgIEIAIgADYCACACQRhqIAFBEGopAgA3AwAgAkEQaiABQQhqKQIANwMAIAIgASkCADcDCCACQYiswAAgAkEEakGIrMAAIAJBCGpBmKvAABDlAgALKwAjAEEQayIAJAAgACABQZiswABBCxD/AjcDCCAAQQhqEOsCIABBEGokAAswAQF/QRBBBBBVIgBFBEBBEEEEEMsCAAsgAEKBgICAEDcCACAAQoCAgIAQNwIIIAALhwUBBn8jAEEgayIBJAAgASAANgIAIAAtAAghAiAAQQE6AAggASACQQFxIgI6AAcCQAJAAkACQAJAAkAgAkUEQCAAQQhqIQICQAJAAkACQEGssMEAKAIAQf////8HcQRAEIkCIQYgAi0AAUUNASAGQQFzIQQMCgsgAi0AAQ0JIAJBAWohBSAAQQxqIQQgACgCDEEBRiEDDAELIAJBAWohBSAAQQxqIQQgACgCDEEBRiEDIAZFDQELQaywwQAoAgBB/////wdxRQ0AEIkCDQAgAkGAAjsAACADRQ0BDAULIAJBADoAACADDQQLIAAgACgCACIDQQFqNgIAIANBf0wNASABIAA2AgggAUEIahB9IAAgACgCACIDQX9qNgIAIANBAUYEQCABKAIIEGILIAItAAAhAyACQQE6AAAgASADQQFxIgM6AAcgAw0GQQAhA0GssMEAKAIAQf////8HcQRAEIkCQQFzIQMLIAUtAAANAiAEKAIARQRAAkAgAw0AQaywwQAoAgBB/////wdxRQ0AEIkCDQAgBUEBOgAACyAAQQA6AAggACAAKAIAIgJBf2o2AgAgAkEBRw0FIAAQYgwFC0GgicEAQRpB7InBABCWAgALDAULAAsgASADOgAMIAEgAjYCCEHMq8AAQSsgAUEIakH4q8AAQZytwAAQ6AIACyABEH0gACAAKAIAIgBBf2o2AgAgAEEBRw0AIAEoAgAQYgsgAUEgaiQADwsgASAEOgAMIAEgAjYCCEHMq8AAQSsgAUEIakH4q8AAQYytwAAQ6AIACyABQRxqQQA2AgAgAUEYakGkrsAANgIAIAFCATcCDCABQbCqwAA2AgggAUEHaiABQQhqEHkAC5kCAQV/IwBBIGsiASQAIAAoAgAiAC0ACCECIABBAToACCABIAJBAXEiAjoABwJAAkAgAkUEQCAAQQhqIQJBrLDBACgCAEH/////B3EEQBCJAkEBcyEDCyACLQABDQEgACgCDCIEQX9qIgUgBEsNAiAAIAU2AgwCQCADDQBBrLDBACgCAEH/////B3FFDQAQiQINACACQQE6AAELIAJBADoAACABQSBqJAAPCyABQRxqQQA2AgAgAUEYakGkrsAANgIAIAFCATcCDCABQbCqwAA2AgggAUEHaiABQQhqEHkACyABIAM6AAwgASACNgIIQcyrwABBKyABQQhqQfirwABBvK3AABDoAgALQeCtwABBIUHMrcAAENMCAAuzAgEFfyMAQSBrIgEkACAAKAIAIgAtAAghAiAAQQE6AAggASACQQFxIgI6AAcCQAJAIAJFBEAgAEEIaiECQaywwQAoAgBB/////wdxBEAQiQJBAXMhAwsgAi0AAQ0BIAAoAgwiBEEBaiIFIARPBEAgACAFNgIMIAAgACgCACIEQQFqNgIAIARBf0wNAwJAIAMNAEGssMEAKAIAQf////8HcUUNABCJAg0AIAJBAToAAQsgAEEAOgAIIAFBIGokACAADwtBsKvAAEEcQZSuwAAQ0wIACyABQRxqQQA2AgAgAUEYakGkrsAANgIAIAFCATcCDCABQbCqwAA2AgggAUEHaiABQQhqEHkACyABIAM6AAwgASACNgIIQcyrwABBKyABQQhqQfirwABBhK7AABDoAgALAAvmMAIGfy1+IwBBgAFrIgckAAJAAkBBACABQX9qIgggCCABSxsiCEGAf3EiCSAITQRAIAIpAzghMCACKQMwITEgAikDKCEyIAIpAyAhMyACKQMYITQgAikDECE1IAIpAwghNiACKQMAITcgB0EAQYABEJ4DIQsgACABIAkgCSABSxsiB2ohCAJAIAEgB2siCkH/AE0EQCALIAggChCcAyEIDAELQYABIQoLQgAgBkEBcyIGIAVBf3NxrX0hOEIAIAatfSE5QQAhBgJAA0AgOCEhIDkhLyAKIQUgCCEHIAYgCUYiDEUEQCAGQYABaiIFIAZJDQUgBSABSw0EIAAgBmohB0IAISFCACEvQYABIQULIDMgBykAICIgIDEgNXx8IiMgBykAKCIpfCAjIC+FQuv6htq/tfbBH4VCIIkiL0Kr8NP0r+68tzx8IiMgMYVCKIkiJHwiKiAvhUIwiSIPICN8IiwgJIVCAYkiJSAHKQBQIiMgBykAECIkIDIg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IgJqIgQgA0kNAyAEQYEBTw0BIAQgA2siBCACRw0CIAAgA2pB0ABqIAEoAgAiBCACEJwDGiAALQDQASACQf8BcWoiA0H/AXEgA0YNBEGwrsAAQRxB6LHAABDTAgALQbCvwABBIUG4scAAENMCAAsgBEGAAUHYscAAENUCAAsgBCACQZyvwAAQhwMAC0GwrsAAQRxByLHAABDTAgALIAAgAzoA0AEgASAFIAJrNgIEIAEgAiAEajYCAAvLAgIDfwJ+IwBBEGsiAyQAIAMgAjYCDCADIAE2AggCQCAALQDQAUUNACAAIANBCGoQggEgAygCDCICRQRAQQAhAgwBCyAAQdAAakGAASAAIAApA0AgAEHIAGoiASkDACAALQDRAUEBEH8gAEEAOgDQASAAIAApA0AiBkKAAXwiBzcDQCABIAEpAwAgByAGVK18NwMACwJAQQAgAkF/aiIBIAEgAksbIgRBgH9xIgEgBE0EQCABBEAgAiABSQ0CIAMoAggiBSABIAAgACkDQCAAQcgAaiIEKQMAIAAtANEBQQEQfyAAIAApA0AiBiABrXwiBzcDQCAEIAQpAwAgByAGVK18NwMAIAMgAiABazYCDCADIAEgBWo2AggLIAAgA0EIahCCASADQRBqJAAPC0Gwr8AAQSFB+LHAABDTAgALIAEgAkGIssAAENUCAAuoAgECfyMAQUBqIgIkACACQThqIAFBOGopAwA3AwAgAkEwaiABQTBqKQMANwMAIAJBKGogAUEoaikDADcDACACQSBqIAFBIGopAwA3AwAgAkEYaiABQRhqKQMANwMAIAJBEGogAUEQaikDADcDACACQQhqIAFBCGopAwA3AwAgAiABKQMANwMAIAEtANABIgNBgQFPBEAgA0GAAUGYssAAENUCAAsgAUHQAGogAyACIAEpA0AgAUHIAGopAwAgAS0A0QFBABB/IAAgAS0A0gE6AEAgACACKQM4NwA4IAAgAikDMDcAMCAAIAIpAyg3ACggACACKQMgNwAgIAAgAikDGDcAGCAAIAIpAxA3ABAgACACKQMINwAIIAAgAikDADcAACACQUBrJAALLgEBfyABLQBAIgJBwQBPBEAgAkHAAEGossAAENUCAAsgACACNgIEIAAgATYCAAstAQF/IwBBEGsiASQAIAFBCGogAEEIaigCADYCACABIAApAgA3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CyACQRxqQQA2AgAgAkGguMAANgIYIAJCATcCDCACQbDYwAA2AgggASACQQhqEPwCDAYLIAJBHGpBADYCACACQaC4wAA2AhggAkIBNwIMIAJBnNjAADYCCCABIAJBCGoQ/AIMBQsgAkEcakEANgIAIAJBoLjAADYCGCACQgE3AgwgAkHczsAANgIIIAEgAkEIahD8AgwECyACQRxqQQA2AgAgAkGguMAANgIYIAJCATcCDCACQcDOwAA2AgggASACQQhqEPwCDAMLIAJBHGpBADYCACACQaC4wAA2AhggAkIBNwIMIAJBhNjAADYCCCABIAJBCGoQ/AIMAgsgAkEcakEANgIAIAJBoLjAADYCGCACQgE3AgwgAkGgzsAANgIIIAEgAkEIahD8AgwBCyACQRxqQQE2AgAgAkIBNwIMIAJB4M3AADYCCCACQcAANgIkIAIgAEEEajYCLCACIAJBIGo2AhggAiACQSxqNgIgIAEgAkEIahD8AgsgAkEwaiQACwwAIAAoAgAgARCgAQuPAgEBfyMAQTBrIgIkAAJ/AkACQAJAAkAgACgCAEEBaw4DAQIDAAsgAkEcakEANgIAIAJBoLjAADYCGCACQgE3AgwgAkHczsAANgIIIAEgAkEIahD8AgwDCyACQRxqQQA2AgAgAkGguMAANgIYIAJCATcCDCACQcDOwAA2AgggASACQQhqEPwCDAILIAJBHGpBADYCACACQaC4wAA2AhggAkIBNwIMIAJBoM7AADYCCCABIAJBCGoQ/AIMAQsgAkEcakEBNgIAIAJCATcCDCACQeDNwAA2AgggAkHAADYCJCACIABBBGo2AiwgAiACQSBqNgIYIAIgAkEsajYCICABIAJBCGoQ/AILIAJBMGokAAtiAQF/IwBBMGsiAiQAIAAoAgAhACACQRxqQQE2AgAgAkICNwIMIAJBtNbAADYCCCACQT02AiQgAiAANgIsIAIgAkEgajYCGCACIAJBLGo2AiAgASACQQhqEPwCIAJBMGokAAsMACAAKAIAIAEQjQMLVwEBfyMAQSBrIgIkACACIAA2AgQgAkEYaiABQRBqKQIANwMAIAJBEGogAUEIaikCADcDACACIAEpAgA3AwggAkEEakG8tsAAIAJBCGoQ3QIgAkEgaiQACw8AIAAoAgAgARClARpBAAvWAgEDfyMAQRBrIgIkAAJAAn8CQCABQYABTwRAIAJBADYCDCABQYAQTw0BIAIgAUE/cUGAAXI6AA0gAiABQQZ2QcABcjoADEECDAILIAAoAggiAyAAQQRqKAIARgRAIAAgA0EBEA0gACgCCCEDCyAAIANBAWo2AgggACgCACADaiABOgAADAILIAFBgIAETwRAIAIgAUE/cUGAAXI6AA8gAiABQRJ2QfABcjoADCACIAFBBnZBP3FBgAFyOgAOIAIgAUEMdkE/cUGAAXI6AA1BBAwBCyACIAFBP3FBgAFyOgAOIAIgAUEMdkHgAXI6AAwgAiABQQZ2QT9xQYABcjoADUEDCyEBIABBBGooAgAgAEEIaiIEKAIAIgNrIAFJBEAgACADIAEQDSAEKAIAIQMLIAAoAgAgA2ogAkEMaiABEJwDGiAEIAEgA2o2AgALIAJBEGokAEEAC1oBAX8jAEEgayICJAAgAiAAKAIANgIEIAJBGGogAUEQaikCADcDACACQRBqIAFBCGopAgA3AwAgAiABKQIANwMIIAJBBGpBvLbAACACQQhqEN0CIAJBIGokAAvNAQEBfyMAQfAAayIDJAAgAyACNgIMIAMgATYCCCADQSRqQQE2AgAgA0ICNwIUIANBuLjAADYCECADQcEANgIsIAMgA0EoajYCICADIANBCGo2AiggA0IANwI0IANBoLjAACgCADYCMCADQUBrIANBMGpBiLTAABD6AiADQRBqIANBQGsQ5wIEQEGgtMAAQTcgA0HoAGpB9LbAAEGktcAAEOgCAAsgACADKQMwNwIEIABBFDYCACAAQQxqIANBOGooAgA2AgAgA0HwAGokAAvNAQEBfyMAQfAAayIDJAAgAyACNgIMIAMgATYCCCADQSRqQQE2AgAgA0ICNwIUIANB3LjAADYCECADQcEANgIsIAMgA0EoajYCICADIANBCGo2AiggA0IANwI0IANBoLjAACgCADYCMCADQUBrIANBMGpBiLTAABD6AiADQRBqIANBQGsQ5wIEQEGgtMAAQTcgA0HoAGpB9LbAAEGktcAAEOgCAAsgACADKQMwNwIEIABBFDYCACAAQQxqIANBOGooAgA2AgAgA0HwAGokAAvyAQEBfyMAQYABayIFJAAgBSACNgIMIAUgATYCCCAFQSRqQQI2AgAgBUE0akEHNgIAIAVCAjcCFCAFQYy5wAA2AhAgBUHBADYCLCAFIAQ2AjwgBSADNgI4IAUgBUEoajYCICAFIAVBOGo2AjAgBSAFQQhqNgIoIAVCADcCRCAFQaC4wAAoAgA2AkAgBUHQAGogBUFAa0GItMAAEPoCIAVBEGogBUHQAGoQ5wIEQEGgtMAAQTcgBUH4AGpB9LbAAEGktcAAEOgCAAsgACAFKQNANwIEIABBFDYCACAAQQxqIAVByABqKAIANgIAIAVBgAFqJAALiAMBBX8jAEEgayICJAAgAkEIaiABKAIAIgMQ3gECQAJAAkACQAJAAkAgAigCCCIBQRVGBEAgAiADEOABQQEhASACLQAAQQFxRQ0BIAItAAFBIkcNAiADENkBIAJBCGogAxDfASACQRhqKAIAIQUgAkEUaigCACEDIAJBEGooAgAhBCACKAIMIQYgAigCCEEBRg0DIAZFBEACQAJAIANBAE4EQCADDQEMAgsQzAIACyADQQEQVSIBRQ0GCyABIAQgAxCcAyEBIABBDGogAzYCACAAQQhqIAM2AgAgACABNgIEQQAhAQwHCyAAIAQ2AgQgAEEMaiAFNgIAIABBCGogAzYCAEEAIQEMBgsgAEEIaiACKQIMNwIAIABBEGogAkEUaigCADYCACAAIAE2AgQMBAsgAEEENgIEDAQLIABBDjYCBAwCCyAAIAY2AgQgAEEQaiAFNgIAIABBDGogAzYCACAAQQhqIAQ2AgAMAgsgA0EBEMsCAAtBASEBCyAAIAE2AgAgAkEgaiQAC4AEAQR/IwBB0ABrIgIkACACQRBqIAEoAgAiAxDgAQJAAkAgAi0AEEEBcUUEQEECIQEMAQsCQAJAAkACQAJAIAItABEiBCIFQSxHBEAgBUH9AEYNAyABLQAEDQFBCSEBDAYLIAEtAAQNACADENkBIAJBCGogAxDgASACLQAIQQFxRQ0EIAItAAkhBAwBCyABQQA6AAQLIARB/wFxIgFB/QBHBEAgAUEiRwRAQRAhAQwFCyACIAMQ4AEgAi0AAEEBcUUNAyACLQABQSJHBEBBDiEBDAULIAMQ2QEgAkEoaiADEN8BIAJBNGooAgAhBCACQTBqKAIAIQUgAigCLCEBIAIoAihBAUcEQCAERSABRSAFRXJyDQMgBRCVAgwDCyABQRVGDQIgAkE4aigCACEDDAQLQRMhAQwDCyAAQQA7AQAMAwsgAkEoaiADEN4BAkAgAigCKCIBQRVHBEAgAkEkaiACQTRqKAIANgIAIAIgAikCLDcCHCACIAE2AhgMAQsgAkEYaiADEKwBIAIoAhhBFUcNACAAQYACOwEADAMLIABBAToAACAAQQRqIAIpAxg3AgAgAEEMaiACQSBqKQMANwIADAILQQQhAQsgAEEBOgAAIABBEGogAzYCACAAQQxqIAQ2AgAgAEEIaiAFNgIAIABBBGogATYCAAsgAkHQAGokAAuICQIDfwF+IwBBgAFrIgIkACACQThqIAEQ4AECQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAi0AOEEBcQRAAkACQCACLQA5IgNBpX9qDiMEAQYBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQUBBgALIANBXmoOCwIAAAAAAAAAAAAFAAsgAkEIaiABEOEBIAItAAhBAXEEQCACLQAJIQMDQCADQSxGIANB3QBGciADQf0ARnINByABENkBIAIgARDhASACLQABIQMgAi0AAEEBcQ0ACwsgAEEDNgIADA8LIABBBDYCAAwOCyACQRBqIAEQ4AEgAi0AEEEBcUUNBCACLQARQSJHDQUgARDZASACQegAaiABEN8BIAJB9ABqKAIAIQMgAkHwAGooAgAhASACKAJsIQQgAigCaEEBRg0GIARFBEAgAEEVNgIADA4LIABBFTYCACABRSADRXINDSABEJUCDA0LIAJBIGogARDgASACLQAgQQFxRQ0GIAItACFB2wBHDQcgARDZASACQRhqIAEQ1wEgAigCGCEDIAIgAi0AHEEBcToAZCACIAM2AmAgAkHoAGogAkHgAGoQrgECQCACLQBoQQFHBEADQCACLQBpRQ0CIAJB6ABqIAJB4ABqEK4BIAItAGhBAUcNAAsLIAJB2ABqIgMgAkH4AGooAgA2AgAgAiACQfAAaikDADcDUCACKAJsIgRBFUcNCQsgAkHoAGogARDbASACKAJoIgFBFUYEQCAAQRU2AgAMDQsgAkHYAGogAkH0AGooAgAiAzYCACACIAIpAmwiBTcDUCAAQQxqIAM2AgAgACAFNwIEIAAgATYCAAwMCyACQTBqIAEQ4AEgAi0AMEEBcUUNCCACLQAxQfsARw0JIAEQ2QEgAkEoaiABENcBIAIoAighAyACIAItACxBAXE6AGQgAiADNgJgIAJB6ABqIAJB4ABqEKsBAkAgAi0AaEEBRwRAA0AgAi0AaUUNAiACQegAaiACQeAAahCrASACLQBoQQFHDQALCyACQdgAaiIDIAJB+ABqKAIANgIAIAIgAkHwAGopAwA3A1AgAigCbCIEQRVHDQsLIAJB6ABqIAEQ3AEgAigCaCIBQRVGBEAgAEEVNgIADAwLIAJB2ABqIAJB9ABqKAIAIgM2AgAgAiACKQJsIgU3A1AgAEEMaiADNgIAIAAgBTcCBCAAIAE2AgAMCwsgAEELNgIADAoLIABBFTYCAAwJCyAAQQQ2AgAMCAsgAEEONgIADAcLIABBDGogAkH4AGooAgA2AgAgAEEIaiADNgIAIAAgATYCBCAAIAQ2AgAMBgsgAEEENgIADAULIABBDjYCAAwECyACQcgAaiADKAIAIgE2AgAgAiACKQNQIgU3A0AgAEEMaiABNgIAIAAgBTcCBCAAIAQ2AgAMAwsgAEEENgIADAILIABBDjYCAAwBCyACQcgAaiADKAIAIgE2AgAgAiACKQNQIgU3A0AgAEEMaiABNgIAIAAgBTcCBCAAIAQ2AgALIAJBgAFqJAALHwAgACgCBCAAQQxBCCAAKAIAQQFGG2ooAgAgARCFAwvDAgIEfwF+IwBBMGsiAiQAIAJBCGogASgCACIDEOABAkACQCACLQAIQQFxBEAgAi0ACSIEIgVBLEcEQAJAIAVB3QBHBEAgAS0ABA0BIABBAToAACAAQQRqQQc2AgAMBQsgAEEAOwEADAQLIAFBADoABAwCCyADENkBIAIgAxDgASACLQAAQQFxBEAgAi0AASEEDAILIABBAToAACAAQQRqQQQ2AgAMAgsgAEEBOgAAIABBBGpBATYCAAwBCyAEQd0ARgRAIABBAToAACAAQQRqQRM2AgAMAQsgAkEgaiADEKwBIAIoAiAiAUEVRgRAIABBgAI7AQAMAQsgAkEYaiACQSxqKAIAIgM2AgAgAiACKQIkIgY3AxAgAEEQaiADNgIAIABBCGogBjcCACAAQQRqIAE2AgAgAEEBOgAACyACQTBqJAAL5QEBBH8jAEEgayICJAAgAiABEOABAkACQAJAIAItAABBAXEEQCACLQABQSJHDQEgARDZASACQQhqIAEQ3wEgAkEYaigCACEEIAJBFGooAgAhAyACQRBqKAIAIQEgAigCDCEFIAIoAghBAUYNAiAFRQRAIAAgASADELABDAQLIAAgASAEELABIANFDQMgARCVAgwDCyAAQoGAgIDAADcCAAwCCyAAQoGAgIDgATcCAAwBCyAAIAU2AgQgAEEBNgIAIABBEGogBDYCACAAQQxqIAM2AgAgAEEIaiABNgIACyACQSBqJAAL/gEBAX8jAEHgAGsiAyQAIAMgAjYCBCADIAE2AgAgA0EIaiABIAIQtwECQCADKAIIQQFHBEAgACADKQIMNwIEIABBADYCACAAQQxqIANBFGooAgA2AgAMAQsgA0HUAGpBATYCACADQgE3AkQgA0G8wsAANgJAIANBwQA2AlwgAyADQdgAajYCUCADIAM2AlggA0EwaiADQUBrEM0CIANBQGtBBHIgA0EwahDPAiADQRQ2AkACQCADKAIwIgFFDQAgAygCNEUNACABEJUCCyAAIAMpA0A3AgQgAEEBNgIAIABBDGogA0HIAGopAwA3AgAgA0EQahAmCyADQeAAaiQAC/IBAQR/IwBBIGsiAiQAIAIgARDgAQJAAkACQCACLQAAQQFxBEAgAi0AAUEiRw0BIAEQ2QEgAkEIaiABEN8BIAJBGGooAgAhBCACQRRqKAIAIQMgAkEQaigCACEBIAIoAgwhBSACKAIIQQFGDQIgBUUEQCAAIAEgAxCyAQwECyAAIAEgBBCyASADRQ0DIAEQlQIMAwsgAEEBOgAAIABBBGpBBDYCAAwCCyAAQQE6AAAgAEEEakEONgIADAELIABBAToAACAAQRBqIAQ2AgAgAEEMaiADNgIAIABBCGogATYCACAAQQRqIAU2AgALIAJBIGokAAvLAQACQAJAAkACQAJAAkACQCACQXlqDg0BBAQEBAQEBAIABAQDBAsgAUHr1sAAQRAQnwMEQCABQfvWwABBEBCfAw0EIABBAjoAAQwGCyAAQQE6AAEMBQsgAUGL18AAQQcQnwMNAiAAQQM6AAEMBAsgAUHc1sAAQQ8QnwNFDQIMAQsgAUGS18AAQRMQnwMNACAAQQQ6AAEMAgsgAEEEaiABIAJBqNfAAEEFEKkBIABBAToAAA8LIABBADoAASAAQQA6AAAPCyAAQQA6AAALqAMBBH8jAEEgayICJAAgAiABEOABAkACQAJAIAACfwJAAkACQAJAIAItAABBAXEEQCACLQABQSJHDQEgARDZASACQQhqIAEQ3wEgAkEYaigCACEEIAJBFGooAgAhAyACQRBqKAIAIQEgAigCDCEFIAIoAghBAUYNAiAFRQRAAkACQCADQX5qDgQACQkBCQsgAS8AAEHv1gFGDQkMCAsgAUHU18AAQQUQnwMNByAAQYACOwEADAkLAkACQCAEQX5qDgQABQUBBQsgAS8AAEHv1gFGDQUMBAsgAUHU18AAQQUQnwMNAyAAQQE6AAFBAAwFCyAAQQE6AAAgAEEEakEENgIADAcLIABBAToAACAAQQRqQQ42AgAMBgsgAEEBOgAAIABBEGogBDYCACAAQQxqIAM2AgAgAEEIaiABNgIAIABBBGogBTYCAAwFCyAAQQRqIAEgBEHY2MAAQQIQqQFBAQwBCyAAQQA6AAFBAAs6AAAgA0UNAiABEJUCDAILIABBBGogASADQdjYwABBAhCpASAAQQE6AAAMAQsgAEEAOwEACyACQSBqJAALHQAgASgCAEUEQAALIABBlMLAADYCBCAAIAE2AgALVQECfyABKAIAIQIgAUEANgIAAkAgAgRAIAEoAgQhA0EIQQQQVSIBRQ0BIAEgAzYCBCABIAI2AgAgAEGUwsAANgIEIAAgATYCAA8LAAtBCEEEEMsCAAscACAAIAEpAgA3AgAgAEEIaiABQQhqKAIANgIAC+AnAhJ/Cn4jAEHwAGsiCSQAAkACQAJAIAIgAkH/////A3FGBEAgAkECdCIEQQNuIQMCQAJAAkACQAJAAkACQCAERQRAQQEhDgwBCyADQQEQVSIORQ0BCyAJQQA2AjggCSADNgI0IAkgDjYCMCACEPMBIgytQgZ+IhVCIIinDQEgFaciBARAIAMgBE8Ef0EABSAJQTBqQQAgBBANIAkoAjAhDiAJKAI4CyIDIA5qIQUgBEECTwR/IAVBACAEQX9qIgQQngMaIA4gAyAEaiIDagUgBQtBADoAACADQQFqIQ0LIAkgDTYCOEEAIQNBhPrAACgCACEHAkACQAJAAkACQAJAIAJBB3EiCA4GAAECAwQBBQtBCCEIDAQLQgEhFiACRQ0MIAEgAkF/aiICai0AACIBQT1GDQwgASAHai0AAEH/AUcNDCABrUIIhiACrUIghoQhFgwMC0EKIQgMAgtBCyEIDAELQQwhCAtBACACIAhrIgQgBCACSxsiCEFgaiIPIAhLBEAgDCEEDAgLA0AgCkEgaiIDIApJDQQgAyACSw0DIAtBGmoiBCALSQ0GIAQgDUsNBQJAAkAgByABIApqIgQtAAAiBWoxAAAiFUL/AVENACAHIARBAWotAAAiBWoxAAAiFkL/AVEEQCAKQQFqIQoMAQsgByAEQQJqLQAAIgVqMQAAIhdC/wFRBEAgCkECaiEKDAELIAcgBEEDai0AACIFajEAACIYQv8BUQRAIApBA2ohCgwBCyAHIARBBGotAAAiBWoxAAAiGUL/AVEEQCAKQQRqIQoMAQsgByAEQQVqLQAAIgVqMQAAIhpC/wFRBEAgCkEFaiEKDAELIAcgBEEGai0AACIFajEAACIbQv8BUQRAIApBBmohCgwBCyAHIARBB2otAAAiBWoxAAAiHEL/AVINASAKQQdqIQoLIAqtQiCGIAWtQgiGhCEWDAoLIAsgDmoiECAWQjSGIBVCOoaEIBdCLoaEIBhCKIaEIBlCIoaEIBpCHIaEIBtCFoaEIBxCEIaEIhVCOIYgFUIohkKAgICAgIDA/wCDhCAVQhiGQoCAgICA4D+DIBVCCIZCgICAgPAfg4SEIBVCCIhCgICA+A+DIBVCGIhCgID8B4OEIBVCKIhCgP4DgyAVQjiIhISENwAAQQghBQJAAkAgByAEQQhqLQAAIgZqMQAAIhVC/wFRDQBBCSEFIAcgBEEJai0AACIGajEAACIWQv8BUQ0AQQohBSAHIARBCmotAAAiBmoxAAAiF0L/AVENAEELIQUgByAEQQtqLQAAIgZqMQAAIhhC/wFRDQBBDCEFIAcgBEEMai0AACIGajEAACIZQv8BUQ0AQQ0hBSAHIARBDWotAAAiBmoxAAAiGkL/AVENAEEOIQUgByAEQQ5qLQAAIgZqMQAAIhtC/wFRDQBBDyEFIAcgBEEPai0AACIGajEAACIcQv8BUg0BCyAFIApqrUIghiAGrUIIhoQhFgwKCyAQQQZqIBZCNIYgFUI6hoQgF0IuhoQgGEIohoQgGUIihoQgGkIchoQgG0IWhoQgHEIQhoQiFUI4hiAVQiiGQoCAgICAgMD/AIOEIBVCGIZCgICAgIDgP4MgFUIIhkKAgICA8B+DhIQgFUIIiEKAgID4D4MgFUIYiEKAgPwHg4QgFUIoiEKA/gODIBVCOIiEhIQ3AABBECEFAkACQCAHIARBEGotAAAiBmoxAAAiFUL/AVENAEERIQUgByAEQRFqLQAAIgZqMQAAIhZC/wFRDQBBEiEFIAcgBEESai0AACIGajEAACIXQv8BUQ0AQRMhBSAHIARBE2otAAAiBmoxAAAiGEL/AVENAEEUIQUgByAEQRRqLQAAIgZqMQAAIhlC/wFRDQBBFSEFIAcgBEEVai0AACIGajEAACIaQv8BUQ0AQRYhBSAHIARBFmotAAAiBmoxAAAiG0L/AVENAEEXIQUgByAEQRdqLQAAIgZqMQAAIhxC/wFSDQELIAUgCmqtQiCGIAatQgiGhCEWDAoLIBBBDGogFkI0hiAVQjqGhCAXQi6GhCAYQiiGhCAZQiKGhCAaQhyGhCAbQhaGhCAcQhCGhCIVQjiGIBVCKIZCgICAgICAwP8Ag4QgFUIYhkKAgICAgOA/gyAVQgiGQoCAgIDwH4OEhCAVQgiIQoCAgPgPgyAVQhiIQoCA/AeDhCAVQiiIQoD+A4MgFUI4iISEhDcAAEEYIQYCQAJAIAcgBEEYai0AACIFajEAACIVQv8BUQ0AQRkhBiAHIARBGWotAAAiBWoxAAAiFkL/AVENAEEaIQYgByAEQRpqLQAAIgVqMQAAIhdC/wFRDQBBGyEGIAcgBEEbai0AACIFajEAACIYQv8BUQ0AQRwhBiAHIARBHGotAAAiBWoxAAAiGUL/AVENAEEdIQYgByAEQR1qLQAAIgVqMQAAIhpC/wFRDQBBHiEGIAcgBEEeai0AACIFajEAACIbQv8BUQ0AQR8hBiAHIARBH2otAAAiBWoxAAAiHEL/AVINAQsgBiAKaq1CIIYgBa1CCIaEIRYMCgsgEEESaiAWQjSGIBVCOoaEIBdCLoaEIBhCKIaEIBlCIoaEIBpCHIaEIBtCFoaEIBxCEIaEIhVCOIYgFUIohkKAgICAgIDA/wCDhCAVQhiGQoCAgICA4D+DIBVCCIZCgICAgPAfg4SEIBVCCIhCgICA+A+DIBVCGIhCgID8B4OEIBVCKIhCgP4DgyAVQjiIhISENwAAIAxBfGoiBCAMTQRAIAtBGGohCyAEIQwgAyIKIA9LDQkMAQsLQaC8wABBIUGEvcAAENMCAAsgA0EBEMsCAAtBoMDAAEEuQdDAwAAQ4wIACyADIAJB1LzAABDVAgALQbCzwABBHEHEvMAAENMCAAsgBCANQfS8wAAQ1QIAC0Gws8AAQRxB5LzAABDTAgALQdCzwABBIUGEvMAAENMCAAsCQCAIQXhqIgogCEsgAyAKT3JFBEACQAJAAkACQAJAAkADQCADQQhqIgggA0kNAiAIIAJLDQEgC0EGaiIFIAtJDQMgBUECaiIGIAVJDQYgBiALSQ0EIAYgDUsNBQJAAkAgByABIANqIgwtAAAiBmoxAAAiFUL/AVENACAHIAxBAWotAAAiBmoxAAAiFkL/AVEEQCADQQFqIQMMAQsgByAMQQJqLQAAIgZqMQAAIhdC/wFRBEAgA0ECaiEDDAELIAcgDEEDai0AACIGajEAACIYQv8BUQRAIANBA2ohAwwBCyAHIAxBBGotAAAiBmoxAAAiGUL/AVEEQCADQQRqIQMMAQsgByAMQQVqLQAAIgZqMQAAIhpC/wFRBEAgA0EFaiEDDAELIAcgDEEGai0AACIGajEAACIbQv8BUQRAIANBBmohAwwBCyAHIAxBB2otAAAiBmoxAAAiHEL/AVINASADQQdqIQMLIAOtQiCGIAatQgiGhCEWDAoLIAsgDmogFkI0hiAVQjqGhCAXQi6GhCAYQiiGhCAZQiKGhCAaQhyGhCAbQhaGhCAcQhCGhCIVQjiGIBVCKIZCgICAgICAwP8Ag4QgFUIYhkKAgICAgOA/gyAVQgiGQoCAgIDwH4OEhCAVQgiIQoCAgPgPgyAVQhiIQoCA/AeDhCAVQiiIQoD+A4MgFUI4iISEhDcAACAEQX9qIgYgBE0EQCAGIQQgCCEDIAUhCyAIIApPDQkMAQsLQaC8wABBIUHkvcAAENMCAAsgCCACQaS9wAAQ1QIAC0Gws8AAQRxBlL3AABDTAgALQbCzwABBHEG0vcAAENMCAAsgCyAGQdS9wAAQ2AIACyAGIA1B1L3AABDVAgALQbCzwABBHEHEvcAAENMCAAsgBCEGIAMhCCALIQULIAZBASAGQQFLGyEEQQAgCGshCiACrSEXIAitIRUCQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAA0AgBEF/aiIERQRAAkACQAJAAkAgCCACTQRAAkAgAiAIRgRAQQAhAUIAIRdBACEDQgAhGAwBCyABIAJqIRIgASAIaiEKQgAhF0EAIQNBACEGQQAhBEEAIQsDQCASIAprIRNBACECAkACQAJAAkACQANAIAIgC2oiD0EBaiIQIA9JDQkgAiAEaiEMIAIgCmoiFC0AACIBQT1HBEAgDEEATA0FIAYgCGoiASAISQ0EIAGtQiCGQoD6AIQhFgwqCyAPQQJxRQ0BIAxBAWogDEgNAiAGIA8gDBshBiATIAJBAWoiAkcNAAsgAyEBDAQLIAggBiAPIAIgBGpBAEobaiIBIAhPBEAgAa1CIIZCgPoAhCEWDCgLQbCzwABBHEHEvsAAENMCAAtBsLPAAEEcQdS+wAAQ0wIAC0Gws8AAQRxB5L7AABDTAgALIBFBCkYNBSABIAdqMQAAIhVC/wFRBEAgCCAPaiICIAhPBEAgAa1CCIYgAq1CIIaEIRYMJgtBsLPAAEEcQYS/wAAQ0wIACyAKQX9zIBRBAWohCiAVIBFBAWoiEUF6bEE+ca2GIBeEIRcgASEDIAwhBCAQIQsgEmogAkcNAQsLQgAhGEEAIQMCfgJAAkACQAJAAkACQAJAIBEOCQgAAQIDAAQFBgALIAlB1ABqQQE2AgAgCUIBNwJEIAlBmLjAADYCQCAJQcEANgIsIAlB6L/AADYCKCAJIAlBKGo2AlAgCUFAa0Hwv8AAENwCAAtCCAwFC0IQDAQLQhgMAwtCIAwCC0IoDAELQjALIRhBASEDCyAXIBiGUARAIAMEQCANIAUgDSAFSxshASAFIA0gBSANSxshAkIAIRVCOCEWA0AgASAFRg0HIAUgDmogFyAWiDwAACAWQnh8IRYgBUEBaiEFIBVCCHwiFSAYVA0ACwtCACEVQQMhAyANIAVJDSMgCSAFNgI4DCMLIAggEWoiAiAISQ0DIAJBf2oiAyACTQRAIAGtQv8Bg0IIhiADrUIghoRCAoQhFgwiC0GgvMAAQSFBgMDAABDTAgALIAggAkG0vsAAENkCAAtBsLPAAEEcQZSzwAAQ0wIAC0GgvMAAQSFB9L7AABDTAgALQbCzwABBHEGAwMAAENMCAAsgAiANQZDAwAAQ1AIACyAVIBdWDRAgBUEGaiIDIAVJDQ8gAyANSw0RIAIgCEYNEiAHIAEgCGoiCy0AACIDajEAACIWQv8BUQ0OIAIgCmoiBkECSQ0TIAcgC0EBai0AACIDajEAACIYQv8BUQ0BIAZBAk0NAiAHIAtBAmotAAAiA2oxAAAiGUL/AVENAyAGQQNNDQQgByALQQNqLQAAIgNqMQAAIhpC/wFRDQUgBkEETQ0GIAcgC0EEai0AACIDajEAACIbQv8BUQ0HIAZBBU0NCCAHIAtBBWotAAAiA2oxAAAiHEL/AVENCSAGQQZNDQogByALQQZqLQAAIgNqMQAAIh1C/wFRDQsgBkEHTQ0MIAcgC0EHai0AACIDajEAACIeQv8BUQ0NIApBeGohCiAVQgh8IRUgBSAOaiIDQQRqIBhCNIYgFkI6hoQgGUIuhoQgGkIohoQgG0IihoQgHEIchoQgHUIWhoQgHkIQhoQiFkIYhkKAgICAgOA/gyAWQgiGQoCAgIDwH4OEQiCIPQAAIAMgFkIIiEKAgID4D4MgFkIYiEKAgPwHg4QgFkIoiEKA/gODIBZCOIiEhD4AACAIQQhqIAhJIAhBCGohCCAFQQZqIQVFDQALQbCzwABBHEGkvsAAENMCAAsgCEEBaiIBIAhJDRIgAa0hFQwMC0ECQQJBxLrAABDUAgALIAhBAmoiASAISQ0RIAGtIRUMCgtBA0EDQeS6wAAQ1AIACyAIQQNqIgEgCEkNECABrSEVDAgLQQRBBEGEu8AAENQCAAsgCEEEaiIBIAhJDQ8gAa0hFQwGC0EFQQVBpLvAABDUAgALIAhBBWoiASAISQ0OIAGtIRUMBAtBBkEGQcS7wAAQ1AIACyAIQQZqIgEgCEkNDSABrSEVDAILQQdBB0Hku8AAENQCAAsgCEEHaiIBIAhJDQwgAa0hFQsgFUIghiADrUIIhoQhFgwMC0Gws8AAQRxBhL7AABDTAgALIAggAkH0vcAAENkCAAsgAyANQZS+wAAQ1QIAC0EAQQBBlLrAABDUAgALQQFBAUGkusAAENQCAAtBsLPAAEEcQbS6wAAQ0wIAC0Gws8AAQRxB1LrAABDTAgALQbCzwABBHEH0usAAENMCAAtBsLPAAEEcQZS7wAAQ0wIAC0Gws8AAQRxBtLvAABDTAgALQbCzwABBHEHUu8AAENMCAAtBsLPAAEEcQfS7wAAQ0wIACyAWQgiIIRUgFqchAwsgCSgCNCECIAkoAjAhASAAAn8CQCADQf8BcUEDRwRAIAkgAzoAICAJIBU+ACEgCSAVQjCIPAAnIAkgFUIgiD0AJSABRSACRXJFBEAgARCVAgsgCSgCICEBIAkgFUIYiD4CLCAJIAE2AiggCUIANwI0IAlBoLjAACgCADYCMCAJQUBrIAlBMGpBiLTAABD6AiAJQShqIAlBQGsQ8gFFDQFBoLTAAEE3IAlB6ABqQfS2wABBpLXAABDoAgALIAkoAjghAyAAQQhqIAI2AgAgACABNgIEQQAMAQsgCUEIaiAJQRhqKQMAIhU3AwAgCSAJKQMQIhY3AwAgCSgCMCEDIAkpAjQhFyAAQSBqIBU3AgAgAEEYaiAWNwIAIABBEGogFzcDACAAQQhqQQM2AgBBAQs2AgAgAEEMaiADNgIAIAlB8ABqJAAL6QEBBH8jAEEgayICJAAgASgCACEEIAIgASgCCCIFQYACEPEBAkACQAJAIAIoAgAEQCACKAIEIgFBAEgNAUEBIQMgAQRAIAEQpQIiA0UNAwsgBCAFQYACIAMgARDwASACQQhqIAMgARCIAyACKAIIQQFGDQMgACABNgIIIAAgATYCBCAAIAM2AgAgAkEgaiQADwtB1MHAAEEtQYTCwAAQiAEACxDMAgALIAFBARDLAgALIAIgAikCDDcCFCACIAE2AhAgAiABNgIMIAIgAzYCCEG1wcAAQQwgAkEIakHktsAAQcTBwAAQ6AIACxYAIAAgASgCCDYCBCAAIAEoAgA2AgALvgQBAX8jAEFAaiICJAACQAJAAkACQAJAAkAgACgCAEEBaw4EAQIDBAALIAIgAEEEajYCBCACQRhqIABBEGooAgAgAEEYaigCABDOAiACQRRqQcIANgIAIAJBPGpBAjYCACACQT02AgwgAkICNwIsIAJB4MPAADYCKCACIAJBGGo2AhAgAiACQQRqNgIIIAIgAkEIajYCOCABIAJBKGoQ/AIhACACKAIYRQ0EIAIoAhwiAUUNBCACQSBqKAIARQ0EIAEQlQIMBAsgAiAAQQRqNgIEIAJBGGogAEEQaigCACAAQRhqKAIAEM4CIAJBFGpBwgA2AgAgAkE8akECNgIAIAJBPTYCDCACQgI3AiwgAkG4w8AANgIoIAIgAkEYajYCECACIAJBBGo2AgggAiACQQhqNgI4IAEgAkEoahD8AiEAIAIoAhhFDQMgAigCHCIBRQ0DIAJBIGooAgBFDQMgARCVAgwDCyACIABBBGo2AgggAkE8akEBNgIAIAJCATcCLCACQZTDwAA2AiggAkE9NgIcIAIgAkEYajYCOCACIAJBCGo2AhggASACQShqEPwCIQAMAgsgAkE8akEANgIAIAJBoLjAADYCOCACQgE3AiwgAkH4wsAANgIoIAEgAkEoahD8AiEADAELIAIgAEEEajYCCCACQTxqQQE2AgAgAkIBNwIsIAJB3MLAADYCKCACQT02AhwgAiACQRhqNgI4IAIgAkEIajYCGCABIAJBKGoQ/AIhAAsgAkFAayQAIAALvQMBCH8CQAJAAkACQAJAAkAgAkEDdCIHRQRADAELIAFBBGohBCAHIQUDQCADIAQoAgBqIgYgA0kNAiAEQQhqIQQgBiEDIAVBeGoiBQ0ACwsgAkH/////A3EgAkcNASAGIAJBAnRqIgUgBkkNAwJAAkAgBUEATgRAIAUNAUEBIQMMAgsQzAIACyAFQQEQVSIDRQ0DCyAAIAM2AgBBACEEIABBCGoiBkEANgIAIABBBGoiCCAFNgIAIAJFDQQgASAHaiEHA0AgAUEEaigCACICQQh2QYD+A3EgAkEYdnIhCSABKAIAIQogBSAEayACSQRAIAAgBCACEA0gBigCACEEIAAoAgAhAwsgAyAEaiAKIAIQnAMaIAYgAiAEaiIDNgIAIAgoAgAgA2tBA00EQCAAIANBBBANIAYoAgAhAwsgBiADQQRqIgQ2AgAgAyAAKAIAIgNqIAJBCHRBgID8B3EgAkEYdHIgCXI2AAAgByABQQhqIgFGDQUgCCgCACEFDAALAAtBsLPAAEEcQdy3wAAQ0wIAC0HQs8AAQSFBlMTAABDTAgALIAVBARDLAgALQbCzwABBHEGkxMAAENMCAAsLZAECfwJAAkACQAJAIABBAE4EQCAADQFBASECDAILEMwCAAsgAEEBEFUiAkUNAQtBDEEEEFUiAUUNASABQQA2AgggASAANgIEIAEgAjYCACABDwsgAEEBEMsCAAtBDEEEEMsCAAtJAQJ/AkAgAARAIAAoAgAiAUUNASAAKAIEIAAQlQIEQCABEJUCCw8LQdDMwABBFkHozMAAEIgBAAtB+MzAAEEdQZjNwAAQiAEAC38BAX4CQEEMQQQQVSIBBEAgASADNgIIIAEgAzYCBCABIAI2AgACQCABEAAiAkUEQCAAQQA2AgAMAQsgAigCACIDRQ0CIAIpAgQhBCACEJUCIAAgBDcCBCAAIAM2AgALIAEQlQIPC0EMQQQQywIAC0H4zMAAQR1BmM3AABCIAQALfwACQAJAIAQEQEEMQQQQVSIARQ0BIAAgAjYCCCAAIAI2AgQgACABNgIAQQxBBBBVIgFFDQIgASAENgIIIAEgBDYCBCABIAM2AgAgACABEAEgARCVAiAAEJUCDwtBtMTAAEGIBEHgyMAAEIgBAAtBDEEEEMsCAAtBDEEEEMsCAAs0AEEMQQQQVSIARQRAQQxBBBDLAgALIAAgAjYCCCAAIAI2AgQgACABNgIAIAAQAiAAEJUCC/cDAgJ/AX4jAEFAaiIBJAACQAJAAkACQAJAAkACQAJAIANBgAJNBEBBDEEEEFUiBEUNBSAEIAM2AgggBCADNgIEIAQgAjYCACAEEAMiBQ0DIAMNAUEBIQUMAgtBIEEBEFUiAkUNBSAAQQE2AgAgAkEYakGIycAAKQAANwAAIAJBEGpBgMnAACkAADcAACACQQhqQfjIwAApAAA3AAAgAkHwyMAAKQAANwAAIABBEGpCoICAgIAENwMAIABBDGogAjYCACAAQQhqQQI2AgAMAwsgA0EBEFUiBUUNBQsgBSACIAMQnAMhAiAAQQxqIAM2AgAgAEEIaiADNgIAIAAgAjYCBCAAQQA2AgAgBBCVAgwBCyAFKAIAIgJFDQQgBSkCBCEGIAUQlQIgASAGNwIEIAEgAjYCACABQTRqQQE2AgAgAUIBNwIkIAFBqMnAADYCICABQcMANgI8IAEgAUE4ajYCMCABIAE2AjggAUEQaiABQSBqEM0CIABBCGpBAjYCACAAQQxqIAEpAxA3AgAgAEEUaiABQRhqKAIANgIAIABBATYCAAJAIAEoAgAiAEUNACABKAIERQ0AIAAQlQILIAQQlQILIAFBQGskAA8LQQxBBBDLAgALQSBBARDLAgALIANBARDLAgALQfjMwABBHUGYzcAAEIgBAAu0BAIBfwF+IwBBQGoiASQAAkACQAJAAkACQAJAAkACQCADQYACTQRAQQxBBBBVIgRFDQMgBCADNgIIIAQgAzYCBCAEIAI2AgBBwABBARBVIgNFDQRBDEEEEFUiAkUNBSACQsAANwIEIAIgAzYCACAEIAIQBCIDDQEgAigCACIDRQ0HIAIpAgQhBSACEJUCIABBCGogBTcCACAAIAM2AgQgAEEANgIAIAQQlQIMAgtBJEEBEFUiAkUNBSAAQQE2AgAgAkEgakHQycAAKAAANgAAIAJBGGpByMnAACkAADcAACACQRBqQcDJwAApAAA3AAAgAkEIakG4ycAAKQAANwAAIAJBsMnAACkAADcAACAAQRBqQqSAgIDABDcDACAAQQxqIAI2AgAgAEEIakECNgIADAELIAMoAgAiAkUNBiADKQIEIQUgAxCVAiABIAU3AgQgASACNgIAIAFBNGpBATYCACABQgE3AiQgAUHwycAANgIgIAFBwwA2AjwgASABQThqNgIwIAEgATYCOCABQRBqIAFBIGoQzQIgAEEIakECNgIAIABBDGogASkDEDcCACAAQRRqIAFBGGooAgA2AgAgAEEBNgIAAkAgASgCACIARQ0AIAEoAgRFDQAgABCVAgsgBBCVAgsgAUFAayQADwtBDEEEEMsCAAtBwABBARDLAgALQQxBBBDLAgALQSRBARDLAgALQfjMwABBHUGYzcAAEIgBAAtB+MzAAEEdQZjNwAAQiAEAC6EDAgJ/AX4jAEFAaiIBJAAgAigCCCEDIAIoAgAhAgJAAkACQAJAQQxBBBBVIgQEQCAEIAM2AgggBCADNgIEIAQgAjYCAEHaAEEBEFUiA0UNAUEMQQQQVSICRQ0CIAJC2gA3AgQgAiADNgIAAkAgBCACEAUiA0UEQCACKAIAIgNFDQUgAikCBCEFIAIQlQIgAEEIaiAFNwIAIAAgAzYCBCAAQQA2AgAMAQsgAygCACICRQ0FIAMpAgQhBSADEJUCIAEgBTcCBCABIAI2AgAgAUE0akEBNgIAIAFCATcCJCABQZDKwAA2AiAgAUHDADYCPCABIAFBOGo2AjAgASABNgI4IAFBEGogAUEgahDNAiAAQQhqQQI2AgAgAEEMaiABKQMQNwIAIABBFGogAUEYaigCADYCACAAQQE2AgACQCABKAIAIgBFDQAgASgCBEUNACAAEJUCCwsgBBCVAiABQUBrJAAPC0EMQQQQywIAC0HaAEEBEMsCAAtBDEEEEMsCAAtB+MzAAEEdQZjNwAAQiAEAC0H4zMAAQR1BmM3AABCIAQALqQIAQQxBBBBVIgEEQAJAIAEgAzYCCCABIAM2AgQgASACNgIAQQxBBBBVIgJFDQAgAiAFNgIIIAIgBTYCBCACIAQ2AgBBDEEEEFUiA0UNACADIAc2AgggAyAHNgIEIAMgBjYCACAAAn8CQAJAAkACQAJAAkACQAJAIAEgAiADEAYiBA4LAQIDBAUGAAAAAAcACyAAQQhqIAQ2AgAgAEEEakEGNgIAQQEMBwsgAEEBOgABQQAMBgsgAEEAOgABQQAMBQtBmMrAAEE4QdDKwAAQiAEACyAAQQRqQQI2AgBBAQwDCyAAQQRqQQM2AgBBAQwCCyAAQQRqQQQ2AgBBAQwBCyAAQQRqQQE2AgBBAQs6AAAgAxCVAiACEJUCIAEQlQIPCwtBDEEEEMsCAAu2AgEBfgJAAkACQEEMQQQQVSIBBEAgASADNgIIIAEgAzYCBCABIAI2AgBBDEEEEFUiAkUNASACIAU2AgggAiAFNgIEIAIgBDYCACAAAn8CQAJAAkACQAJAAkACQCABIAIgBkH/AXEQByIHQiCIpyIDDgcBAAIDBAAFAAsgAEEDNgIEIABBCGogAzYCAAwFCyAHpyIDRQ0IIAMoAgAiBEUNCSADKQIEIQcgAxCVAiAAQQhqIAc3AgAgACAENgIEQQAMBQtBmMrAAEE4QeDKwAAQiAEACyAAQQA2AgQMAgsgAEEBNgIEQQEMAgsgAEECNgIEC0EBCzYCACACEJUCIAEQlQIPC0EMQQQQywIAC0EMQQQQywIAC0HQzMAAQRZB6MzAABCIAQALQfjMwABBHUGYzcAAEIgBAAusAgBBDEEEEFUiAQRAAkAgASADNgIIIAEgAzYCBCABIAI2AgBBDEEEEFUiAkUNACACIAU2AgggAiAFNgIEIAIgBDYCAEEMQQQQVSIDRQ0AIAMgBzYCCCADIAc2AgQgAyAGNgIAIAACfwJAAkACQAJAAkACQAJAAkAgASACIAMQCCIEDgsBAgMEBQYAAAAABwALIABBCGogBDYCACAAQQRqQQY2AgBBAQwHCyAAQQE6AAFBAAwGCyAAQQA6AAFBAAwFC0G8y8AAQcEAQYDMwAAQiAEAC0HwysAAQTtBrMvAABCIAQALIABBBGpBAzYCAEEBDAILIABBBGpBBDYCAEEBDAELIABBBGpBATYCAEEBCzoAACADEJUCIAIQlQIgARCVAg8LC0EMQQQQywIAC7ADAQF/IwBBMGsiASQAIAEgAiADELsBIAEoAgghAyABKAIAIQhBDEEEEFUiAgRAAkAgAiADNgIIIAIgAzYCBCACIAg2AgAgAUEQaiAEIAUQuwEgASgCGCEEIAEoAhAhBUEMQQQQVSIDRQ0AIAMgBDYCCCADIAQ2AgQgAyAFNgIAIAFBIGogBiAHELsBIAEoAighBiABKAIgIQdBDEEEEFUiBEUNACAEIAY2AgggBCAGNgIEIAQgBzYCACAAAn8CQAJAAkACQAJAAkACQAJAIAIgAyAEEAkiBg4LAQIDBAUGAAAAAAcACyAAQQhqIAY2AgAgAEEEakEGNgIAQQEMBwsgAEEBOgABQQAMBgsgAEEAOgABQQAMBQtBvMvAAEHBAEGgzMAAEIgBAAtB8MrAAEE7QZDMwAAQiAEACyAAQQRqQQM2AgBBAQwCCyAAQQRqQQQ2AgBBAQwBCyAAQQRqQQE2AgBBAQs6AAAgBBCVAiABKAIkBEAgBxCVAgsgAxCVAiABKAIUBEAgBRCVAgsgAhCVAiABKAIEBEAgCBCVAgsgAUEwaiQADwsLQQxBBBDLAgALNABBDEEEEFUiAEUEQEEMQQQQywIACyAAIAI2AgggACACNgIEIAAgATYCACAAEAogABCVAgvRQwIUfwJ+IwBB0ANrIgQkAAJAAkACQAJAAkACQAJAAn4CQAJAAn4CQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACQAJAAkACQAJAQQxBBBBVIhEEQCARIAM2AgggESADNgIEIBEgAjYCACAREAsiAQRAIAEoAgAiFARAIAEoAgQhFSABKAIIIRZBBCEFIAEQlQIgBEHQAmogFCAWENgBIARByAJqIARB0AJqEOABQQAhAyAELQDIAkEBcUUNICAELQDJAiIBQfsARwRAIAFBIkcEQEEKIQUMIgsgBEGoA2ogBEHQAmoQswEgBC0AqANBAUYNBEEOIQUMIQsgBEHQAmoQ2QEgBEGoA2ogBEHQAmoiDRCzAQJAIAQtAKgDQQFHBEAgBC0AqQMhFyAEQagDaiANEN4BIAQoAqgDIgVBFUYNASAEQbADaikDACEYIAQoAqwDIQYMIgsgBCgCrAMiBUEIdiEDIARBtANqKQIAIRggBEGwA2ooAgAhBgwhCyAEQcACaiANEOABIAQtAMACQQFxIQEgBC0AwQIhBgJAIBdBAUcEQEEAIQIgAUUEQEEEIQcMIQsCQAJAAkAgBkH/AXEiAUH7AEcEQCABQSJHBEBBCiEHDCULIARBqANqIA0QswEgBC0AqANBAUYNAUEOIQcMJAsgDRDZASAEQagDaiANIggQswECQCAELQCoA0EBRwRAIAQtAKkDIQogBEGoA2ogCBDeASAEKAKoAyIHQRVGDQEgBEGwA2opAwAhGCAEKAKsAyEGDCULIAQoAqwDIgdBCHYhAiAEQbQDaikCACEYIARBsANqKAIAIQYMJAsCfwJAIApBAUcEQCAEQagDaiAIEK8BIARBtANqKAIAIQ4gBEGwA2ooAgAhBiAEKAKsAyEHIAQoAqgDQQFHBEAgBEG4AmogCBDgAQJAIAQtALgCQQFxBEAgBC0AuQJB/QBGDQQgBw0BDCcLIAdFIAZFcg0lIAcQlQIMJQsgBkUNJSAHEJUCDCULIA6tIRggBEG4A2o1AgBCIIYMJQsgBEGwAmogCBDgASAELQCwAkEBcUUNICAELQCxAkEiRwRAQQ4hB0IADCILIAgQ2QEgBEGoA2ogCBDfASAEQbgDaigCACEOIARBtANqKAIAIQEgBEGwA2ooAgAhBiAEKAKsAyEHIAQoAqgDQQFHBEACQCAHRQRAAkACQCABQQBOBEAgAQ0BQQEhAwwCCxDMAgALIAFBARBVIgNFDQoLIAMgBiABEJwDGiABIQ4MAQsgBiEDCyAEQagCaiAIEOABAkACQCAELQCoAkEBcQRAIAQtAKkCQf0ARg0CQQshByABDQEMIwsgAUUgA0VyDSMgAxCVAkEEIQdCAAwkCyADRQ0hIAMQlQJCAAwjCyAIENkBIAEhBiADIQdBAQwCCyABrSEYIA6tQiCGDCELIAgQ2QFBAAshASAEQaACaiANEOABIAQtAKACQQFxRQ0BIAQtAKECQf0ARw0CIA0Q2QEgBCAGrSAOrUIghoQ3AuQCIAQgBzYC4AJBAAwPCyAELwCtAyAELQCvA0EQdHIhAiAEQbQDaikCACEYIARBsANqKAIAIQYgBC0ArAMhBwwiCyAHRSAGRXIhASAKQQFHBEBBBCEFIAENIyAHEJUCDCMLQQQhBSABDSIgBxCVAgwiCyAHRSAGRXIhASAKQQFHBEBBCyEFIAENIiAHEJUCDCILQQshBSABDSEgBxCVAgwhC0EAIQIgAUUNFiAGQf8BcSIBQfsARwRAIAFBIkcEQEEKIQUMGQsgBEGoA2ogDRCxASAELQCoA0EBRg0GQQ4hBQwYCyANENkBIARBqANqIA0iChCxAQJAAn8gBC0AqANBAUcEQCAELQCpAyEGIARBqANqIAoQ3gEgBCgCqAMiBUEVRwRAIARBsANqKQMAIRggBCgCrAMhBkEADAILIAZBAWsOBAwLCgkCCyAEQbQDaikCACEYIARBsANqKAIAIQYgBCgCrAMiBUEIdgshAiAYQiCIpyEIIBinIQcMGAsgBEE4aiAKEOABIAQtADhBAXFFBEBBBCEFDBYLIAQtADlB+wBHBEBBDiEFDBYLIAoQ2QEgBEEwaiAKENcBIAQoAjAhAiAEIAQtADRBAXEiAToA/AIgBCACNgL4AiAEQShqIAIQ4AEgBC0AKEEBcUUEQEECIQUMFgsgBC0AKSEFIARBiANqQQRyIQ8gBEGoA2pBBHIhEiAEQbADaiETIAEhBgNAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQCAFQf8BcSIHQSxHBEAgB0H9AEYNAyABQf8BcQ0BQQkhBQwiCyAGQf8BcQ0AIAIQ2QEgBEEgaiACEOABIAQtACBBAXFFDSAgBC0AISEFDAELQQAhASAEQQA6APwCCyAFQf8BcSIGQSJHBEBBECEFIAZB/QBHDSBBEyEFDCALIARBCGogAhDgASAELQAIQQFxRQ0eIAQtAAlBIkcEQEEOIQUMIAsgAhDZASAEQagDaiACEN8BIAQoArgDIQggBCgCtAMhByAEKAKwAyEGIAQoAqwDIQUgBCgCqANBAUYNHyAFRQRAIAdBe2oOAwILBAsLAn8CQAJAAkAgCEF7ag4DAAIBAgsgBkHU18AAQQUQnwMNAUEADAILIAZB4dfAAEEHEJ8DDQBBAQwBC0ECCyAHBEAgBhCVAgsOAgIECgsgA0UNByAJDQYgBEGoA2pBBHJB4dfAAEEHEKcBIARBuANqKAIAIQggBEG0A2ooAgAhByAEQbADaigCACEGIAQoAqwDIQUgC0UNISADEJUCDCELIAZB1NfAAEEFEJ8DDQgLIAMNAiAEQagDaiAEQfgCahCqASAEKAKoA0EBRg0DIAQoArQDIQ4gBCgCsAMhCyAEKAKsAyEDDAgLIAZB4dfAAEEHEJ8DDQYLIAkEQCAEQagDakHh18AAQQcQqAEgBEG0A2ooAgAhCCAEQbADaigCACEHIAQoAqwDIQYgBCgCqAMhBSAMDRwMHQsgBEGIA2ogAhDeAQJAIAQoAogDIgVBFUcEQCATIA8pAgA3AgAgE0EIaiAPQQhqKAIANgIADAELIARBqANqIAIQrwEgBCgCqANBAUcEQCAEKAK0AyEQIAQoArADIQwgBCgCrAMhCQwICyAEKAKsAyEFCyAEQbgDaigCACEIIAQoArQDIQcgBCgCsAMhBgwcCyAEQagDakHU18AAQQUQqAEgBEG0A2ooAgAhCCAEQbADaigCACEHIAQoAqwDIQYgBCgCqAMhBQwZCyAEQbgDaigCACEIIAQoArQDIQcgBCgCsAMMAgsgBEGoA2ogChDcASAEKAKoAyIFQRVHBEAgBEG0A2ooAgAhCCAEQbADaigCACEHIAQoAqwDIQYgCwRAIAMQlQILIAxFDRsgCRCVAgwbCyAEQRhqIAoQ4AECQCAELQAYQQFxBEAgBC0AGUH9AEcNASAKENkBQQAhAQwRCyALBEAgAxCVAgtBBCEFQQAhAiAMRQ0dIAkQlQIMHQsgCwRAIAMQlQILQQshBUEAIQIgDEUNHCAJEJUCDBwLIARBqANqQQRyQdTXwABBBRCnASAEQbgDaigCACEIIARBtANqKAIAIQcgBEGwA2ooAgALIQYgBCgCrAMhBUEAIQMMFQsgBEGoA2ogAhDeAQJAIAQoAqgDIgVBFUcEQCAPIBIpAgA3AgAgD0EIaiASQQhqKAIANgIADAELIARBiANqIAIQrAEgBCgCiAMiBUEVRg0BCyAEQZQDaigCACEIIARBkANqKAIAIQcgBCgCjAMhBgwUCyAEIAIQ4AFBACEGIAQtAAEhBSAELQAAQQFxDQALQQIhBQwSCyABQQEQywIAC0H4zMAAQR1BmM3AABCIAQALQdDMwABBFkHozMAAEIgBAAtBDEEEEMsCAAsgBC8ArQMgBC0ArwNBEHRyIQMgBEG0A2opAgAhGCAEQbADaigCACEGIAQtAKwDIQUMHAsgBC8ArQMgBC0ArwNBEHRyIQIgBEG0A2opAgAiGEIgiKchCCAEQbADaigCACEGIAQtAKwDIQUgGKchBwwRCyAEQZgCaiAKEOABAkAgBC0AmAJBAXFFBEBBBCEFDAELIAQtAJkCQfsARwRAQQ4hBQwBCyAKENkBIARBkAJqIAoQ1wEgBCgCkAIhBSAEIAQtAJQCQQFxIgg6APQCIAQgBTYC8AIgBEGIAmogBRDgAUECIQECQAJAAkACQCAELQCIAkEBcUUNACAELQCJAiEGIARBiANqQQRyIQwgBEH4AmpBBHIhECAIIQICQANAAkACQAJAAkACQAJAAkACQAJAIAZB/wFxIgdBLEcEQCAHQf0ARg0DIAhB/wFxDQFBCSEBDAwLIAJB/wFxDQAgBRDZASAEQYACaiAFEOABIAQtAIACQQFxRQ0KIAQtAIECIQYMAQtBACEIIARBADoA9AILIAZB/wFxIgZBIkcEQEEQIQEgBkH9AEcNB0ETIQEMBwsgBEHwAWogBRDgASAELQDwAUEBcUUNCCAELQDxAUEiRwRAQQ4hAQwKCyAFENkBIARBiANqIAUQ3wEgBCgCmAMhByAEKAKUAyEGIAQoApADIQIgBCgCjAMhCSAEKAKIA0EBRgRAIAkhAQwKCwJAIAlFBEAgBkEERw0DIAIoAABB69K5owZHIQkMAQtBASEJIAdBBEYEQCACKAAAQevSuaMGRyEJCyAGRQ0AIAIQlQILIAkNASADDQMgBEGIA2ogBEHwAmoQqgEgBCgCiANBAUYNAiAEKAKUAyEOIAQoApADIQsgBCgCjAMhAwwFCyADDQMgBEGIA2pBBHJB5dPAAEEEEKcBIARBtANqIARBlANqKQIANwIAIAQgBCkCjAM3AqwDDAsLIARBiANqIAUQ3gECQCAEKAKIAyICQRVHBEAgECAMKQIANwIAIBBBCGogDEEIaigCADYCACAEIAI2AvgCDAELIARB+AJqIAUQrAEgBCgC+AJBFUYNBAsgBEG0A2ogBEGAA2opAwA3AgAgBCAEKQP4AjcCrAMMCAsgBEG0A2ogDEEIaikCADcCACAEIAwpAgA3AqwDDAkLIARBqANqQQRyQeXTwABBBBCoASALDQcMCAsgBEGoA2ogChDcASAEKAKoAyIFQRVHBEAgBEG0A2ooAgAhCCAEQbADaigCACEHIAQoAqwDIQYgC0UNCSADEJUCDAkLIARB+AFqIAoQ4AECQCAELQD4AUEBcQRAIAQtAPkBQf0ARw0BIAoQ2QFBBCEBDA4LQQQhBUEAIQIgC0UNGiADEJUCDBoLQQshBUEAIQIgC0UNGSADEJUCDBkLIARB6AFqIAUQ4AFBACECIAQtAOkBIQYgBC0A6AFBAXENAQsLDAELQQQhAQsgBEG4A2ogBzYCACAEQbQDaiAGNgIAIARBsANqIAI2AgAgBCABNgKsAwsgC0UgA0VyDQELIAMQlQILIARBsANqKAIAIQYgBEG0A2ooAgAhByAEQbgDaigCACEIIAQoAqwDIQULIAVBCHYhAgwQCyAEQeABaiAKEOABAkACQCAELQDgAUEBcUUNAAJAIAQtAOEBQfsARw0AIAoQ2QEgBEHYAWogChDXASAELQDcASEGIARB0AFqIAQoAtgBIggQ4AECQCAELQDQAUEBcUUNACAELQDRASEFIAZBAXEhBiAEQYgDakEEciEDIARBqANqQQRyIQsDQAJAAkACQCAFQf8BcSIHQSxHBEAgB0H9AEYNAiAGQf8BcQ0BQQkhBQwICyAGQf8BcQRAQRAhBQwICyAIENkBIARByAFqIAgQ4AEgBC0AyAFBAXFFDQYgBC0AyQEhBQsgBUH/AXEiDkEiRwRAQRAhBSAOQf0ARw0HQRMhBQwHCyAEQbgBaiAIEOABIAQtALgBQQFxRQ0FIAQtALkBQSJHDQQgCBDZASAEQagDaiAIEN8BIAQoArQDIQcgBCgCsAMhBiAEKAKsAyEFIAQoAqgDQQFHBEAgB0UgBUUgBkVycg0CIAYQlQIMAgsgBUEVRg0BIAQoArgDIQgMBgsgBEGoA2ogChDcASAEKAKoAyIFQRVHBEAgBEG0A2ooAgAhCCAEQbADaigCACEHIAQoAqwDIQYMBgsgBEHAAWogChDgASAELQDAAUEBcUUNFCAELQDBAUH9AEcEQEELIQUMFgsgChDZAUEDIQEMCAsgBEGoA2ogCBDeAQJAIAQoAqgDIgVBFUcEQCADIAspAgA3AgAgA0EIaiALQQhqKAIANgIADAELIARBiANqIAgQrAEgBCgCiAMiBUEVRw0AIARBsAFqIAgQ4AFBACEGIAQtALEBIQUgBC0AsAFBAXFFDQIMAQsLIARBlANqKAIAIQggBEGQA2ooAgAhByAEKAKMAyEGDAMLQQIhBQwCC0EOIQUMAQtBBCEFCyAFQQh2IQIMDwsgBEGoAWogChDgAQJAIAQtAKgBQQFxRQRAQQQhBQwBCyAELQCpAUH7AEcEQEEOIQUMAQsgChDZASAEQaABaiAKENcBIAQoAqABIQUgBCAELQCkAUEBcSIIOgD0AiAEIAU2AvACIARBmAFqIAUQ4AFBAiEBAkACQAJAAkAgBC0AmAFBAXFFDQAgBC0AmQEhBiAEQYgDakEEciEMIARB+AJqQQRyIRAgCCECAkADQAJAAkACQAJAAkACQAJAAkACQCAGQf8BcSIHQSxHBEAgB0H9AEYNAyAIQf8BcQ0BQQkhAQwMCyACQf8BcQ0AIAUQ2QEgBEGQAWogBRDgASAELQCQAUEBcUUNCiAELQCRASEGDAELQQAhCCAEQQA6APQCCyAGQf8BcSIGQSJHBEBBECEBIAZB/QBHDQdBEyEBDAcLIARBgAFqIAUQ4AEgBC0AgAFBAXFFDQggBC0AgQFBIkcEQEEOIQEMCgsgBRDZASAEQYgDaiAFEN8BIAQoApgDIQcgBCgClAMhBiAEKAKQAyECIAQoAowDIQkgBCgCiANBAUYEQCAJIQEMCgsCQCAJRQRAIAZBBEcNAyACKAAAQeHIkZMHRyEJDAELQQEhCSAHQQRGBEAgAigAAEHhyJGTB0chCQsgBkUNACACEJUCCyAJDQEgAw0DIARBiANqIARB8AJqEKoBIAQoAogDQQFGDQIgBCgClAMhDiAEKAKQAyELIAQoAowDIQMMBQsgAw0DIARBiANqQQRyQdDXwABBBBCnASAEQbQDaiAEQZQDaikCADcCACAEIAQpAowDNwKsAwwLCyAEQYgDaiAFEN4BAkAgBCgCiAMiAkEVRwRAIBAgDCkCADcCACAQQQhqIAxBCGooAgA2AgAgBCACNgL4AgwBCyAEQfgCaiAFEKwBIAQoAvgCQRVGDQQLIARBtANqIARBgANqKQMANwIAIAQgBCkD+AI3AqwDDAgLIARBtANqIAxBCGopAgA3AgAgBCAMKQIANwKsAwwJCyAEQagDakEEckHQ18AAQQQQqAEgCw0HDAgLIARBqANqIAoQ3AEgBCgCqAMiBUEVRwRAIARBtANqKAIAIQggBEGwA2ooAgAhByAEKAKsAyEGIAtFDQkgAxCVAgwJCyAEQYgBaiAKEOABAkAgBC0AiAFBAXEEQCAELQCJAUH9AEcNASAKENkBDAwLQQQhBUEAIQIgC0UNGCADEJUCDBgLQQshBUEAIQIgC0UNFyADEJUCDBcLIARB+ABqIAUQ4AFBACECIAQtAHkhBiAELQB4QQFxDQELCwwBC0EEIQELIARBuANqIAc2AgAgBEG0A2ogBjYCACAEQbADaiACNgIAIAQgATYCrAMLIAtFIANFcg0BCyADEJUCCyAEQbADaigCACEGIARBtANqKAIAIQcgBEG4A2ooAgAhCCAEKAKsAyEFCyAFQQh2IQIMDgsgBEHwAGogChDgASAELQBwQQFxRQRAQQQhBQwHCyAELQBxQfsARwRAQQ4hBQwHCyAKENkBIARB6ABqIAoQ1wEgBCgCaCECIAQgBC0AbEEBcSIBOgD8AiAEIAI2AvgCIARB4ABqIAIQ4AEgBC0AYEEBcUUEQEECIQUMBwsgBC0AYSEFIARBiANqQQRyIQ8gBEGoA2pBBHIhEiAEQbADaiETIAEhBgNAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQCAFQf8BcSIHQSxHBEAgB0H9AEYNAyABQf8BcQ0BQQkhBQwTCyAGQf8BcQ0AIAIQ2QEgBEHYAGogAhDgASAELQBYQQFxRQ0RIAQtAFkhBQwBC0EAIQEgBEEAOgD8AgsgBUH/AXEiBkEiRwRAQRAhBSAGQf0ARw0RQRMhBQwRCyAEQcgAaiACEOABIAQtAEhBAXFFDQ8gBC0ASUEiRwRAQQ4hBQwRCyACENkBIARBqANqIAIQ3wEgBCgCuAMhCCAEKAK0AyEHIAQoArADIQYgBCgCrAMhBSAEKAKoA0EBRg0QIAVFBEAgB0F7ag4EAgsLBAsLAn8CQAJAAkAgCEF7ag4EAAICAQILIAZB1NfAAEEFEJ8DDQFBAAwCCyAGKQAAQvLKzYP3zdu55QBSDQBBAQwBC0ECCyAHBEAgBhCVAgsOAgIECgsgA0UNByAJDQYgBEGoA2pBBHJB2dfAAEEIEKcBIARBuANqKAIAIQggBEG0A2ooAgAhByAEQbADaigCACEGIAQoAqwDIQUgC0UNEiADEJUCDBILIAZB1NfAAEEFEJ8DDQgLIAMNAiAEQagDaiAEQfgCahCqASAEKAKoA0EBRg0DIAQoArQDIQ4gBCgCsAMhCyAEKAKsAyEDDAgLIAYpAABC8srNg/fN27nlAFINBgsgCQRAIARBqANqQdnXwABBCBCoASAEQbQDaigCACEIIARBsANqKAIAIQcgBCgCrAMhBiAEKAKoAyEFIAwNDQwOCyAEQYgDaiACEN4BAkAgBCgCiAMiBUEVRwRAIBMgDykCADcCACATQQhqIA9BCGooAgA2AgAMAQsgBEGoA2ogAhCvASAEKAKoA0EBRwRAIAQoArQDIRAgBCgCsAMhDCAEKAKsAyEJDAgLIAQoAqwDIQULIARBuANqKAIAIQggBCgCtAMhByAEKAKwAyEGDA0LIARBqANqQdTXwABBBRCoASAEQbQDaigCACEIIARBsANqKAIAIQcgBCgCrAMhBiAEKAKoAyEFDAoLIARBuANqKAIAIQggBCgCtAMhByAEKAKwAwwCCyAEQagDaiAKENwBIAQoAqgDIgVBFUcEQCAEQbQDaigCACEIIARBsANqKAIAIQcgBCgCrAMhBiALBEAgAxCVAgsgDEUNDCAJEJUCDAwLIARB0ABqIAoQ4AECQCAELQBQQQFxBEAgBC0AUUH9AEcNASAKENkBQQEhAQwHCyALBEAgAxCVAgtBBCEFQQAhAiAMRQ0TIAkQlQIMEwsgCwRAIAMQlQILQQshBUEAIQIgDEUNEiAJEJUCDBILIARBqANqQQRyQdTXwABBBRCnASAEQbgDaigCACEIIARBtANqKAIAIQcgBEGwA2ooAgALIQYgBCgCrAMhBUEAIQMMBgsgBEGoA2ogAhDeAQJAIAQoAqgDIgVBFUcEQCAPIBIpAgA3AgAgD0EIaiASQQhqKAIANgIADAELIARBiANqIAIQrAEgBCgCiAMiBUEVRg0BCyAEQZQDaigCACEIIARBkANqKAIAIQcgBCgCjAMhBgwFCyAEQUBrIAIQ4AFBACEGIAQtAEEhBSAELQBAQQFxDQALQQIhBQwDCyAEQRBqIA0Q4AECQAJAAkACQAJAIAQtABBBAXEEQCAELQARQf0ARg0FQQshBSABDgQCAwQTAQtBBCEFAkACQAJAAkAgAQ4EAQIDFgALIAtFIANFcg0VIAMQlQIMFQsgC0UgA0VyRQRAIAMQlQILIAxFIAlFcg0UIAkQlQIMFAsgC0UgA0VyRQRAIAMQlQILIAxFIAlFcg0TIAkQlQIMEwsgC0UgA0VyDRIgAxCVAgwSCyALRSADRXINESADEJUCDBELIAtFIANFckUEQCADEJUCCyAMRSAJRXINECAJEJUCDBALIAtFIANFckUEQCADEJUCCyAMRSAJRXINDyAJEJUCDA8LIAtFIANFcg0OIAMQlQIMDgsgDRDZASAEIAM2AuACIAQgC60gDq1CIIaENwLkAkEBCyECIAQpA+ACIRggBEGoA2ogBEHQAmoQ2gEgGEIgiKchDSAYpyEIIAQoAqgDIgNBFUYEQCAAQRxqIBA2AgAgAEEYaiAMNgIAIABBFGogCTYCACAAQRBqIA42AgAgAEEMaiANNgIAIABBCGogCDYCACAAIAE2AgQgACACNgIAIBVFDRggFBCVAgwYCyAEQbQDaigCACEFIARBsANqKAIAIQIgBCgCrAMhBiAXQQFHBEAgCEUgDUVyIQkgAUUEQCAJDRggCBCVAgwYCyAJDRcgCBCVAgwXCwJAAkACQAJAIAEOBAECAxoACyAIRSANRXINGSAIEJUCDBkLIAhFIA1FckUEQCAIEJUCCyAMRSAJRXINGCAJEJUCDBgLIAhFIA1FckUEQCAIEJUCCyAMRSAJRXINFyAJEJUCDBcLIAhFIA1Fcg0WIAgQlQIMFgtBBCEFCyAMRSAJRXINAQsgCRCVAgsgC0UgA0VyDQAgAxCVAgsgBUEIdiECDAYLQQQhBQsgDEUgCUVyDQELIAkQlQILIAtFIANFcg0AIAMQlQILIAVBCHYhAgwBC0EEIQULIAVB/wFxIAJBCHRyIQULIAVBCHYhAyAHrSAIrUIghoQhGAwIC0IADAELQQQhB0IACyEZIBggGYQhGCAHQQh2IQIMAwtBBCEHQgAMAQtBCyEHQgALIRkgGCAZhCEYIAdBCHYhAgsgB0H/AXEgAkEIdHIhBQsgBUEIdiEDCyAFQf8BcSADQQh0ciEDIBhCIIinIQUgGKchAgsgBEGUA2ogBTYCACAEQZADaiACNgIAIAQgBjYCjAMgBCADNgKIA0GIAUEBEFUiAUUNASABQbS1wABBiAEQnAMhASAEQgA3AvwCIARBoLjAACgCADYC+AIgBEGoA2ogBEH4AmpBiLTAABD6AiAEQYgDaiAEQagDahDWAQ0CIAQoAvgCIQIgBCgC/AIhAyAEKAKAAyEGAkAgBCgCiANBFEkNACAEKAKMAyIIRQ0AIAQoApADRQ0AIAgQlQILIARBoANqIAY2AgAgBEGcA2ogAzYCACAEQZgDaiACNgIAIARBkANqQoiBgICAETcDACAEIAE2AowDIARBBzYCiAMgBEIANwL8AiAEQaC4wAAoAgA2AvgCIARBqANqIARB+AJqQYi0wAAQ+gIgBEGIA2ogBEGoA2oQygENAiAAQQhqIAQpA/gCNwIAIABBEGogBEGAA2ooAgA2AgAgAEEcaiAWNgIAIABBGGogFTYCACAAQRRqIBQ2AgAgAEKBgICAEDcCACAEQYgDahAmCyAREJUCIARB0ANqJAAPC0GIAUEBEMsCAAtBoLTAAEE3IARB4AJqQfS2wABBpLXAABDoAgALgwgBAX8jAEEwayICJAACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQCAAKAIAQQFrDgsBAgMEBQYHCAkKCwALIAJBLGpBATYCACACQgE3AhwgAkHM0sAANgIYIAJBxAA2AgQgAiAAQQRqNgIUIAIgAjYCKCACIAJBFGo2AgAgASACQRhqEPwCDAsLIAJBLGpBATYCACACQgE3AhwgAkGw0sAANgIYIAJBxQA2AgQgAiAAQQRqNgIUIAIgAjYCKCACIAJBFGo2AgAgASACQRhqEPwCDAoLIAJBLGpBATYCACACQgE3AhwgAkGQ0sAANgIYIAJBPTYCBCACIABBBGo2AhQgAiACNgIoIAIgAkEUajYCACABIAJBGGoQ/AIMCQsgAkEsakEBNgIAIAJCATcCHCACQfjRwAA2AhggAkE9NgIEIAIgAEEEajYCFCACIAI2AiggAiACQRRqNgIAIAEgAkEYahD8AgwICyACQQxqQcYANgIAIAJBLGpBAjYCACACIABBCGo2AhAgAkICNwIcIAJB0NHAADYCGCACQcYANgIEIAIgAEEQajYCFCACIAI2AiggAiACQRRqNgIIIAIgAkEQajYCACABIAJBGGoQ/AIMBwsgAkEsakEBNgIAIAJCATcCHCACQaTRwAA2AhggAkE9NgIEIAIgAEEEajYCFCACIAI2AiggAiACQRRqNgIAIAEgAkEYahD8AgwGCyACQSxqQQE2AgAgAkICNwIcIAJB7NDAADYCGCACQT02AgQgAiAAQQRqNgIUIAIgAjYCKCACIAJBFGo2AgAgASACQRhqEPwCDAULIAJBDGpBPTYCACACQSxqQQI2AgAgAiAAQQRqNgIQIAJCAjcCHCACQdDQwAA2AhggAkE9NgIEIAIgAEEQajYCFCACIAI2AiggAiACQRRqNgIIIAIgAkEQajYCACABIAJBGGoQ/AIMBAsgAkEMakE9NgIAIAJBLGpBAjYCACACIABBBGo2AhAgAkICNwIcIAJBqNDAADYCGCACQT02AgQgAiAAQRBqNgIUIAIgAjYCKCACIAJBFGo2AgggAiACQRBqNgIAIAEgAkEYahD8AgwDCyACQSxqQQE2AgAgAkIBNwIcIAJBhNDAADYCGCACQccANgIEIAIgAEEEajYCFCACIAI2AiggAiACQRRqNgIAIAEgAkEYahD8AgwCCyACQSxqQQE2AgAgAkIBNwIcIAJB8M/AADYCGCACQcgANgIEIAIgAEEEajYCFCACIAI2AiggAiACQRRqNgIAIAEgAkEYahD8AgwBCyACQSxqQQA2AgAgAkGguMAANgIoIAJCATcCHCACQdjPwAA2AhggASACQRhqEPwCCyACQTBqJAALPQECfyAAKAIIIQEgACgCACECQQxBBBBVIgBFBEBBDEEEEMsCAAsgACABNgIIIAAgATYCBCAAIAI2AgAgAAtLAQF/AkAgAQRAIAEoAgAiAg0BQfjMwABBHUGYzcAAEIgBAAtB0MzAAEEWQejMwAAQiAEACyAAIAI2AgAgACABKQIENwIEIAEQlQILPgEBfyMAQRBrIgIkACACIAFBqM3AAEEEEIADIAIgADYCDCACIAJBDGpBrLnAABDtAhogAhDuAiACQRBqJAALPgEBfyMAQRBrIgIkACACIAFBrM3AAEENEIADIAIgADYCDCACIAJBDGpBnLnAABDtAhogAhDuAiACQRBqJAALpwYBAX8jAEEQayICJAACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAAKAIAQQFrDgsBAgMEBQYHCAkKCwALIAIgAUHc1MAAQQ8Q/wI3AwAgAiAAQQRqNgIMIAJB5tLAAEEGIAJBDGpB7NTAABDiAhoMCwsgAiABQbnUwABBEBD/AjcDACACIABBBGo2AgwgAkHm0sAAQQYgAkEMakHM1MAAEOICGgwKCyACIAFBr9TAAEEKEP8CNwMAIAIgAEEEajYCDCACQcfTwABBAyACQQxqQay5wAAQ4gIaDAkLIAIgAUGi1MAAQQ0Q/wI3AwAgAiAAQQRqNgIMIAJBx9PAAEEDIAJBDGpBrLnAABDiAhoMCAsgAiABQfTTwABBDxD/AjcDACACIABBCGo2AgwgAkGD1MAAQQggAkEMakGM1MAAEOICGiACIABBEGo2AgwgAkGc1MAAQQYgAkEMakGM1MAAEOICGgwHCyACIAFB6dPAAEELEP8CNwMAIAIgAEEEajYCDCACQcfTwABBAyACQQxqQay5wAAQ4gIaDAYLIAIgAUHd08AAQQgQ/wI3AwAgAiAAQQRqNgIMIAJB5dPAAEEEIAJBDGpBrLnAABDiAhoMBQsgAiABQcrTwABBCBD/AjcDACACIABBBGo2AgwgAkHS08AAQQsgAkEMakGsucAAEOICGiACIABBEGo2AgwgAkHH08AAQQMgAkEMakGsucAAEOICGgwECyACIAFBsNPAAEEMEP8CNwMAIAIgAEEEajYCDCACQbzTwABBCyACQQxqQay5wAAQ4gIaIAIgAEEQajYCDCACQcfTwABBAyACQQxqQay5wAAQ4gIaDAMLIAIgAUGY08AAQQgQ/wI3AwAgAiAAQQRqNgIMIAJB5tLAAEEGIAJBDGpBoNPAABDiAhoMAgsgAiABQfzSwABBDBD/AjcDACACIABBBGo2AgwgAkHm0sAAQQYgAkEMakGI08AAEOICGgwBCyACIAFB1NLAAEESEP8CNwMAIAIgAEEEajYCDCACQebSwABBBiACQQxqQezSwAAQ4gIaCyACEOwCIAJBEGokAAstAQF/IwBBEGsiASQAIAFBCGogAEEIaigCADYCACABIAApAgA3AwAgARDRAQALLAEBfyMAQRBrIgEkACABIAApAgA3AwggAUEIakHo2MAAQQAgACgCCBCnAgALLgEBfyMAQRBrIgAkACAAQYDhwAA2AgggAEEdNgIEIABB4eDAADYCACAAENABAAsZACAAKAIAKAIAIgAoAgAgACgCCCABEIUDCx0AIAEoAgBFBEAACyAAQfzYwAA2AgQgACABNgIAC1UBAn8gASgCACECIAFBADYCAAJAIAIEQCABKAIEIQNBCEEEEFUiAUUNASABIAM2AgQgASACNgIAIABB/NjAADYCBCAAIAE2AgAPCwALQQhBBBDLAgAL6gMBAX8jAEEwayICJAACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAAKAIAQQFrDhQBAgMEBQYHCAkKCwwNDg8QERITFAALIAJB+t7AADYCKEEiDBQLIAJB4d7AADYCKEEZDBMLIAJBxd7AADYCKEEcDBILIAJBqt7AADYCKEEbDBELIAJBi97AADYCKEEfDBALIAJB5d3AADYCKEEmDA8LIAJBvd3AADYCKEEoDA4LIAJBht3AADYCKEE3DA0LIAJB39zAADYCKEEnDAwLIAJBp9zAADYCKEE4DAsLIAJB79vAADYCKEE4DAoLIAJBwdvAADYCKEEuDAkLIAJBqdvAADYCKEEYDAgLIAJBmtvAADYCKEEPDAcL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Qf8BcUEGSQ0EIAVBn39qQf8BcUEGTw0GIAVBqX9qIQEMBQtBASEBQQwhBUEBIQkCQAJAAkACQAJAAkAgA0Feag5UABMTExMTExMTExMTEwATExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTEwATExMTEwETExMCExMTExMTEwMTExMEEwUPEwsgAigCDCAERgRAIAJBCGogBEEBEA0gAigCECEECyACKAIIIgcgBGogAzoAAAwGCyACKAIMIARGBEAgAkEIaiAEQQEQDSACKAIQIQQLIAIoAggiByAEakEIOgAADAULIAIoAgwgBEYEQCACQQhqIARBARANIAIoAhAhBAsgAigCCCIHIARqQQw6AAAMBAsgAigCDCAERgRAIAJBCGogBEEBEA0gAigCECEECyACKAIIIgcgBGpBCjoAAAwDCyACKAIMIARGBEAgAkEIaiAEQQEQDSACKAIQIQQLIAIoAggiByAEakENOgAADAILIAIoAgwgBEYEQCACQQhqIARBARANIAIoAhAhBAsgAigCCCIHIARqQQk6AAAMAQsgC0EBRgRAQREhBQwNCyACKAIMIARGBEAgAkEIaiAEQQEQDSACKAIQIQQgAigCCCEHCyAEIAdqIAM6AAALIAIgBEEBaiIENgIQDAYLIAhBBEGA4MAAENQCAAsgBUFJaiEBCwJAIAVBCHYiA0FQaiIIQf8BcUEKSQ0AIANBv39qQf8BcUEGTwRAIANBn39qQf8BcUEGTw0CIANBqX9qIQgMAQsgA0FJaiEICwJAIAVBEHYiA0FQaiIJQf8BcUEKSQ0AIANBv39qQf8BcUEGTwRAIANBn39qQf8BcUEGTw0CIANBqX9qIQkMAQsgA0FJaiEJCyAFQRh2IgVBUGoiA0H/AXFBCkkNAiAFQb9/akH/AXFBBkkNASAFQZ9/akH/AXFBBk8NACAFQal/aiEDDAILENIBAAsgBUFJaiEDCyAIQQh0IAFBDHRyIAlB/wFxQQR0ciIBIANB/wFxciEDAkACQCABQYDwA3FBgLADRwRAIANBgPADcUGAsANGIgFFDQFBDCEFDAgLAkAgC0EBRgRAIANB//8DcUGAuANPDQFBCCEFDAkLIANB//8DcUH/twNLDQdBACEIQQEhCyADIQwMAwsgDEH//wNxQYDQfGoiBUH/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IARBARANIAYoAgAhBAsgASgCACAEakHcADoAACAGIARBAWoiBDYCACAHKAIAIARGBEAgASAEQQEQDSAGKAIAIQQLIAEoAgAgBGpB9AA6AAAgBEEBagwECyAHKAIAIARGBEAgASAEQQEQDSAGKAIAIQQLIAEoAgAgBGpB3AA6AAAgBiAEQQFqIgQ2AgAgBygCACAERgRAIAEgBEEBEA0gBigCACEECyABKAIAIARqQe4AOgAAIARBAWoMAwsgBygCACAERgRAIAEgBEEBEA0gBigCACEECyABKAIAIARqQdwAOgAAIAYgBEEBaiIENgIAIAcoAgAgBEYEQCABIARBARANIAYoAgAhBAsgASgCACAEakHmADoAACAEQQFqDAILIAcoAgAgBEYEQCABIARBARANIAYoAgAhBAsgASgCACAEakHcADoAACAGIARBAWoiBDYCACAHKAIAIARGBEAgASAEQQEQDSAGKAIAIQQLIAEoAgAgBGpB8gA6AAAgBEEBagwBCwJ/AkACQAJAIAVBIE8EQCAFQYABSQ0BIAVBgBBJDQIgBUGAgARPDQMgCCAFQT9xQYABcjoADiAIIAVBDHZB4AFyOgAMIAggBUEGdkE/cUGAAXI6AA1BAwwECyAHKAIAIARGBEAgASAEQQEQDSAGKAIAIQQLIAEoAgAgBGpB3AA6AAAgBiAEQQFqIgQ2AgAgBygCACAERgRAIAEgBEEBEA0gBigCACEECyABKAIAIARqQfUAOgAAIAYgBEEBaiIENgIAIAcoAgAgBEYEQCABIARBARANIAYoAgAhBAsgASgCACAEakEwOgAAIAYgBEEBaiIENgIAIAcoAgAgBEYEQCABIARBARANIAYoAgAhBAsgASgCACAEakEwOgAAIAYgBEEBaiIENgIAIAVBD3EiA0EKSSEKIAcoAgAgBEYEQCABIARBARANIAYoAgAhBAsgASgCACAEaiAFQfABcUEEdkEwcjoAACAGIARBAWoiBDYCACAHKAIAIARGBEAgASAEQQEQDSAGKAIAIQQLIAEoAgAgBGogA0EwciADQTdqIAobOgAAIARBAWoMBAsgBygCACAERgRAIAEgBEEBEA0gBigCACEECyABKAIAIARqIAU6AAAgBEEBagwDCyAIIAVBP3FBgAFyOgANIAggBUEGdkHAAXI6AAxBAgwBCyAIIAVBP3FBgAFyOgAPIAggBUESdkHwAXI6AAwgCCAFQQZ2QT9xQYABcjoADiAIIAVBDHZBP3FBgAFyOgANQQQLIQUgBygCACAEayAFSQRAIAEgBCAFEA0gBigCACEECyABKAIAIARqIAhBDGogBRCcAxogBCAFagsiBDYCACACIAlHDQALCyABQQRqKAIAIARGBEAgASAEQQEQDSABQQhqKAIAIQQLIABBADYCACABQQhqIARBAWo2AgAgASgCACAEakEiOgAAIAhBEGokAAtQAQJ/IAFBBGooAgAgAUEIaiIDKAIAIgJrQQNNBEAgASACQQQQDSADKAIAIQILIABBADYCACADIAJBBGo2AgAgASgCACACakHu6rHjBjYAAAsNACAAIAEgAiADEOkBC1QAIAEoAggiAiABQQRqKAIARgRAIAEgAkEBEA0gASgCCCECCyAAIAE2AgQgAEEANgIAIAEgAkEBajYCCCAAQQhqQQE6AAAgASgCACACakHbADoAAAtWAQF/IAEoAggiAiABQQRqKAIARgRAIAEgAkEBEA0gASgCCCECCyAAIAE2AgQgAEEANgIAIAEgAkEBajYCCCAAQQhqQQE6AAAgASgCACACakH7ADoAAAurAgICfwF+IwBBIGsiBSQAIAEoAggiBCABQQRqKAIARgRAIAEgBEEBEA0gASgCCCEECyABIARBAWo2AgggASgCACAEakH7ADoAACAFQRBqIAEgAiADEOkBAkAgBSgCEEEBRwRAIAEoAggiBCABQQRqIgIoAgBGBEAgASAEQQEQDSABKAIIIQQLIAEoAgAgBGpBOjoAACABIARBAWoiBDYCCCACKAIAIARGBEAgASAEQQEQDSABKAIIIQQLIAAgATYCBCAAQQA2AgAgASAEQQFqNgIIIABBCGpBAToAACABKAIAIARqQfsAOgAADAELIAVBCGogBUEcaigCACIBNgIAIAUgBSkCFCIGNwMAIABBDGogATYCACAAIAY3AgQgAEEBNgIACyAFQSBqJAAL7A8CCH8DfgJAAkACQAJAAkACQCABQRtJBEAMAQtBACABQWZqIgcgByABSxshCgNAIAhBGmoiByAISQ0DIAcgAUsNAgJAIAZBIGoiByAGTwRAIAcgA00NASAHIANB4OXAABDVAgALQfDjwABBHEHQ5cAAENMCAAsgAiAGaiIFIAQg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9QEACywBAX8jAEEQayIBJAAgASAAKQIANwMIIAFBCGpBnPrAAEEAIAAoAggQpwIACy4BAX8jAEEQayIAJAAgAEHw+8AANgIIIABBDjYCBCAAQeH7wAA2AgAgABD0AQALFgAgACgCACIAKAIAIAAoAgQgARCFAwsdACABKAIARQRAAAsgAEGw+sAANgIEIAAgATYCAAtVAQJ/IAEoAgAhAiABQQA2AgACQCACBEAgASgCBCEDQQhBBBBVIgFFDQEgASADNgIEIAEgAjYCACAAQbD6wAA2AgQgACABNgIADwsAC0EIQQQQywIAC6wDAQN/IwBBMGsiAiQAAn8CQAJAAkACQAJAIAAoAgQiBA4DAAMCAQsQ9gEACyACQSxqQQA2AgAgAkGw+sAANgIoIAJCATcCHCACQcj6wAA2AhhBASABIAJBGGoQ/AINAxogBEEDdEF4akEDdkEBaiEEIAAoAgAhAANAIAIgADYCFCADBEAgAkEANgIsIAJBsPrAADYCKCACQgE3AhwgAkHU+sAANgIYIAEgAkEYahD8Ag0ECyACQQE2AiwgAkICNwIcIAJB3PrAADYCGCACQegANgIEIAIgAjYCKCACIAJBFGo2AgAgASACQRhqEPwCDQMgAEEIaiEAIAQgA0EBaiIDRw0AC0EADAMLIAJBDGpB6QA2AgAgAkEsakECNgIAIAJCAzcCHCACQfT6wAA2AhggAkHpADYCBCACIAAoAgAiADYCACACIABBCGo2AgggAiACNgIoIAEgAkEYahD8AgwCCyACQSxqQQE2AgAgAkICNwIcIAJB3PrAADYCGCACQekANgIEIAIgACgCADYCACACIAI2AiggASACQRhqEPwCDAELQQELIAJBMGokAAvqAgIDfwJ+AkACQAJAAkACQAJAIAApAwAiBiACrUIDhnwiByAGWgRAIAAgBzcDAEHAACAAKAIIIgNrIgRBwQBPDQEgBCACTQRAIABBzABqIgUgAwRAIANBwQBPDQcgAyAAQQxqIgNqIAEgBBCcAxogAEEANgIIIAUgA0EBEPwBIAIgBGshAiABIARqIQELIAEgAkEGdhD8ASAAQQxqIAEgAkFAcWogAkE/cSICEJwDGgwHCyACIANqIgQgA0kNBCAEQcEATw0CIAQgA2siBCACRw0DIAAgA2pBDGogASACEJwDGiAAKAIIIgEg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AX8CQCAAQQEQjwIiAUUNACABEMACELcCDQAgAUEAIAAQngMaCyABC14BA38jAEEQayIBJAAgACgCDCICRQRAQfiAwQBBK0GwhcEAENMCAAsgACgCCCIDRQRAQfiAwQBBK0HAhcEAENMCAAsgASACNgIIIAEgADYCBCABIAM2AgAgARCeAgALigIBA38jAEEgayIEJABBASEFQaywwQBBrLDBACgCACIGQQFqNgIAAkBBiLTBACgCAEEBRgRAQYy0wQAoAgBBAWohBQwBC0GItMEAQQE2AgALQYy0wQAgBTYCAAJAAkAgBkEASCAFQQJLcg0AIAQgAzYCHCAEIAI2AhhBoLDBACgCACICQX9MDQBBoLDBACACQQFqIgI2AgBBoLDBAEGosMEAKAIAIgMEf0GksMEAKAIAIARBCGogACABKAIQEQEAIAQgBCkDCDcDECAEQRBqIAMoAgwRAQBBoLDBACgCAAUgAgtBf2o2AgAgBUEBTQ0BCwALIwBBEGsiAiQAIAIgATYCDCACIAA2AggAC6kCAgR/AX4jAEEwayICJAAgAUEEaiEEAkAgASgCBARAQfSBwQAoAgAhBQwBCyABKAIAIQMgAkIANwIMIAJB9IHBACgCACIFNgIIIAIgAkEIajYCFCACQShqIANBEGopAgA3AwAgAkEgaiADQQhqKQIANwMAIAIgAykCADcDGCACQRRqQZiAwQAgAkEYahDdAhogBEEIaiACQRBqKAIANgIAIAQgAikDCDcCAAsgAkEgaiIDIARBCGooAgA2AgAgAUEMakEANgIAIAQpAgAhBiABQQhqQQA2AgAgASAFNgIEIAIgBjcDGEEMQQQQVSIBRQRAQQxBBBDLAgALIAEgAikDGDcCACABQQhqIAMoAgA2AgAgAEH4hcEANgIEIAAgATYCACACQTBqJAALsQEBAn8jAEEwayICJAAgAUEEaiEDIAEoAgRFBEAgASgCACEBIAJCADcCDCACQfSBwQAoAgA2AgggAiACQQhqNgIUIAJBKGogAUEQaikCADcDACACQSBqIAFBCGopAgA3AwAgAiABKQIANwMYIAJBFGpBmIDBACACQRhqEN0CGiADQQhqIAJBEGooAgA2AgAgAyACKQMINwIACyAAQfiFwQA2AgQgACADNgIAIAJBMGokAAtFAQJ/IAEoAgQhAiABKAIAIQNBCEEEEFUiAUUEQEEIQQQQywIACyABIAI2AgQgASADNgIAIABBiIbBADYCBCAAIAE2AgALEwAgAEGIhsEANgIEIAAgATYCAAtVAQJ/IAEoAgAhAiABQQA2AgACQCACBEAgASgCBCEDQQhBBBBVIgFFDQEgASADNgIEIAEgAjYCACAAQYiGwQA2AgQgACABNgIADwsAC0EIQQQQywIACx0AIAEoAgBFBEAACyAAQYiGwQA2AgQgACABNgIAC1gBAX8jAEEQayICJAAgAiABQbeGwQBBCBCAAyACIAA2AgwgAiACQQxqQeSBwQAQ7QIaIAIgAEEEajYCDCACIAJBDGpBwIbBABDtAhogAhDuAiACQRBqJAALEAAgACABakF/akEAIAFrcQsPACAAQQF0IgBBACAAa3ILCgBBACAAayAAcQsSAEEAQRkgAEEBdmsgAEEfRhsLCgAgACgCBEF4cQsNACAALQAEQQJxQQF2CwoAIAAoAgRBAXELDQAgACgCBEEDcUEBRwsLACAALQAEQQNxRQsnACAAIAAoAgRBAXEgAXJBAnI2AgQgACABaiIAIAAoAgRBAXI2AgQLHgAgACABQQNyNgIEIAAgAWoiACAAKAIEQQFyNgIECwwAIAAgAUEDcjYCBAsWACAAIAFBAXI2AgQgACABaiABNgIACyMAIAIgAigCBEF+cTYCBCAAIAFBAXI2AgQgACABaiABNgIACwcAIAAgAWoLBwAgACABawsHACAAQQhqCwcAIABBeGoLGQEBfyAAKAIQIgEEfyABBSAAQRRqKAIACwsKACAAKAIMQQFxCwoAIAAoAgxBAXYLDQAgACgCACAAKAIEags5AQF/IAFBEHZAACECIABBADYCCCAAQQAgAUGAgHxxIAJBf0YiARs2AgQgAEEAIAJBEHQgARs2AgALZAECfyMAQRBrIgIkACAAKAIAIgAoAgghAyAAKAIAIQAgAiABEIEDNwMAIAMEQANAIAIgADYCDCACIAJBDGpB/IrBABDwAiAAQQFqIQAgA0F/aiIDDQALCyACEPECIAJBEGokAAsMACAAKAIAIAEQmwMLDgAgACgCACABEMkCQQAL1gIBA38jAEEQayICJAACQAJ/AkACQCABQYABTwRAIAJBADYCDCABQYAQSQ0BIAFBgIAETw0CIAIgAUE/cUGAAXI6AA4gAiABQQx2QeABcjoADCACIAFBBnZBP3FBgAFyOgANQQMMAwsgACgCCCIDIABBBGooAgBGBEAgACADQQEQDSAAKAIIIQMLIAAgA0EBajYCCCAAKAIAIANqIAE6AAAMAwsgAiABQT9xQYABcjoADSACIAFBBnZBwAFyOgAMQQIMAQsgAiABQT9xQYABcjoADyACIAFBEnZB8AFyOgAMIAIgAUEGdkE/cUGAAXI6AA4gAiABQQx2QT9xQYABcjoADUEECyEBIABBBGooAgAgAEEIaiIEKAIAIgNrIAFJBEAgACADIAEQDSAEKAIAIQMLIAAoAgAgA2ogAkEMaiABEJwDGiAEIAEgA2o2AgALIAJBEGokAAtaAQF/IwBBIGsiAiQAIAIgACgCADYCBCACQRhqIAFBEGopAgA3AwAgAkEQaiABQQhqKQIANwMAIAIgASkCADcDCCACQQRqQeSKwQAgAkEIahDdAiACQSBqJAALGgAgACABQZywwQAoAgAiAEHuACAAGxEBAAALEgBB8IzBAEERQYSNwQAQ0wIAC9cCAQZ/IwBBIGsiAyQAIAEoAgAhBwJAIAEoAgQiBkEDdCICRQRADAELIAdBBGohBQNAIAUoAgAgBGohBCAFQQhqIQUgAkF4aiICDQALCwJAAkACQAJAAkAgAUEUaigCAEUEQCAEIQIMAQsgBkUNAkEAIQVBASEGIARBD00EQCAHQQRqKAIARQ0CCyAEIARqIgIgBEkNAQtBACEFAkAgAkEATgRAIAINAUEBIQYMAgsQzAIACyACIQUgAkEBEFUiBkUNAwsgAEEANgIIIAAgBjYCACAAIAU2AgQgAyAANgIEIANBGGogAUEQaikCADcDACADQRBqIAFBCGopAgA3AwAgAyABKQIANwMIIANBBGpB5IrBACADQQhqEN0CDQEgA0EgaiQADwtBAEEAQdiLwQAQ1AIAC0H4i8EAQTMgA0EIakHoi8EAQcSMwQAQ6AIACyACQQEQywIAC/QEAQV/IwBBQGoiAyQAIANBCGogASACEIoDIAMgAygCCCADKAIMEIoDIAMgAykDADcDECADQTBqIANBEGoQiwMCQAJAAkACQCADKAIwIgYEQCADKAI0IgQgAkcNAiAAIAY2AgQMAQsgAEHoi8EANgIEQQAhAgsgAEEANgIAIABBCGogAjYCAAwBCwJAAkAgAkEATgRAIANBPGooAgAhByACDQFBASEFQQAhAgwCCxDMAgALIAJBARBVIgVFDQILQQAhASADQQA2AiAgAyAFNgIYIAMgAjYCHCACIARJBEAgA0EYakEAIAQQDSADKAIYIQUgAygCICEBCyABIAVqIAYgBBCcAxogAyABIARqIgI2AiAgBwRAIAMoAhwgAmtBAk0EQCADQRhqIAJBAxANIAMoAiAhAgsgAygCGCACaiIBQZSNwQAvAAA7AAAgAUECakGWjcEALQAAOgAAIAMgAkEDaiICNgIgCyADIAMpAxA3AyggA0EwaiADQShqEIsDIAMoAjAiBQRAA0AgAygCPCADKAIcIAJrIAMoAjQiBEkEQCADQRhqIAIgBBANIAMoAiAhAgsgAygCGCIBIAJqIAUgBBCcAxogAyACIARqIgI2AiAEQCADKAIcIAJrQQJNBEAgA0EYaiACQQMQDSADKAIgIQIgAygCGCEBCyABIAJqIgFBlI3BAC8AADsAACABQQJqQZaNwQAtAAA6AAAgAyACQQNqIgI2AiALIANBMGogA0EoahCLAyADKAIwIgUNAAsLIAAgAykDGDcCBCAAQQE2AgAgAEEMaiADQSBqKAIANgIACyADQUBrJAAPCyACQQEQywIAC2cBA38CQAJAAkAgASgCCCICQQBOBEAgASgCACEBIAINAUEBIQQMAgsQzAIACyACIQMgAkEBEFUiBEUNAQsgBCABIAIQnAMhASAAIAI2AgggACADNgIEIAAgATYCAA8LIAJBARDLAgALWgECfwJAAkACQCACQQBOBEAgAg0BQQEhAwwCCxDMAgALIAIhBCACQQEQVSIDRQ0BCyAAIAM2AgAgACAENgIEIAMgASACEJwDGiAAIAI2AggPCyACQQEQywIAC2kBAX8jAEEQayICJAAgAiABQZeNwQBBDRD/AjcDACACIAA2AgwgAkGkjcEAQQUgAkEMakGsjcEAEOICGiACIABBDGo2AgwgAkG8jcEAQQUgAkEMakHEjcEAEOICGiACEOwCIAJBEGokAAsOACAAKAIAGgNADAALAAtIAQF/IwBBIGsiAyQAIANBFGpBADYCACADQdSNwQA2AhAgA0IBNwIEIAMgATYCHCADIAA2AhggAyADQRhqNgIAIAMgAhDcAgALbQEBfyMAQTBrIgMkACADIAE2AgQgAyAANgIAIANB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CUHgAXFBoAFHDRALIAcgAkECaiIEaiIGQQAgAyAESxsiBUHUjcEAIAUbLQAAQcABcUGAAUYEQCACQQNqIQIMEgsgAyACSQ0JIAJBfUsNCiADIARJDQsMFgsgBSAHaiIEQQAgAyAFSxsiBkHUjcEAIAYbLQAAQcABcUGAAUYNDSADIAJJDQsgAyACTQ0MDBMLIAIgA0HYmsEAENUCAAsgBSADQdiawQAQ1QIACyACIANB6JrBABDVAgALIAIgBEHomsEAENgCAAsgBCADQeiawQAQ1QIACyACIANB+JrBABDVAgALIAIgBEH4msEAENgCAAsgBCADQfiawQAQ1QIACyACIANBmJvBABDVAgALIAIgBEGYm8EAENgCAAsgBCADQZibwQAQ1QIACyACIANBuJvBABDVAgALIAUgA0G4m8EAENUCAAsgAkECaiECDAILAkAgAyACTwRAIAMgAk0NAQwGCyACIANBqJvBABDVAgALIAUgA0Gom8EAENUCAAsCQCADIAJPBEAgAyACTQ0BDAULIAIgA0GIm8EAENUCAAsgBSADQYibwQAQ1QIACyACIANJDQALIAFBADYCBCABQdSNwQA2AgAgACADNgIEIAAgBzYCACAAQQxqQQA2AgAgAEEIakHUjcEANgIADwsgAEEANgIADwsgASAENgIAIAAgAjYCBCAAIAc2AgAgASADIAVrNgIECyAAQQxqQQE2AgAMAQsgASAGNgIAIAAgAjYCBCAAIAc2AgAgASADIARrNgIEIABBDGpBAjYCAAsgAEEIaiAINgIAC48BAQN/IwBBgAFrIgMkACAALQAAIQJBACEAA0AgACADakH/AGogAkEPcSIEQTByIARB1wBqIARBCkkbOgAAIABBf2ohACACQQR2IgINAAsgAEGAAWoiAkGBAU8EQCACQYABQYCTwQAQ2QIACyABQQFBkJPBAEECIAAgA2pBgAFqQQAgAGsQ9AIgA0GAAWokAAsOACAAKQMAQQEgARCRAwuPAQEDfyMAQYABayIDJAAgACgCACECQQAhAANAIAAgA2pB/wBqIAJBD3EiBEEwciAEQdcAaiAEQQpJGzoAACAAQX9qIQAgAkEEdiICDQALIABBgAFqIgJBgQFPBEAgAkGAAUGAk8EAENkCAAsgAUEBQZCTwQBBAiAAIANqQYABakEAIABrEPQCIANB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fCIFQiCGfCIKNwMAIAAgCiAHVK0gASAEfiACIAN+fCAGIAl+fCAFIAhUrUIghiAFQiCIhHx8NwMICwurrwEOAEGAgMAAC+MVYXR0ZW1wdCB0byBhZGQgd2l0aCBvdmVyZmxvdwoAAAAAAAAAAQAAAAsAAAAMAAAADQAAAAoAAAAAAAAAAQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAVAAAACgAAAAAAAAABAAAAFgAAAENvaW5kZW5vbWFtb3VudHdhc21jdXN0b21iYW5rQmFua1F1ZXJ5YWxsX2JhbGFuY2VzYWRkcmVzc2JhbGFuY2VBbGxCYWxhbmNlUmVzcG9uc2VXYXNtUXVlcnljb250cmFjdF9pbmZvY29udHJhY3RfYWRkcnJhd2tleXNtYXJ0bXNnYmxvY2t0cmFuc2FjdGlvbmNvbnRyYWN0aGVpZ2h0dGltZWNoYWluX2lkc2VuZGVyZnVuZHNpbmRleFF1ZXJpZXIgc3lzdGVtIGVycm9yOiAALQEQABYAAABRdWVyaWVyIGNvbnRyYWN0IGVycm9yOiBMARAAGAAAAFNlcmlhbGl6aW5nIFF1ZXJ5UmVxdWVzdDogAABsARAAGgAAAC9jb2RlL3BhY2thZ2VzL3N0ZC9zcmMvZXhwb3J0cy5ycwAAAJABEAAhAAAAggAAABoAAACQARAAIQAAAJgAAAAaAAAAkAEQACEAAABmAAAAGgAAAJABEAAhAAAArgAAABoAAACQARAAIQAAANcAAAAaAAAAQmFua01zZ2J1cm5zZW5kdG9fYWRkcmVzc1dhc21Nc2djbGVhcl9hZG1pbnVwZGF0ZV9hZG1pbmFkbWlubWlncmF0ZW5ld19jb2RlX2lkaW5zdGFudGlhdGVjb2RlX2lkbGFiZWxleGVjdXRlU3ViTXNnZ2FzX2xpbWl0UmVwbHlPbm5ldmVyc3VjY2Vzc2Vycm9yYWx3YXlzb2tFbXB0eUV2ZW50dHlwZWF0dHJpYnV0ZXNBdHRyaWJ1dGV2YWx1ZVJlc3BvbnNlZXZlbnRzAAoAAAAIAAAABAAAABcAAAAYAAAAGQAAAAwAAAAEAAAAGgAAABsAAAAcAAAAYSBEaXNwbGF5IGltcGxlbWVudGF0aW9uIHJldHVybmVkIGFuIGVycm9yIHVuZXhwZWN0ZWRseS9ydXN0Yy9hMTc4ZDAzMjJjZTIwZTMzZWFjMTI0NzU4ZTgzN2NiZDgwYTZmNjMzL2xpYnJhcnkvYWxsb2Mvc3JjL3N0cmluZy5ycwAANwMQAEsAAABPCQAADgAAAGNvc213YXNtX3N0ZDo6cXVlcnk6OmJhbms6OkFsbEJhbGFuY2VSZXNwb25zZWNvc213YXNtX3N0ZDo6dHlwZXM6Ok1lc3NhZ2VJbmZvaGFja2F0b206Om1zZzo6U3Vkb01zZ2hhY2thdG9tOjptc2c6OlJlY3Vyc2VSZXNwb25zZWhhY2thdG9tOjpzdGF0ZTo6U3RhdGVoYWNrYXRvbTo6bXNnOjpJbnN0YW50aWF0ZU1zZ2hhY2thdG9tOjptc2c6OkV4ZWN1dGVNc2djb3Ntd2FzbV9zdGQ6OnR5cGVzOjpFbnZjb3Ntd2FzbV9zdGQ6OnJlc3VsdHM6OmNvbnRyYWN0X3Jlc3VsdDo6Q29udHJhY3RSZXN1bHQ8Y29zbXdhc21fc3RkOjpyZXN1bHRzOjpyZXNwb25zZTo6UmVzcG9uc2U+aGFja2F0b206Om1zZzo6TWlncmF0ZU1zZ2Nvc213YXNtX3N0ZDo6cmVzdWx0czo6Y29udHJhY3RfcmVzdWx0OjpDb250cmFjdFJlc3VsdDxjb3Ntd2FzbV9zdGQ6OmJpbmFyeTo6QmluYXJ5PmhhY2thdG9tOjptc2c6OlZlcmlmaWVyUmVzcG9uc2VoYWNrYXRvbTo6bXNnOjpRdWVyeU1zZ2Nvc213YXNtX3N0ZDo6cXVlcnk6OlF1ZXJ5UmVxdWVzdDxjb3Ntd2FzbV9zdGQ6OnJlc3VsdHM6OmVtcHR5OjpFbXB0eT4ACgAAAAQAAAAEAAAAHQAAAB4AAAAfAAAAY2FsbGVkIGBSZXN1bHQ6OnVud3JhcF9lcnIoKWAgb24gYW4gYE9rYCB2YWx1ZQAAGQAAAAwAAAAEAAAAIAAAABkAAAAMAAAABAAAACEAAAAKAAAAAAAAAAEAAAAiAAAAY2FsbGVkIGBSZXN1bHQ6OnVud3JhcCgpYCBvbiBhbiBgRXJyYCB2YWx1ZQAjAAAAIAAAAAgAAAAJAAAACAAAAAAAAAAEAAAAAAAAAAEAAAAAAAAAbWlzc2luZyBmaWVsZCBgYJAGEAAPAAAAnwYQAAEAAABkdXBsaWNhdGUgZmllbGQgYAAAALAGEAARAAAAnwYQAAEAAAB1bmtub3duIHZhcmlhbnQgYGAsIGV4cGVjdGVkIAAAANQGEAARAAAA5QYQAAwAAABpbnZhbGlkIGJhc2U2NDogBAcQABAAAABpbnZhbGlkIFVpbnQ2NCAnJyAtIBwHEAAQAAAALAcQAAQAAABpbnZhbGlkIFVpbnQxMjggJwAAAEAHEAARAAAALAcQAAQAAAB1bGwACgAAAAgAAAAEAAAAJAAAAGNvbmZpZ2FjdGlvbnJlbGVhc2VkZXN0aW5hdGlvbvALqlN0YXRlcGFzc3dvcmRvdGhlcnNhbHRoYXNoX2VuY29kZWQgZXJyb3JlZDogAAAArwcQABYAAABzcmMvY29udHJhY3QucnMA0AcQAA8AAACIAAAACQAAAHRlc3Qua2V50AcQAA8AAACUAAAACQAAAG11c3Qgbm90IGJlIGVtcHR5AAAA0AcQAA8AAACcAAAAIQAAANAHEAAPAAAAnAAAABMAAABtZW1vcnkuZ3JvdyBmYWlsZWRUaGlzIHBhZ2UgaW50ZW50aW9uYWxseSBmYXVsdGVkAAAA0AcQAA8AAAC1AAAABQAAANAHEAAPAAAAvAAAACgAAABVbmV4cGVjdGVkIGVycm9yIGluIGRvX3VzZXJfZXJyb3JzX2luX2FwaV9jYWxsczogAAAAkAgQADEAAABibjloaHNzb21lbHR2aHpndnVxa3dqa3B3eG9qZnVpZ2x0d2VkYXl6eGxqdWNlZmlrdWllaWxsb3dhdGlja3NvaXN0cW95bm1nY25qMjE5YdAHEAAPAAAAyQAAADEAAADQBxAADwAAANcAAAAlAAAA0AcQAA8AAADjAAAAKQAAANAHEAAPAAAA7wAAACwAAABoZXJlIHdlIGdvIPCfmoBMZXQgdGhlaGFja2luZyBiZWdpblVuYXV0aG9yaXplZAB/CRAADAAAAHgGEAAAAAAAdmVyaWZpZXJiZW5lZmljaWFyeXN0ZWFsX2Z1bmRzAACvCRAACwAAAHJlY2lwaWVudGFyZ29uMmNwdV9sb29wc3RvcmFnZV9sb29wbWVtb3J5X2xvb3BhbGxvY2F0ZV9sYXJnZV9tZW1vcnlwYW5pY3VzZXJfZXJyb3JzX2luX2FwaV9jYWxsc4QHEAAHAAAAzQkQAAYAAADTCRAACAAAANsJEAAMAAAA5wkQAAsAAADyCRAAFQAAAAcKEAAFAAAADAoQABgAAABwYWdlc21lbV9jb3N0dGltZV9jb3N0UXVlcnlNc2dyZWN1cnNlZGVwdGh3b3Jrb3RoZXJfYmFsYW5jZQCcCRAACAAAAJIKEAANAAAAggoQAAcAAABWZXJpZmllclJlc3BvbnNlUmVjdXJzZVJlc3BvbnNlaGFzaGVkZnVuZGVyAEHwlcAAC9EHYXR0ZW1wdCB0byBhZGQgd2l0aCBvdmVyZmxvd0JhZCBoYXNoIGxlbmd0aDogAAAADAsQABEAAAAvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmxha2UyYl9zaW1kLTAuNS4xMS9zcmMvbGliLnJzKAsQAFgAAADvAAAACQAAACoAAAAkAAAABAAAACsAAAAvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvY3Jvc3NiZWFtLXV0aWxzLTAuOC41L3NyYy90aHJlYWQucnMAAACgCxAAXQAAALgBAAAkAAAAZmFpbGVkIHRvIHNwYXduIHNjb3BlZCB0aHJlYWQAAACgCxAAXQAAAP0AAAAOAAAAoAsQAF0AAACqAAAACgAAAKALEABdAAAArQAAACwAAAAAY2Fubm90IHJlY3Vyc2l2ZWx5IGFjcXVpcmUgbXV0ZXgAAABhDBAAIAAAAC9ydXN0Yy9hMTc4ZDAzMjJjZTIwZTMzZWFjMTI0NzU4ZTgzN2NiZDgwYTZmNjMzL2xpYnJhcnkvc3RkL3NyYy9zeXMvd2FzbS8uLi91bnN1cHBvcnRlZC9tdXRleC5yc4wMEABgAAAAFwAAAAkAAAAvcnVzdGMvYTE3OGQwMzIyY2UyMGUzM2VhYzEyNDc1OGU4MzdjYmQ4MGE2ZjYzMy9saWJyYXJ5L3N0ZC9zcmMvdGhyZWFkL21vZC5ycwAAAPwMEABNAAAA9AQAABwAAAAsAAAAFAAAAAQAAAAtAAAALgAAAAgAAAAEAAAALwAAADAAAAAvcnVzdGMvYTE3OGQwMzIyY2UyMGUzM2VhYzEyNDc1OGU4MzdjYmQ4MGE2ZjYzMy9saWJyYXJ5L2NvcmUvc3JjL3NsaWNlL21vZC5ycwAAAIANEABNAAAA8gsAAA0AAABjYWxsZWQgYE9wdGlvbjo6dW53cmFwKClgIG9uIGEgYE5vbmVgIHZhbHVlADEAAAAIAAAABAAAADIAAAAzAAAAFAAAAAQAAAA0AAAAY2FsbGVkIGBSZXN1bHQ6OnVud3JhcCgpYCBvbiBhbiBgRXJyYCB2YWx1ZQA1AAAACAAAAAQAAAA2AAAANQAAAAgAAAAEAAAANgAAADUAAAAIAAAABAAAADYAAAAuAAAABAAAAAQAAAA3AAAABAAAAAAAAABhdHRlbXB0IHRvIG11bHRpcGx5IHdpdGggb3ZlcmZsb3cAQdCdwAALwQRhdHRlbXB0IHRvIHN1YnRyYWN0IHdpdGggb3ZlcmZsb3cvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTMuMC9zcmMvZW5jb2RlLnJzSW52YWxpZCBVVEY4AADxDhAAVQAAADQAAAAcAAAAaW50ZWdlciBvdmVyZmxvdyB3aGVuIGNhbGN1bGF0aW5nIGJ1ZmZlciBzaXplAAAA8Q4QAFUAAAAvAAAAEQAAAFBvaXNvbkVycm9yAC4AAAAIAAAABAAAADgAAAAvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvcnVzdC1hcmdvbjItMC44LjMvc3JjL2NvbnRleHQucnMAAMAPEABaAAAAZQAAAB0AAAAvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvcnVzdC1hcmdvbjItMC44LjMvc3JjL2NvcmUucnMALBAQAFcAAAAsAAAAIAAAACwQEABXAAAALAAAABkAAAAsEBAAVwAAAC4AAAAiAAAALBAQAFcAAAAvAAAAFwAAACwQEABXAAAAsgAAABAAAAAsEBAAVwAAALYAAAAQAAAALBAQAFcAAAABAQAAGwAAACwQEABXAAAAAgEAAAsAAAAsEBAAVwAAAAUBAAAeAEGgosAAC4UJYXR0ZW1wdCB0byBjYWxjdWxhdGUgdGhlIHJlbWFpbmRlciB3aXRoIGEgZGl2aXNvciBvZiB6ZXJvAAAALBAQAFcAAAAJAQAACQAAACwQEABXAAAABwEAAAkAAAAsEBAAVwAAABABAAAbAAAALBAQAFcAAAAbAQAAGwAAACwQEABXAAAAIwEAAA0AAAAsEBAAVwAAAC4BAAAeAAAALBAQAFcAAAAwAQAAHwAAACwQEABXAAAAMQEAAB4AAAAsEBAAVwAAAHABAAAaAAAALBAQAFcAAABwAQAADQAAACwQEABXAAAAdAEAABYAAAAsEBAAVwAAAI8BAAANAAAALBAQAFcAAACTAQAADQAAACwQEABXAAAAkQEAAA0AAAAsEBAAVwAAAIYBAAANAAAALBAQAFcAAACIAQAADQAAACwQEABXAAAAgwEAAA0AAAAsEBAAVwAAAJkBAAAZAAAALBAQAFcAAACgAQAADQAAACwQEABXAAAAqAEAAAYAAAAsEBAAVwAAALABAAAFAAAAjAwQAAAAAAAkJHY9JG09LHQ9LHA9AAAAtBIQAAEAAAC1EhAAAwAAALgSEAADAAAAuxIQAAMAAAC+EhAAAwAAALQSEAABAAAAtBIQAAEAAABEZWNvZGluZyBmYWlsZWRUaGVyZSBpcyBubyBzdWNoIHZlcnNpb24gb2YgQXJnb24yVGhlcmUgaXMgbm8gc3VjaCB0eXBlIG9mIEFyZ29uMlRvbyBtYW55IGxhbmVzVG9vIGZldyBsYW5lc01lbW9yeSBjb3N0IGlzIHRvbyBsYXJnZU1lbW9yeSBjb3N0IGlzIHRvbyBzbWFsbFRpbWUgY29zdCBpcyB0b28gbGFyZ2VUaW1lIGNvc3QgaXMgdG9vIHNtYWxsU2VjcmV0IGlzIHRvbyBsb25nU2VjcmV0IGlzIHRvbyBzaG9ydEFzc29jaWF0ZWQgZGF0YSBpcyB0b28gbG9uZ0Fzc29jaWF0ZWQgZGF0YSBpcyB0b28gc2hvcnRTYWx0IGlzIHRvbyBsb25nU2FsdCBpcyB0b28gc2hvcnRQYXNzd29yZCBpcyB0b28gbG9uZ1Bhc3N3b3JkIGlzIHRvbyBzaG9ydE91dHB1dCBpcyB0b28gbG9uZ091dHB1dCBpcyB0b28gc2hvcnQvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvcnVzdC1hcmdvbjItMC44LjMvc3JjL21lbW9yeS5ycwCOFBAAWQAAAB8AAAAVAAAAYXJnb24yaWRhcmdvbjJpYXJnb24yZABjYW5ub3QgcmVjdXJzaXZlbHkgYWNxdWlyZSBtdXRleAAPFRAAIAAAAC9ydXN0Yy9hMTc4ZDAzMjJjZTIwZTMzZWFjMTI0NzU4ZTgzN2NiZDgwYTZmNjMzL2xpYnJhcnkvc3RkL3NyYy9zeXMvd2FzbS8uLi91bnN1cHBvcnRlZC9tdXRleC5yczgVEABgAAAAFwAAAAkAQbCrwAAL8QJhdHRlbXB0IHRvIGFkZCB3aXRoIG92ZXJmbG93Y2FsbGVkIGBSZXN1bHQ6OnVud3JhcCgpYCBvbiBhbiBgRXJyYCB2YWx1ZQA5AAAACAAAAAQAAAA6AAAAOwAAAAQAAAAEAAAAPAAAAFBvaXNvbkVycm9yL3Vzci9sb2NhbC9jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2Nyb3NzYmVhbS11dGlscy0wLjguNS9zcmMvc3luYy93YWl0X2dyb3VwLnJzAAAAIxYQAGYAAABpAAAAJQAAACMWEABmAAAAcAAAACwAAAAjFhAAZgAAAHIAAAAsAAAAIxYQAGYAAAB5AAAAMQAAACMWEABmAAAAegAAAAkAAAAAAAAAYXR0ZW1wdCB0byBzdWJ0cmFjdCB3aXRoIG92ZXJmbG93AAAAIxYQAGYAAACEAAAAMQAAACMWEABmAAAAhQAAAAkAQbCuwAAL8QRhdHRlbXB0IHRvIGFkZCB3aXRoIG92ZXJmbG93L3J1c3RjL2ExNzhkMDMyMmNlMjBlMzNlYWMxMjQ3NThlODM3Y2JkODBhNmY2MzMvbGlicmFyeS9jb3JlL3NyYy9zbGljZS9tb2QucnMAAABMFxAATQAAAPILAAANAAAAAAAAAGF0dGVtcHQgdG8gc3VidHJhY3Qgd2l0aCBvdmVyZmxvdy91c3IvbG9jYWwvY2FyZ28vcmVnaXN0cnkvc3JjL2dpdGh1Yi5jb20tMWVjYzYyOTlkYjllYzgyMy9ibGFrZTJiX3NpbWQtMC41LjExL3NyYy9wb3J0YWJsZS5ycwAA0RcQAF0AAACEAAAABQAAANEXEABdAAAAlgAAABUAAADRFxAAXQAAAKQAAAAJAAAAL3Vzci9sb2NhbC9jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2JsYWtlMmJfc2ltZC0wLjUuMTEvc3JjL2xpYi5yc2AYEABYAAAAqQEAAB0AAABgGBAAWAAAAKoBAAAoAAAAYBgQAFgAAACqAQAACQAAAGAYEABYAAAAqwEAAAkAAABgGBAAWAAAAMsBAAAJAAAAYBgQAFgAAADOAQAAEgAAAGAYEABYAAAA5QEAAA4AAABgGBAAWAAAAE4CAAAKAAAAL3J1c3RjL2ExNzhkMDMyMmNlMjBlMzNlYWMxMjQ3NThlODM3Y2JkODBhNmY2MzMvbGlicmFyeS9jb3JlL3NyYy9pdGVyL2FkYXB0ZXJzL2VudW1lcmF0ZS5ycwA4GRAAWwAAAC4AAAAJAEGws8AAC+EIYXR0ZW1wdCB0byBhZGQgd2l0aCBvdmVyZmxvdwAAAABhdHRlbXB0IHRvIG11bHRpcGx5IHdpdGggb3ZlcmZsb3cAAABJAAAACAAAAAQAAABKAAAASwAAAEwAAAAMAAAABAAAAE0AAABOAAAATwAAAGEgRGlzcGxheSBpbXBsZW1lbnRhdGlvbiByZXR1cm5lZCBhbiBlcnJvciB1bmV4cGVjdGVkbHkvcnVzdGMvYTE3OGQwMzIyY2UyMGUzM2VhYzEyNDc1OGU4MzdjYmQ4MGE2ZjYzMy9saWJyYXJ5L2FsbG9jL3NyYy9zdHJpbmcucnMAAFcaEABLAAAATwkAAA4AAABjb3Ntd2FzbV9zdGQ6OnJlc3VsdHM6OnN5c3RlbV9yZXN1bHQ6OlN5c3RlbVJlc3VsdDxjb3Ntd2FzbV9zdGQ6OnJlc3VsdHM6OmNvbnRyYWN0X3Jlc3VsdDo6Q29udHJhY3RSZXN1bHQ8Y29zbXdhc21fc3RkOjpiaW5hcnk6OkJpbmFyeT4+SQAAAAQAAAAEAAAAUAAAAFEAAABSAAAASQAAAAQAAAAEAAAAUwAAAEwAAAAUAAAABAAAADQAAABJAAAAAAAAAAEAAAAiAAAAL3J1c3RjL2ExNzhkMDMyMmNlMjBlMzNlYWMxMjQ3NThlODM3Y2JkODBhNmY2MzMvbGlicmFyeS9jb3JlL3NyYy9pdGVyL3RyYWl0cy9hY2N1bS5ycwAAAIQbEABVAAAAjwAAAAEAAABpbnRlcm5hbCBlcnJvcjogZW50ZXJlZCB1bnJlYWNoYWJsZSBjb2RlOiAAAOwbEAAqAAAAAQAAAAAAAABtaXNzaW5nIGZpZWxkIGBgKBwQAA8AAAA3HBAAAQAAAGR1cGxpY2F0ZSBmaWVsZCBgAAAASBwQABEAAAA3HBAAAQAAAHVua25vd24gdmFyaWFudCBgYCwgZXhwZWN0ZWQgAAAAbBwQABEAAAB9HBAADAAAAEkAAAAEAAAABAAAAFQAAABJAAAABAAAAAQAAABVAAAAL3Vzci9sb2NhbC9jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2Jhc2U2NC0wLjEzLjAvc3JjL2RlY29kZS5ycwAAALwcEABVAAAA0gEAAB8AAAC8HBAAVQAAANgBAAAfAAAAvBwQAFUAAADbAQAADQAAALwcEABVAAAA4QEAAB8AAAC8HBAAVQAAAOQBAAANAAAAvBwQAFUAAADqAQAAHwAAALwcEABVAAAA7QEAAA0AAAC8HBAAVQAAAPMBAAAfAAAAvBwQAFUAAAD2AQAADQAAALwcEABVAAAA/AEAAB8AAAC8HBAAVQAAAP8BAAANAAAAvBwQAFUAAAAFAgAAHwAAALwcEABVAAAACAIAAA0AAAC8HBAAVQAAAA4CAAAfAAAAvBwQAFUAAAARAgAADQAAALwcEABVAAAAbgAAAC8AQaC8wAALlSdhdHRlbXB0IHRvIHN1YnRyYWN0IHdpdGggb3ZlcmZsb3cAAAC8HBAAVQAAAAMBAAA3AAAAvBwQAFUAAAADAQAAJAAAALwcEABVAAAABAEAAD4AAAC8HBAAVQAAAAQBAAApAAAAvBwQAFUAAAAhAQAAEQAAALwcEABVAAAAKgEAACkAAAC8HBAAVQAAACoBAAAWAAAAvBwQAFUAAAAuAQAAKQAAALwcEABVAAAALgEAACgAAAC8HBAAVQAAAC0BAAAaAAAAvBwQAFUAAAAzAQAAEQAAALwcEABVAAAAQQEAAA4AAAC8HBAAVQAAAEQBAAAnAAAAvBwQAFUAAABEAQAAEgAAALwcEABVAAAARwEAAAkAAAC8HBAAVQAAAFgBAAATAAAAvBwQAFUAAABmAQAAKQAAALwcEABVAAAAeAEAAA0AAAC8HBAAVQAAAIIBAAARAAAAvBwQAFUAAACKAQAAFQAAALwcEABVAAAAjgEAADEAAABJbXBvc3NpYmxlOiBtdXN0IG9ubHkgaGF2ZSAwIHRvIDggaW5wdXQgYnl0ZXMgaW4gbGFzdCBjaHVuaywgd2l0aCBubyBpbnZhbGlkIGxlbmd0aHOUHxAAVAAAALwcEABVAAAAnQEAAA4AAAC8HBAAVQAAAKgBAAANAAAAvBwQAFUAAACxAQAACQAAAE92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgb3V0cHV0IGJ1ZmZlciBsZW5ndGgAALwcEABVAAAAlgAAAAoAAAAvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTMuMC9zcmMvZW5jb2RlLnJzSW52YWxpZCBVVEY4AAAAYCAQAFUAAAA0AAAAHAAAAGludGVnZXIgb3ZlcmZsb3cgd2hlbiBjYWxjdWxhdGluZyBidWZmZXIgc2l6ZQAAAGAgEABVAAAALwAAABEAAABJAAAACAAAAAQAAABWAAAAIBwQAAAAAABpbnZhbGlkIGJhc2U2NDogLCEQABAAAABVbnN1cHBvcnRlZCBxdWVyeSB0eXBlOiBEIRAAGAAAAFVua25vd24gc3lzdGVtIGVycm9yZCEQABQAAABObyBzdWNoIGNvbnRyYWN0OiAAAIAhEAASAAAAQ2Fubm90IHBhcnNlIHJlc3BvbnNlOiAgaW46IJwhEAAXAAAAsyEQAAUAAABDYW5ub3QgcGFyc2UgcmVxdWVzdDogAADIIRAAFgAAALMhEAAFAAAAL2NvZGUvcGFja2FnZXMvc3RkL3NyYy9zZWN0aW9ucy5ycwAA8CEQACIAAAAaAAAAEAAAAPAhEAAiAAAAGgAAAAUAAABUTDtEUjogVmFsdWUgbXVzdCBub3QgYmUgZW1wdHkgaW4gU3RvcmFnZTo6c2V0IGJ1dCBpbiBtb3N0IGNhc2VzIHlvdSBjYW4gdXNlIFN0b3JhZ2U6OnJlbW92ZSBpbnN0ZWFkLiBMb25nIHN0b3J5OiBHZXR0aW5nIGVtcHR5IHZhbHVlcyBmcm9tIHN0b3JhZ2UgaXMgbm90IHdlbGwgc3VwcG9ydGVkIGF0IHRoZSBtb21lbnQuIFNvbWUgb2Ygb3VyIGludGVybmFsIGludGVyZmFjZXMgY2Fubm90IGRpZmZlcmVudGlhdGUgYmV0d2VlbiBhIG5vbi1leGlzdGVudCBrZXkgYW5kIGFuIGVtcHR5IHZhbHVlLiBSaWdodCBub3csIHlvdSBjYW5ub3QgcmVseSBvbiB0aGUgYmVoYXZpb3VyIG9mIGVtcHR5IHZhbHVlcy4gVG8gcHJvdGVjdCB5b3UgZnJvbSB0cm91YmxlIGxhdGVyIG9uLCB3ZSBzdG9wIGhlcmUuIFNvcnJ5IGZvciB0aGUgaW5jb252ZW5pZW5jZSEgV2UgaGlnaGx5IHdlbGNvbWUgeW91IHRvIGNvbnRyaWJ1dGUgdG8gQ29zbVdhc20sIG1ha2luZyB0aGlzIG1vcmUgc29saWQgb25lIHdheSBvciB0aGUgb3RoZXIuL2NvZGUvcGFja2FnZXMvc3RkL3NyYy9pbXBvcnRzLnJzAAAAPCQQACEAAABXAAAADQAAAGlucHV0IHRvbyBsb25nIGZvciBhZGRyX3ZhbGlkYXRlYWRkcl92YWxpZGF0ZSBlcnJvcmVkOiAAkCQQABcAAABpbnB1dCB0b28gbG9uZyBmb3IgYWRkcl9jYW5vbmljYWxpemVhZGRyX2Nhbm9uaWNhbGl6ZSBlcnJvcmVkOiAA1CQQABsAAABhZGRyX2h1bWFuaXplIGVycm9yZWQ6IAD4JBAAFwAAAE1lc3NhZ2VUb29Mb25nIG11c3Qgbm90IGhhcHBlbi4gVGhpcyBpcyBhIGJ1ZyBpbiB0aGUgVk0uPCQQACEAAAD0AAAAEgAAADwkEAAhAAAAEQEAABIAAABJbnZhbGlkSGFzaEZvcm1hdCBtdXN0IG5vdCBoYXBwZW4uIFRoaXMgaXMgYSBidWcgaW4gdGhlIFZNLgA8JBAAIQAAACsBAAASAAAARXJyb3IgY29kZSAyIHVudXNlZCBzaW5jZSBDb3NtV2FzbSAwLjE1LiBUaGlzIGlzIGEgYnVnIGluIHRoZSBWTS4AAAA8JBAAIQAAACoBAAASAAAAPCQQACEAAABLAQAAEgAAADwkEAAhAAAASgEAABIAAAAvY29kZS9wYWNrYWdlcy9zdGQvc3JjL21lbW9yeS5yc1JlZ2lvbiBwb2ludGVyIGlzIG51bGwAADAmEAAgAAAAOQAAAAUAAABSZWdpb24gc3RhcnRzIGF0IG51bGwgcG9pbnRlcgAAADAmEAAgAAAAPwAAAAUAAABBZGRyQ2Fub25pY2FsQWRkckJpbmFyeQBJAAAABAAAAAQAAABXAAAAVW5rbm93biBlcnJvcjogANAmEAAPAAAASW52YWxpZCByZWNvdmVyeSBwYXJhbWV0ZXIuIFN1cHBvcnRlZCB2YWx1ZXM6IDAgYW5kIDEuAADoJhAANgAAAEludmFsaWQgc2lnbmF0dXJlIGZvcm1hdCgnEAAYAAAASW52YWxpZCBoYXNoIGZvcm1hdABIJxAAEwAAAFVua25vd25FcnJlcnJvcl9jb2RlSQAAAAQAAAAEAAAAWAAAAEludmFsaWRSZWNvdmVyeVBhcmFtSW52YWxpZFNpZ25hdHVyZUZvcm1hdEludmFsaWRIYXNoRm9ybWF0Q29udmVyc2lvbiBlcnJvcjogAAAAwycQABIAAABEaXZpZGUgYnkgemVybzog4CcQABAAAABPdmVyZmxvdzogAAD4JxAACgAAAEVycm9yIHNlcmlhbGl6aW5nIHR5cGUgOiAAAAAMKBAAFwAAACMoEAACAAAARXJyb3IgcGFyc2luZyBpbnRvIHR5cGUgOCgQABgAAAAjKBAAAgAAACBub3QgZm91bmQAACAcEAAAAAAAYCgQAAoAAABDYW5ub3QgZGVjb2RlIFVURjggYnl0ZXMgaW50byBzdHJpbmc6IAAAfCgQACYAAABJbnZhbGlkIGRhdGEgc2l6ZTogZXhwZWN0ZWQ9IGFjdHVhbD2sKBAAHAAAAMgoEAAIAAAASW52YWxpZCBCYXNlNjQgc3RyaW5nOiAA4CgQABcAAABHZW5lcmljIGVycm9yOiAAACkQAA8AAABSZWNvdmVyIHB1YmtleSBlcnJvcjogAAAYKRAAFgAAAFZlcmlmaWNhdGlvbiBlcnJvcjogOCkQABQAAABDb252ZXJzaW9uT3ZlcmZsb3dzb3VyY2VJAAAABAAAAAQAAABZAAAARGl2aWRlQnlaZXJvSQAAAAQAAAAEAAAAWgAAAE92ZXJmbG93SQAAAAQAAAAEAAAAWwAAAFNlcmlhbGl6ZUVycnNvdXJjZV90eXBlbXNnUGFyc2VFcnJ0YXJnZXRfdHlwZU5vdEZvdW5ka2luZEludmFsaWRVdGY4SW52YWxpZERhdGFTaXplZXhwZWN0ZWQASQAAAAQAAAAEAAAAXAAAAGFjdHVhbEludmFsaWRCYXNlNjRHZW5lcmljRXJyUmVjb3ZlclB1YmtleUVycgAAAEkAAAAEAAAABAAAAF0AAABWZXJpZmljYXRpb25FcnIASQAAAAQAAAAEAAAAXgAAAFNobFNoclBvd011bFN1YkFkZENhbm5vdCAgd2l0aCAgYW5kII4qEAAHAAAAlSoQAAYAAACbKhAABQAAAE92ZXJmbG93RXJyb3JvcGVyYXRpb24AAEkAAAAEAAAABAAAAD8AAABvcGVyYW5kMW9wZXJhbmQyQ29udmVyc2lvbk92ZXJmbG93RXJyb3IASQAAAAQAAAAEAAAAXwAAAHZhbHVlQ2Fubm90IGRldmlkZSAgYnkgemVybwAdKxAADgAAACsrEAAIAAAARGl2aWRlQnlaZXJvRXJyb3JvcGVyYW5kaW52YWxpZF9yZXF1ZXN0aW52YWxpZF9yZXNwb25zZW5vX3N1Y2hfY29udHJhY3R1bmtub3dudW5zdXBwb3J0ZWRfcmVxdWVzdAAAAFwrEAAPAAAAaysQABAAAAB7KxAAEAAAAIsrEAAHAAAAkisQABMAAABhZGRyZXJyb3JyZXNwb25zZXJlcXVlc3RJbnZhbGlkIHB1YmxpYyBrZXkgZm9ybWF0AAAA6CsQABkAAABHZW5lcmljIGVycm9yAAAADCwQAA0AAABCYXRjaCBlcnJvcgAkLBAACwAAAEludmFsaWRQdWJrZXlGb3JtYXRCYXRjaEVycm9rAAAAUywQAAIAAADUKxAABQAAAGIAAAAIAAAABAAAAGMAAABkAAAAYgAAAAgAAAAEAAAAZQAAAHwsEAAAAAAASlNPTiBoYXMgYSBjb21tYSBhZnRlciB0aGUgbGFzdCB2YWx1ZSBpbiBhbiBhcnJheSBvciBtYXAuSlNPTiBoYXMgbm9uLXdoaXRlc3BhY2UgdHJhaWxpbmcgY2hhcmFjdGVycyBhZnRlciB0aGUgdmFsdWUuRm91bmQgYSBsb25lIHN1cnJvZ2F0ZSwgd2hpY2ggY2FuIGV4aXN0IGluIEpTT04gYnV0IGNhbm5vdCBiZSBlbmNvZGVkIHRvIFVURi04Lk9iamVjdCBrZXkgaXMgbm90IGEgc3RyaW5nLkludmFsaWQgdW5pY29kZSBjb2RlIHBvaW50LkludmFsaWQgdHlwZUludmFsaWQgbnVtYmVyLkludmFsaWQgZXNjYXBlIHNlcXVlbmNlLkV4cGVjdGVkIHRoaXMgY2hhcmFjdGVyIHRvIHN0YXJ0IGEgSlNPTiB2YWx1ZS5FeHBlY3RlZCB0byBwYXJzZSBlaXRoZXIgYSBgdHJ1ZWAsIGBmYWxzZWAsIG9yIGEgYG51bGxgLkV4cGVjdGVkIHRoaXMgY2hhcmFjdGVyIHRvIGJlIGVpdGhlciBhIGAnLCdgIG9yIGEgYCd9J2AuRXhwZWN0ZWQgYSBsb3cgc3Vycm9nYXRlIChEQzAw4oCTREZGRikuRXhwZWN0ZWQgdGhpcyBjaGFyYWN0ZXIgdG8gYmUgZWl0aGVyIGEgYCcsJ2Agb3JhIGAnXSdgLkV4cGVjdGVkIGEgaGlnaCBzdXJyb2dhdGUgKEQ4MDDigJNEQkZGKS5FeHBlY3RlZCB0aGlzIGNoYXJhY3RlciB0byBiZSBhIGAnOidgLkVPRiB3aGlsZSBwYXJzaW5nIGEgSlNPTiB2YWx1ZS5FT0Ygd2hpbGUgcGFyc2luZyBhIHN0cmluZy5FT0Ygd2hpbGUgcGFyc2luZyBhbiBvYmplY3QuRU9GIHdoaWxlIHBhcnNpbmcgYSBsaXN0LkNvbnRyb2wgY2hhcmFjdGVyIGZvdW5kIGluIHN0cmluZy4vdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvc2VyZGUtanNvbi13YXNtLTAuMy4xL3NyYy9kZS91bmVzY2FwZS5ycwAAnC8QAGIAAAAlAAAAFQAAAGF0dGVtcHQgdG8gYWRkIHdpdGggb3ZlcmZsb3ecLxAAYgAAADMAAAApAAAAAAAAAGF0dGVtcHQgdG8gc3VidHJhY3Qgd2l0aCBvdmVyZmxvd05vbi1oZXggQVNDSUkgY2hhcmFjdGVyIGZvdW5kAACcLxAAYgAAAJkAAAAOAAAAL3Vzci9sb2NhbC9jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL3NlcmRlLWpzb24td2FzbS0wLjMuMS9zcmMvZGUvbW9kLnJzAAAAkDAQAF0AAAAkAAAACQAAAJAwEABdAAAAfQAAACIAAACQMBAAXQAAAIEAAAAsAAAAQnVmZmVyIGlzIGZ1bGwAACAxEAAOAAAAL3Vzci9sb2NhbC9jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL3NlcmRlLWpzb24td2FzbS0wLjMuMS9zcmMvc2VyL21vZC5ycwAAODEQAF4AAADJAAAACQAAADgxEABeAAAAzgAAAAkAQcDjwAALIWF0dGVtcHQgdG8gc3VidHJhY3Qgd2l0aCBvdmVyZmxvdwBB8OPAAAuBGmF0dGVtcHQgdG8gYWRkIHdpdGggb3ZlcmZsb3cvdXNyL2xvY2FsL2NhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvYmFzZTY0LTAuMTMuMC9zcmMvZW5jb2RlLnJzAAAADDIQAFUAAACSAAAAJwAAAHVzaXplIG92ZXJmbG93IHdoZW4gY2FsY3VsYXRpbmcgYjY0IGxlbmd0aAAADDIQAFUAAACZAAAACgAAAAwyEABVAAAAtgAAADMAAAAMMhAAVQAAALYAAAAgAAAADDIQAFUAAAC3AAAAOgAAAAwyEABVAAAAtwAAACUAAAAMMhAAVQAAAPcAAAAYAAAADDIQAFUAAAD8AAAALwAAAAwyEABVAAAA/AAAABwAAAAMMhAAVQAAAP0AAAA2AAAADDIQAFUAAAD9AAAAIQAAAAwyEABVAAAAEwEAAC4AAAAMMhAAVQAAABMBAAAJAAAADDIQAFUAAAAUAQAACQAAAAwyEABVAAAACwEAAC4AAAAMMhAAVQAAAAsBAAAJAAAADDIQAFUAAAANAQAADwAAAAwyEABVAAAADAEAAAkAAAAMMhAAVQAAAA8BAAAJAAAADDIQAFUAAAARAQAACQAAAGludGVybmFsIGVycm9yOiBlbnRlcmVkIHVucmVhY2hhYmxlIGNvZGU6IAAA0DMQACoAAABJbXBvc3NpYmxlIHJlbWFpbmRlcgQ0EAAUAAAADDIQAFUAAAAqAQAAFgAAAAwyEABVAAAAOwEAAAkAAABJbnZhbGlkIGxhc3Qgc3ltYm9sICwgb2Zmc2V0IC4AAEA0EAAUAAAAVDQQAAkAAABdNBAAAQAAAEVuY29kZWQgdGV4dCBjYW5ub3QgaGF2ZSBhIDYtYml0IHJlbWFpbmRlci4AeDQQACsAAABJbnZhbGlkIGJ5dGUgAAAArDQQAA0AAABUNBAACQAAAF00EAABAAAAT3ZlcmZsb3cgd2hlbiBjYWxjdWxhdGluZyBudW1iZXIgb2YgY2h1bmtzIGluIGlucHV0L3Vzci9sb2NhbC9jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2Jhc2U2NC0wLjEzLjAvc3JjL2RlY29kZS5ycwc1EABVAAAAvAAAAAoAAAAhIiMkJSYnKCkqKywtMDEyMzQ1Njc4OUBBQkNERUZHSElKS0xNTlBRUlNUVVZYWVpbYGFiY2RlaGlqa2xtcHFyQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLC4vQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkuLzAxMjM0NTY3ODlBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODktX0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5Ky////////////////////////////////////////////8AAQIDBAUGBwgJCgsM//8NDg8QERITFBUW////////FxgZGhscHR4fICEiIyQl/yYnKCkqKyz/LS4vMP////8xMjM0NTb//zc4OTo7PP//PT4//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////z4/////NDU2Nzg5Ojs8Pf////////8AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGf///////xobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AATY3ODk6Ozw9Pj//////////AgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhv///////8cHR4fICEiIyQlJicoKSorLC0uLzAxMjM0Nf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAECAwQFBgcICQoL/////////wwNDg8QERITFBUWFxgZGhscHR4fICEiIyQl////////JicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Pv//NDU2Nzg5Ojs8Pf////////8AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGf////8//xobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIz//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8+////PzQ1Njc4OTo7PD3/////////AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBn///////8aGxwdHh8gISIjJCUmJygpKissLS4vMDEyM/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+sNhAAbDYQACw2EADsNRAArDUQAGw1EADsOxAA7DoQAOw5EADsOBAA7DcQAOw2EABqAAAACAAAAAQAAABrAAAAbAAAAGoAAAAIAAAABAAAAG0AAABgb25lIG9mIEE9EAAHAAAALCAAAFA9EAACAAAAQD0QAAEAAABAPRAAAQAAAGAgb3IgYAAAQD0QAAEAAABsPRAABgAAAEA9EAABAAAAL3Vzci9sb2NhbC9jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL3NlcmRlLTEuMC4xMjYvc3JjL2RlL21vZC5yc2V4cGxpY2l0IHBhbmljAIw9EABVAAAA1AgAABIAAABhdHRlbXB0IHRvIGFkZCB3aXRoIG92ZXJmbG93L3Vzci9sb2NhbC9jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL2Jsb2NrLWJ1ZmZlci0wLjkuMC9zcmMvbGliLnJzABw+EABXAAAAhAAAAAkAAAAcPhAAVwAAAIUAAAAJAAAAHD4QAFcAAACHAAAAFwAAABw+EABXAAAAiwAAABsAAAAcPhAAVwAAADoAAAAjAAAAHD4QAFcAAAA6AAAADQAAABw+EABXAAAAOwAAAA0AAAAcPhAAVwAAAEEAAAANAEGA/sAAC6cbYXR0ZW1wdCB0byBzdWJ0cmFjdCB3aXRoIG92ZXJmbG93AAAAHD4QAFcAAADWAAAACQAAAC9ydXN0Yy9hMTc4ZDAzMjJjZTIwZTMzZWFjMTI0NzU4ZTgzN2NiZDgwYTZmNjMzL2xpYnJhcnkvY29yZS9zcmMvc2xpY2UvbW9kLnJzAAAAND8QAE0AAADyCwAADQAAAGfmCWqFrme7cvNuPDr1T6V/Ug5RjGgFm6vZgx8ZzeBbL3Vzci9sb2NhbC9jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL3NoYTItMC45LjUvc3JjL3NoYTI1Ni5ycwAAtD8QAFIAAAAfAAAACQAAAG8AAAAEAAAABAAAAHAAAABxAAAAcgAAAG8AAAAEAAAABAAAAHMAAABhbHJlYWR5IGJvcnJvd2VkYWxyZWFkeSBtdXRhYmx5IGJvcnJvd2VkbwAAAAAAAAABAAAAdAAAAGNhbGxlZCBgT3B0aW9uOjp1bndyYXAoKWAgb24gYSBgTm9uZWAgdmFsdWUAbwAAAAAAAAABAAAAdQAAAG8AAAAAAAAAAQAAAHYAAAB3AAAAEAAAAAQAAAB4AAAAbwAAAAQAAAAEAAAAeQAAAG8AAAAEAAAABAAAAHoAAAABAAAAAAAAAGxpYnJhcnkvc3RkL3NyYy90aHJlYWQvbW9kLnJzZmFpbGVkIHRvIGdlbmVyYXRlIHVuaXF1ZSB0aHJlYWQgSUQ6IGJpdHNwYWNlIGV4aGF1c3RlZPxAEAAdAAAA8wMAABEAAAD8QBAAHQAAAPkDAAAqAAAAdGhyZWFkIG5hbWUgbWF5IG5vdCBjb250YWluIGludGVyaW9yIG51bGwgYnl0ZXMA/EAQAB0AAAAzBAAAKgAAAABFcnJvcmtpbmQAAG8AAAABAAAAAQAAAHsAAABtZXNzYWdlAG8AAAAIAAAABAAAAHwAAABLaW5kT3Njb2RlAABvAAAABAAAAAQAAAB9AAAAfgAAAAwAAAAEAAAAfwAAAGxpYnJhcnkvc3RkL3NyYy9zeXNfY29tbW9uL3RocmVhZF9pbmZvLnJzAAAAEEIQACkAAAAoAAAAJAAAAGFzc2VydGlvbiBmYWlsZWQ6IGMuYm9ycm93KCkuaXNfbm9uZSgpAAAQQhAAKQAAACgAAAAaAAAAEEIQACkAAAApAAAAIgAAAGxpYnJhcnkvc3RkL3NyYy9wYW5pY2tpbmcucnOUQhAAHAAAAAECAAAfAAAAlEIQABwAAAACAgAAHgAAAIAAAAAQAAAABAAAAIEAAACCAAAAbwAAAAgAAAAEAAAAgwAAAIQAAAB+AAAADAAAAAQAAACFAAAAbwAAAAgAAAAEAAAAhgAAAG8AAAAIAAAABAAAAIcAAACIAAAAVW5zdXBwb3J0ZWROdWxFcnJvcgBvAAAABAAAAAQAAACJAAAAZXJyb3JDdXN0b20AbwAAAAQAAAAEAAAAigAAAG8AAAAEAAAABAAAAIsAAABPdXRPZk1lbW9yeVVuZXhwZWN0ZWRFb2ZPdGhlckludGVycnVwdGVkV3JpdGVaZXJvVGltZWRPdXRJbnZhbGlkRGF0YUludmFsaWRJbnB1dFdvdWxkQmxvY2tBbHJlYWR5RXhpc3RzQnJva2VuUGlwZUFkZHJOb3RBdmFpbGFibGVBZGRySW5Vc2VOb3RDb25uZWN0ZWRDb25uZWN0aW9uQWJvcnRlZENvbm5lY3Rpb25SZXNldENvbm5lY3Rpb25SZWZ1c2VkUGVybWlzc2lvbkRlbmllZE5vdEZvdW5kb3BlcmF0aW9uIHN1Y2Nlc3NmdWxvcGVyYXRpb24gbm90IHN1cHBvcnRlZCBvbiB0aGlzIHBsYXRmb3JtAG9EEAAoAAAAY29uZHZhciB3YWl0IG5vdCBzdXBwb3J0ZWRsaWJyYXJ5L3N0ZC9zcmMvc3lzL3dhc20vLi4vdW5zdXBwb3J0ZWQvY29uZHZhci5yc7pEEAAyAAAAFwAAAAkAAABjYW5ub3QgcmVjdXJzaXZlbHkgYWNxdWlyZSBtdXRlePxEEAAgAAAAbGlicmFyeS9zdGQvc3JjL3N5cy93YXNtLy4uL3Vuc3VwcG9ydGVkL211dGV4LnJzJEUQADAAAAAXAAAACQAAAIwAAAAEAAAABAAAAI0AAACOAAAAjwAAAIwAAAAEAAAABAAAAJAAAAAvcnVzdGMvYTE3OGQwMzIyY2UyMGUzM2VhYzEyNDc1OGU4MzdjYmQ4MGE2ZjYzMy9saWJyYXJ5L2NvcmUvc3JjL2ZtdC9tb2QucnMAjEUQAEsAAAByAQAAEwAAAIwAAAAAAAAAAQAAACIAAABhIGZvcm1hdHRpbmcgdHJhaXQgaW1wbGVtZW50YXRpb24gcmV0dXJuZWQgYW4gZXJyb3JsaWJyYXJ5L2FsbG9jL3Ny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" +} diff --git a/yarn.lock b/yarn.lock index 57cc51367..5771bf685 100644 --- a/yarn.lock +++ b/yarn.lock @@ -302,6 +302,26 @@ __metadata: languageName: node linkType: hard +"@cosmjs/cosmwasm-stargate@npm:0.28.4": + version: 0.28.4 + resolution: "@cosmjs/cosmwasm-stargate@npm:0.28.4" + dependencies: + "@cosmjs/amino": 0.28.4 + "@cosmjs/crypto": 0.28.4 + "@cosmjs/encoding": 0.28.4 + "@cosmjs/math": 0.28.4 + "@cosmjs/proto-signing": 0.28.4 + "@cosmjs/stargate": 0.28.4 + "@cosmjs/tendermint-rpc": 0.28.4 + "@cosmjs/utils": 0.28.4 + cosmjs-types: ^0.4.0 + long: ^4.0.0 + pako: ^2.0.2 + protobufjs: ~6.10.2 + checksum: 6b1142c45f6dc130385889d4bd4e2fc13bde3ef431f649f88e019871b961a01eb92af5d60d3f97c407e6d25aeb62e6baf5cdbb366b8e5d5ae009a7231cee40b9 + languageName: node + linkType: hard + "@cosmjs/crypto@npm:0.28.4": version: 0.28.4 resolution: "@cosmjs/crypto@npm:0.28.4" @@ -594,6 +614,7 @@ __metadata: resolution: "@lbmjs/finschia@workspace:packages/finschia" dependencies: "@cosmjs/amino": 0.28.4 + "@cosmjs/cosmwasm-stargate": 0.28.4 "@cosmjs/crypto": 0.28.4 "@cosmjs/encoding": 0.28.4 "@cosmjs/math": 0.28.4 @@ -609,6 +630,7 @@ __metadata: "@types/karma-jasmine-html-reporter": ^1 "@types/long": ^4.0.1 "@types/node": ^15.0.1 + "@types/pako": ^1.0.1 "@typescript-eslint/eslint-plugin": ^5.13.0 "@typescript-eslint/parser": ^5.13.0 cosmjs-types: 0.4.0 @@ -632,6 +654,7 @@ __metadata: lbmjs-types: ^0.46.0-rc7 long: ^4.0.0 nyc: ^15.1.0 + pako: ^2.0.2 prettier: ^2.4.1 protobufjs: ~6.10.2 readonly-date: ^1.0.0 From 41cc65c3faf876c4ef1b161cc185b97d09805a8b Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Thu, 8 Sep 2022 01:36:57 +0900 Subject: [PATCH 04/15] feat: enable tests --- .../finschia/src/cosmwasm-testutils.spec.ts | 240 --- .../src/finschiaclient.searchtx.spec.ts | 378 ++++ packages/finschia/src/finschiaclient.spec.ts | 655 +++++++ .../{finschiaClient.ts => finschiaclient.ts} | 1 + .../finschia/src/modules/wasm/queries.spec.ts | 116 +- .../src/signingfinschiaclient.spec.ts | 1555 +++++++++++++++++ packages/finschia/src/testutils.spec.ts | 155 +- 7 files changed, 2751 insertions(+), 349 deletions(-) delete mode 100644 packages/finschia/src/cosmwasm-testutils.spec.ts create mode 100644 packages/finschia/src/finschiaclient.searchtx.spec.ts create mode 100644 packages/finschia/src/finschiaclient.spec.ts rename packages/finschia/src/{finschiaClient.ts => finschiaclient.ts} (99%) create mode 100644 packages/finschia/src/signingfinschiaclient.spec.ts diff --git a/packages/finschia/src/cosmwasm-testutils.spec.ts b/packages/finschia/src/cosmwasm-testutils.spec.ts deleted file mode 100644 index 4792de7d6..000000000 --- a/packages/finschia/src/cosmwasm-testutils.spec.ts +++ /dev/null @@ -1,240 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { AminoSignResponse, Secp256k1HdWallet, Secp256k1HdWalletOptions, StdSignDoc } from "@cosmjs/amino"; -import { Bip39, EnglishMnemonic, Random } from "@cosmjs/crypto"; -import { fromBase64, toBech32 } from "@cosmjs/encoding"; -import { - DirectSecp256k1HdWallet, - DirectSecp256k1HdWalletOptions, - DirectSignResponse, - makeAuthInfoBytes, -} from "@cosmjs/proto-signing"; -import { - AuthExtension, - BankExtension, - calculateFee, - coins, - GasPrice, - QueryClient, - setupAuthExtension, - setupBankExtension, -} from "@cosmjs/stargate"; -import { SigningStargateClientOptions } from "@cosmjs/stargate"; -import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; -import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; -import { AuthInfo, SignDoc, TxBody } from "cosmjs-types/cosmos/tx/v1beta1/tx"; - -import { setupWasmExtension, WasmExtension } from "./modules"; -import hackatom from "./testdata/contract.json"; - -export const defaultGasPrice = GasPrice.fromString("0.025cony"); -export const defaultSendFee = calculateFee(100_000, defaultGasPrice); -export const defaultUploadFee = calculateFee(1_500_000, defaultGasPrice); -export const defaultInstantiateFee = calculateFee(500_000, defaultGasPrice); -export const defaultUploadAndInstantiateFee = calculateFee(2_000_000, defaultGasPrice); -export const defaultExecuteFee = calculateFee(200_000, defaultGasPrice); -export const defaultMigrateFee = calculateFee(200_000, defaultGasPrice); -export const defaultUpdateAdminFee = calculateFee(80_000, defaultGasPrice); -export const defaultClearAdminFee = calculateFee(80_000, defaultGasPrice); - -/** An internal testing type. SigningCosmWasmClient has a similar but different interface */ -export interface ContractUploadInstructions { - /** The wasm bytecode */ - readonly data: Uint8Array; -} - -export const wasmd = { - blockTime: 1_000, // ms - chainId: "simd-testing", - endpoint: "localhost:26658", - prefix: "link", - validator: { - address: "linkvaloper146asaycmtydq45kxc8evntqfgepagygeddajpy", - }, -}; - -/** Setting to speed up testing */ -export const defaultSigningClientOptions: SigningStargateClientOptions = { - broadcastPollIntervalMs: 300, - broadcastTimeoutMs: 8_000, -}; - -export function getHackatom(): ContractUploadInstructions { - return { - data: fromBase64(hackatom.data), - }; -} - -export function makeRandomAddress(): string { - return toBech32("link", Random.getBytes(20)); -} - -export const tendermintIdMatcher = /^[0-9A-F]{64}$/; -/** @see https://rgxdb.com/r/1NUN74O6 */ -export const base64Matcher = - /^(?:[a-zA-Z0-9+/]{4})*(?:|(?:[a-zA-Z0-9+/]{3}=)|(?:[a-zA-Z0-9+/]{2}==)|(?:[a-zA-Z0-9+/]{1}===))$/; -// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 -export const bech32AddressMatcher = /^[\x21-\x7e]{1,83}1[02-9ac-hj-np-z]{38,58}$/; - -export const alice = { - mnemonic: - "mind flame tobacco sense move hammer drift crime ring globe art gaze cinnamon helmet cruise special produce notable negative wait path scrap recall have", - pubkey0: { - type: "tendermint/PubKeySecp256k1", - value: "AgT2QPS4Eu6M+cfHeba+3tumsM/hNEBGdM7nRojSZRjF", - }, - address0: "link146asaycmtydq45kxc8evntqfgepagygelel00h", - address1: "link1aaffxdz4dwcnjzumjm7h89yjw5c5wul88zvzuu", - address2: "link1ey0w0xj9v48vk82ht6mhqdlh9wqkx8enkpjwpr", - address3: "link1dfyywjglcfptn72axxhsslpy8ep6wq7wujasma", - address4: "link1equ4n3uwyhapak5g3leq0avz85k0q6jcdy5w0f", -}; - -/** Unused account */ -export const unused = { - pubkey: { - type: "tendermint/PubKeySecp256k1", - value: "A7Tvuh48+JzNyBnTeK2Qw987f5FqFHK/QH65pTVsZvuh", - }, - address: "link1g7gsgktl9yjqatacswlwvns5yzy4u5jehsx2pz", - accountNumber: 8, - sequence: 0, -}; - -export const validator = { - /** - * delegator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/wasmd/template/.wasmd/config/genesis.json - * - * `jq ".app_state.genutil.gen_txs[0].body.messages[0].delegator_address" scripts/wasmd/template/.wasmd/config/genesis.json` - */ - delegatorAddress: "link1twsfmuj28ndph54k4nw8crwu8h9c8mh3rtx705", - /** - * validator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/wasmd/template/.wasmd/config/genesis.json - * - * `jq ".app_state.genutil.gen_txs[0].body.messages[0].validator_address" scripts/wasmd/template/.wasmd/config/genesis.json` - */ - validatorAddress: "linkvaloper1twsfmuj28ndph54k4nw8crwu8h9c8mh33lyrp8", - sequence: 1, -}; - -/** Deployed as part of scripts/wasmd/init.sh */ -export const deployedHackatom = { - codeId: 1, - checksum: "470c5b703a682f778b8b088d48169b8d6e43f7f44ac70316692cdbe69e6605e3", - instances: [ - { - beneficiary: alice.address0, - address: "link14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sgf2vn8", - label: "From deploy_hackatom.js (0)", - }, - { - beneficiary: alice.address1, - address: "link1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrshuxemw", - label: "From deploy_hackatom.js (1)", - }, - { - beneficiary: alice.address2, - address: "link1yyca08xqdgvjz0psg56z67ejh9xms6l436u8y58m82npdqqhmmtq6cjue5", - label: "From deploy_hackatom.js (2)", - }, - ], -}; - -/** Deployed as part of scripts/wasmd/init.sh */ -export const deployedIbcReflect = { - codeId: 2, - instances: [ - { - address: "link1aakfpghcanxtc45gpqlx8j3rq0zcpyf49qmhm9mdjrfx036h4z5s782d42", - ibcPortId: "wasm.link1aakfpghcanxtc45gpqlx8j3rq0zcpyf49qmhm9mdjrfx036h4z5s782d42", - }, - ], -}; - -export function wasmdEnabled(): boolean { - return !!process.env.WASMD_ENABLED; -} - -export function pendingWithoutWasmd(): void { - if (!wasmdEnabled()) { - return pending("Set WASMD_ENABLED to enable Wasmd-based tests"); - } -} - -/** Returns first element. Throws if array has a different length than 1. */ -export function fromOneElementArray(elements: ArrayLike): T { - if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`); - return elements[0]; -} - -export async function makeWasmClient( - endpoint: string, -): Promise { - const tmClient = await Tendermint34Client.connect(endpoint); - return QueryClient.withExtensions(tmClient, setupAuthExtension, setupBankExtension, setupWasmExtension); -} - -/** - * A class for testing clients using an Amino signer which modifies the transaction it receives before signing - */ -export class ModifyingSecp256k1HdWallet extends Secp256k1HdWallet { - public static override async fromMnemonic( - mnemonic: string, - options: Partial = {}, - ): Promise { - const mnemonicChecked = new EnglishMnemonic(mnemonic); - const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); - return new ModifyingSecp256k1HdWallet(mnemonicChecked, { ...options, seed: seed }); - } - - public override async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise { - const modifiedSignDoc = { - ...signDoc, - fee: { - amount: coins(3000, "cony"), - gas: "333333", - }, - memo: "This was modified", - }; - return super.signAmino(signerAddress, modifiedSignDoc); - } -} - -/** - * A class for testing clients using a direct signer which modifies the transaction it receives before signing - */ -export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet { - public static override async fromMnemonic( - mnemonic: string, - options: Partial = {}, - ): Promise { - const mnemonicChecked = new EnglishMnemonic(mnemonic); - const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); - return new ModifyingDirectSecp256k1HdWallet(mnemonicChecked, { ...options, seed: seed }); - } - - public override async signDirect(address: string, signDoc: SignDoc): Promise { - const txBody = TxBody.decode(signDoc.bodyBytes); - const modifiedTxBody = TxBody.fromPartial({ - ...txBody, - memo: "This was modified", - }); - const authInfo = AuthInfo.decode(signDoc.authInfoBytes); - const signers = authInfo.signerInfos.map((signerInfo) => ({ - pubkey: signerInfo.publicKey!, - sequence: signerInfo.sequence.toNumber(), - })); - const modifiedFeeAmount = coins(3000, "cony"); - const modifiedGasLimit = 333333; - const modifiedSignDoc = { - ...signDoc, - bodyBytes: Uint8Array.from(TxBody.encode(modifiedTxBody).finish()), - authInfoBytes: makeAuthInfoBytes( - signers, - modifiedFeeAmount, - modifiedGasLimit, - SignMode.SIGN_MODE_DIRECT, - ), - }; - return super.signDirect(address, modifiedSignDoc); - } -} diff --git a/packages/finschia/src/finschiaclient.searchtx.spec.ts b/packages/finschia/src/finschiaclient.searchtx.spec.ts new file mode 100644 index 000000000..48d620e84 --- /dev/null +++ b/packages/finschia/src/finschiaclient.searchtx.spec.ts @@ -0,0 +1,378 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { fromBase64, toBase64 } from "@cosmjs/encoding"; +import { + decodeTxRaw, + DirectSecp256k1HdWallet, + encodePubkey, + makeAuthInfoBytes, + makeSignDoc, + Registry, + TxBodyEncodeObject, +} from "@cosmjs/proto-signing"; +import { + Coin, + coins, + DeliverTxResponse, + isDeliverTxFailure, + isDeliverTxSuccess, + isMsgSendEncodeObject, +} from "@cosmjs/stargate"; +import { assert, sleep } from "@cosmjs/utils"; +import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; + +import { FinschiaClient } from "./finschiaclient"; +import { makeLinkPath } from "./paths"; +import { + defaultSigningClientOptions, + faucet, + fromOneElementArray, + makeRandomAddress, + pendingWithoutSimapp, + simapp, + simappEnabled, +} from "./testutils.spec"; + +interface TestTxSend { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: Uint8Array; +} + +async function sendTokens( + client: FinschiaClient, + registry: Registry, + wallet: DirectSecp256k1HdWallet, + recipient: string, + amount: readonly Coin[], + memo: string, +): Promise<{ + readonly broadcastResponse: DeliverTxResponse; + readonly tx: Uint8Array; +}> { + const [{ address: walletAddress, pubkey: pubkeyBytes }] = await wallet.getAccounts(); + const pubkey = encodePubkey({ + type: "tendermint/PubKeySecp256k1", + value: toBase64(pubkeyBytes), + }); + const txBodyFields: TxBodyEncodeObject = { + typeUrl: "/cosmos.tx.v1beta1.TxBody", + value: { + messages: [ + { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: walletAddress, + toAddress: recipient, + amount: amount, + }, + }, + ], + memo: memo, + }, + }; + const txBodyBytes = registry.encode(txBodyFields); + const { accountNumber, sequence } = (await client.getSequence(walletAddress))!; + const feeAmount = [ + { + amount: "2000", + denom: "cony", + }, + ]; + const gasLimit = 200000; + const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], feeAmount, gasLimit); + + const chainId = await client.getChainId(); + const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber); + const { signature } = await wallet.signDirect(walletAddress, signDoc); + const txRaw = TxRaw.fromPartial({ + bodyBytes: txBodyBytes, + authInfoBytes: authInfoBytes, + signatures: [fromBase64(signature.signature)], + }); + const txRawBytes = Uint8Array.from(TxRaw.encode(txRaw).finish()); + const broadcastResponse = await client.broadcastTx( + txRawBytes, + defaultSigningClientOptions.broadcastTimeoutMs, + defaultSigningClientOptions.broadcastPollIntervalMs, + ); + return { + broadcastResponse: broadcastResponse, + tx: txRawBytes, + }; +} + +describe("FinschiaClient.getTx and .searchTx", () => { + const registry = new Registry(); + + let sendUnsuccessful: TestTxSend | undefined; + let sendSuccessful: TestTxSend | undefined; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const unsuccessfulRecipient = makeRandomAddress(); + const successfulRecipient = makeRandomAddress(); + + const unsuccessfulResult = await sendTokens( + client, + registry, + wallet, + unsuccessfulRecipient, + coins(123456700000000, "cony"), + "Sending more than I can afford", + ); + if (isDeliverTxFailure(unsuccessfulResult.broadcastResponse)) { + sendUnsuccessful = { + sender: faucet.address0, + recipient: unsuccessfulRecipient, + hash: unsuccessfulResult.broadcastResponse.transactionHash, + height: unsuccessfulResult.broadcastResponse.height, + tx: unsuccessfulResult.tx, + }; + } + const successfulResult = await sendTokens( + client, + registry, + wallet, + successfulRecipient, + coins(1234567, "cony"), + "Something I can afford", + ); + if (isDeliverTxSuccess(successfulResult.broadcastResponse)) { + sendSuccessful = { + sender: faucet.address0, + recipient: successfulRecipient, + hash: successfulResult.broadcastResponse.transactionHash, + height: successfulResult.broadcastResponse.height, + tx: successfulResult.tx, + }; + } + + await sleep(75); // wait until transactions are indexed + + client.disconnect(); + } + }); + + describe("getTx", () => { + it("can get successful tx by ID", async () => { + pendingWithoutSimapp(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const result = await client.getTx(sendSuccessful.hash); + expect(result).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + code: 0, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can get unsuccessful tx by ID", async () => { + pendingWithoutSimapp(); + assert(sendUnsuccessful, "value must be set in beforeAll()"); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const result = await client.getTx(sendUnsuccessful.hash); + expect(result).toEqual( + jasmine.objectContaining({ + height: sendUnsuccessful.height, + hash: sendUnsuccessful.hash, + code: 5, + tx: sendUnsuccessful.tx, + }), + ); + }); + + it("can get by ID (non existent)", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; + const result = await client.getTx(nonExistentId); + expect(result).toBeNull(); + }); + }); + + describe("with SearchByHeightQuery", () => { + it("can search successful tx by height", async () => { + pendingWithoutSimapp(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const result = await client.searchTx({ height: sendSuccessful.height }); + expect(result.length).toBeGreaterThanOrEqual(1); + expect(result).toContain( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + code: 0, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search unsuccessful tx by height", async () => { + pendingWithoutSimapp(); + assert(sendUnsuccessful, "value must be set in beforeAll()"); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const result = await client.searchTx({ height: sendUnsuccessful.height }); + expect(result.length).toBeGreaterThanOrEqual(1); + expect(result).toContain( + jasmine.objectContaining({ + height: sendUnsuccessful.height, + hash: sendUnsuccessful.hash, + code: 5, + tx: sendUnsuccessful.tx, + }), + ); + }); + }); + + describe("with SearchBySentFromOrToQuery", () => { + it("can search by sender", async () => { + pendingWithoutSimapp(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const results = await client.searchTx({ sentFromOrTo: sendSuccessful.sender }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const tx = decodeTxRaw(result.tx); + const filteredMsgs = tx.body.messages.filter((msg) => { + if (!isMsgSendEncodeObject(msg)) return false; + const decoded = registry.decode(msg); + return decoded.fromAddress === sendSuccessful?.sender; + }); + expect(filteredMsgs.length).toBeGreaterThanOrEqual(1); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search by recipient", async () => { + pendingWithoutSimapp(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const results = await client.searchTx({ sentFromOrTo: sendSuccessful.recipient }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const tx = decodeTxRaw(result.tx); + const filteredMsgs = tx.body.messages.filter((msg) => { + if (!isMsgSendEncodeObject(msg)) return false; + const decoded = registry.decode(msg); + return decoded.toAddress === sendSuccessful?.recipient; + }); + expect(filteredMsgs.length).toBeGreaterThanOrEqual(1); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search by recipient and filter by minHeight", async () => { + pendingWithoutSimapp(); + assert(sendSuccessful); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const query = { sentFromOrTo: sendSuccessful.recipient }; + + { + const result = await client.searchTx(query, { minHeight: 0 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: sendSuccessful.height - 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: sendSuccessful.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: sendSuccessful.height + 1 }); + expect(result.length).toEqual(0); + } + }); + + it("can search by recipient and filter by maxHeight", async () => { + pendingWithoutSimapp(); + assert(sendSuccessful); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const query = { sentFromOrTo: sendSuccessful.recipient }; + + { + const result = await client.searchTx(query, { maxHeight: 9999999999999 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: sendSuccessful.height + 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: sendSuccessful.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: sendSuccessful.height - 1 }); + expect(result.length).toEqual(0); + } + }); + }); + + describe("with SearchByTagsQuery", () => { + it("can search by transfer.recipient", async () => { + pendingWithoutSimapp(); + assert(sendSuccessful, "value must be set in beforeAll()"); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const results = await client.searchTx({ + tags: [{ key: "transfer.recipient", value: sendSuccessful.recipient }], + }); + expect(results.length).toBeGreaterThanOrEqual(1); + + // Check basic structure of all results + for (const result of results) { + const tx = decodeTxRaw(result.tx); + const msg = fromOneElementArray(tx.body.messages); + expect(msg.typeUrl).toEqual("/cosmos.bank.v1beta1.MsgSend"); + const decoded = registry.decode(msg); + expect(decoded.toAddress).toEqual(sendSuccessful.recipient); + } + + // Check details of most recent result + expect(results[results.length - 1]).toEqual( + jasmine.objectContaining({ + height: sendSuccessful.height, + hash: sendSuccessful.hash, + tx: sendSuccessful.tx, + }), + ); + }); + }); +}); diff --git a/packages/finschia/src/finschiaclient.spec.ts b/packages/finschia/src/finschiaclient.spec.ts new file mode 100644 index 000000000..fcba109ba --- /dev/null +++ b/packages/finschia/src/finschiaclient.spec.ts @@ -0,0 +1,655 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Code } from "@cosmjs/cosmwasm-stargate"; +import { sha256 } from "@cosmjs/crypto"; +import { fromAscii, fromBase64, fromHex, toAscii, toBase64 } from "@cosmjs/encoding"; +import { Int53 } from "@cosmjs/math"; +import { + DirectSecp256k1HdWallet, + encodePubkey, + makeAuthInfoBytes, + makeSignDoc, + Registry, + TxBodyEncodeObject, +} from "@cosmjs/proto-signing"; +import { + assertIsDeliverTxSuccess, + coins, + isDeliverTxFailure, + isDeliverTxSuccess, + logs, + MsgSendEncodeObject, + StdFee, +} from "@cosmjs/stargate"; +import { assert, sleep } from "@cosmjs/utils"; +import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; +import { ReadonlyDate } from "readonly-date"; + +import { FinschiaClient, PrivateFinschiaClient } from "./finschiaclient"; +import { makeLinkPath } from "./paths"; +import { SigningFinschiaClient } from "./signingfinschiaclient"; +import { + defaultInstantiateFee, + defaultUploadFee, + deployedHackatom, + deployedIbcReflect, + faucet, + getHackatom, + makeRandomAddress, + nonExistentAddress, + pendingWithoutSimapp, + simapp, + simappEnabled, + tendermintIdMatcher, + unused, + validator, +} from "./testutils.spec"; + +const resultFailure = { + code: 5, + height: 219901, + rawLog: + "failed to execute message; message index: 0: 1855527000ufct is smaller than 20000000000000000000000ufct: insufficient funds", + transactionHash: "FDC4FB701AABD465935F7D04AE490D1EF5F2BD4B227601C4E98B57EB077D9B7D", + gasUsed: 54396, + gasWanted: 200000, +}; +const resultSuccess = { + code: 0, + height: 219894, + rawLog: + '[{"events":[{"type":"message","attributes":[{"key":"action","value":"send"},{"key":"sender","value":"firma1trqyle9m2nvyafc2n25frkpwed2504y6avgfzr"},{"key":"module","value":"bank"}]},{"type":"transfer","attributes":[{"key":"recipient","value":"firma12er8ls2sf5zess3jgjxz59xat9xtf8hz0hk6n4"},{"key":"sender","value":"firma1trqyle9m2nvyafc2n25frkpwed2504y6avgfzr"},{"key":"amount","value":"2000000ufct"}]}]}]', + transactionHash: "C0B416CA868C55C2B8C1BBB8F3CFA233854F13A5CB15D3E9599F50CAF7B3D161", + gasUsed: 61556, + gasWanted: 200000, +}; + +describe("isDeliverTxFailure", () => { + it("works", () => { + expect(isDeliverTxFailure(resultFailure)).toEqual(true); + expect(isDeliverTxFailure(resultSuccess)).toEqual(false); + }); +}); + +describe("isDeliverTxSuccess", () => { + it("works", () => { + expect(isDeliverTxSuccess(resultFailure)).toEqual(false); + expect(isDeliverTxSuccess(resultSuccess)).toEqual(true); + }); +}); + +interface HackatomInstance { + readonly instantiateMsg: { + readonly verifier: string; + readonly beneficiary: string; + }; + readonly address: string; +} + +describe("FinschiaClient", () => { + describe("connect", () => { + it("can be constructed", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + expect(client).toBeTruthy(); + client.disconnect(); + }); + }); + + describe("getChainId", () => { + it("works", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + expect(await client.getChainId()).toEqual(simapp.chainId); + client.disconnect(); + }); + + it("caches chain ID", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const openedClient = client as unknown as PrivateFinschiaClient; + const getCodeSpy = spyOn(openedClient.tmClient!, "status").and.callThrough(); + + expect(await client.getChainId()).toEqual(simapp.chainId); // from network + expect(await client.getChainId()).toEqual(simapp.chainId); // from cache + + expect(getCodeSpy).toHaveBeenCalledTimes(1); + + client.disconnect(); + }); + }); + + describe("getHeight", () => { + it("works", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + + const height1 = await client.getHeight(); + expect(height1).toBeGreaterThan(0); + + await sleep(simapp.blockTime * 1.4); // tolerate chain being 40% slower than expected + + const height2 = await client.getHeight(); + expect(height2).toBeGreaterThanOrEqual(height1 + 1); + expect(height2).toBeLessThanOrEqual(height1 + 2); + + client.disconnect(); + }); + }); + + describe("getAccount", () => { + it("works for unused account", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + expect(await client.getAccount(unused.address)).toEqual({ + address: unused.address, + accountNumber: unused.accountNumber, + sequence: unused.sequence, + pubkey: null, + }); + + client.disconnect(); + }); + + it("works for account with pubkey and non-zero sequence", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + + const account = await client.getAccount(validator.delegatorAddress); + assert(account); + expect(account).toEqual({ + address: validator.delegatorAddress, + pubkey: validator.pubkey, + accountNumber: validator.accountNumber, + sequence: 1, + }); + + client.disconnect(); + }); + + it("returns null for missing accounts", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const missing = makeRandomAddress(); + expect(await client.getAccount(missing)).toBeNull(); + }); + }); + + describe("getSequence", () => { + it("works", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + expect(await client.getSequence(unused.address)).toEqual({ + accountNumber: unused.accountNumber, + sequence: unused.sequence, + }); + }); + + it("rejects for missing accounts", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const missing = makeRandomAddress(); + await expectAsync(client.getSequence(missing)).toBeRejectedWithError( + /account does not exist on chain/i, + ); + }); + }); + + describe("getBlock", () => { + it("works for latest block", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const response = await client.getBlock(); + + // id + expect(response.id).toMatch(tendermintIdMatcher); + + // header + expect(response.header.height).toBeGreaterThanOrEqual(1); + expect(response.header.chainId).toEqual(await client.getChainId()); + expect(new ReadonlyDate(response.header.time).getTime()).toBeLessThan(ReadonlyDate.now()); + expect(new ReadonlyDate(response.header.time).getTime()).toBeGreaterThanOrEqual( + ReadonlyDate.now() - 5_000, + ); + + // txs + expect(Array.isArray(response.txs)).toEqual(true); + }); + + it("works for block by height", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const height = (await client.getBlock()).header.height; + const response = await client.getBlock(height - 1); + + // id + expect(response.id).toMatch(tendermintIdMatcher); + + // header + expect(response.header.height).toEqual(height - 1); + expect(response.header.chainId).toEqual(await client.getChainId()); + expect(new ReadonlyDate(response.header.time).getTime()).toBeLessThan(ReadonlyDate.now()); + expect(new ReadonlyDate(response.header.time).getTime()).toBeGreaterThanOrEqual( + ReadonlyDate.now() - 5_000, + ); + + // txs + expect(Array.isArray(response.txs)).toEqual(true); + }); + }); + + describe("broadcastTx", () => { + it("works", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const registry = new Registry(); + + const memo = "My first contract on chain"; + const sendMsg: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: faucet.address0, + toAddress: makeRandomAddress(), + amount: coins(1234567, "cony"), + }, + }; + const fee: StdFee = { + amount: coins(5000, "cony"), + gas: "890000", + }; + + const chainId = await client.getChainId(); + const sequenceResponse = await client.getSequence(faucet.address0); + assert(sequenceResponse); + const { accountNumber, sequence } = sequenceResponse; + const pubkey = encodePubkey(faucet.pubkey0); + const txBody: TxBodyEncodeObject = { + typeUrl: "/cosmos.tx.v1beta1.TxBody", + value: { + messages: [sendMsg], + memo: memo, + }, + }; + const txBodyBytes = registry.encode(txBody); + const gasLimit = Int53.fromString(fee.gas).toNumber(); + const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], fee.amount, gasLimit); + const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber); + const { signed, signature } = await wallet.signDirect(faucet.address0, signDoc); + const txRaw = TxRaw.fromPartial({ + bodyBytes: signed.bodyBytes, + authInfoBytes: signed.authInfoBytes, + signatures: [fromBase64(signature.signature)], + }); + const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish()); + const result = await client.broadcastTx(signedTx); + assertIsDeliverTxSuccess(result); + const amountAttr = logs.findAttribute(logs.parseRawLog(result.rawLog), "transfer", "amount"); + expect(amountAttr.value).toEqual("1234567cony"); + expect(result.transactionHash).toMatch(/^[0-9A-F]{64}$/); + }); + + it("errors immediately for a CheckTx failure", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const [{ address, pubkey: pubkeyBytes }] = await wallet.getAccounts(); + const pubkey = encodePubkey({ + type: "tendermint/PubKeySecp256k1", + value: toBase64(pubkeyBytes), + }); + const registry = new Registry(); + const invalidRecipientAddress = "tgrade1z363ulwcrxged4z5jswyt5dn5v3lzsemwz9ewj"; // wrong bech32 prefix + const txBodyFields: TxBodyEncodeObject = { + typeUrl: "/cosmos.tx.v1beta1.TxBody", + value: { + messages: [ + { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: { + fromAddress: address, + toAddress: invalidRecipientAddress, + amount: [ + { + denom: "cony", + amount: "1234567", + }, + ], + }, + }, + ], + }, + }; + const txBodyBytes = registry.encode(txBodyFields); + const { accountNumber, sequence } = (await client.getSequence(address))!; + const feeAmount = coins(2000, "cony"); + const gasLimit = 200000; + const authInfoBytes = makeAuthInfoBytes([{ pubkey, sequence }], feeAmount, gasLimit, sequence); + + const chainId = await client.getChainId(); + const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber); + const { signature } = await wallet.signDirect(address, signDoc); + const txRaw = TxRaw.fromPartial({ + bodyBytes: txBodyBytes, + authInfoBytes: authInfoBytes, + signatures: [fromBase64(signature.signature)], + }); + const txRawBytes = Uint8Array.from(TxRaw.encode(txRaw).finish()); + + await expectAsync(client.broadcastTx(txRawBytes)).toBeRejectedWithError(/invalid recipient address/i); + + client.disconnect(); + }); + }); + + describe("getBalance", () => { + it("works for different existing balances", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + + const response1 = await client.getBalance(unused.address, simapp.denomFee); + expect(response1).toEqual({ + amount: unused.balanceFee, + denom: simapp.denomFee, + }); + const response2 = await client.getBalance(unused.address, simapp.denomStaking); + expect(response2).toEqual({ + amount: unused.balanceStaking, + denom: simapp.denomStaking, + }); + + client.disconnect(); + }); + + describe("getBalanceStaked", () => { + it("works", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const response = await client.getBalanceStaked(faucet.address0); + + expect(response).toEqual({ denom: "stake", amount: "63474" }); + }); + }); + + it("returns 0 for non-existent balance", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + + const response = await client.getBalance(unused.address, "gintonic"); + expect(response).toEqual({ + denom: "gintonic", + amount: "0", + }); + + client.disconnect(); + }); + + it("returns 0 for non-existent address", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + + const response = await client.getBalance(nonExistentAddress, simapp.denomFee); + expect(response).toEqual({ + denom: simapp.denomFee, + amount: "0", + }); + + client.disconnect(); + }); + }); + + describe("getAllBalances", () => { + it("returns all balances for unused account", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + + const balances = await client.getAllBalances(unused.address); + expect(balances).toEqual([ + { + amount: unused.balanceFee, + denom: simapp.denomFee, + }, + { + amount: unused.balanceStaking, + denom: simapp.denomStaking, + }, + ]); + + client.disconnect(); + }); + + it("returns an empty list for non-existent account", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + + const balances = await client.getAllBalances(nonExistentAddress); + expect(balances).toEqual([]); + + client.disconnect(); + }); + }); + + describe("getCodes", () => { + it("works", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const result = await client.getCodes(); + expect(result.length).toBeGreaterThanOrEqual(1); + const [first] = result; + expect(first).toEqual({ + id: deployedHackatom.codeId, + checksum: deployedHackatom.checksum, + creator: faucet.address0, + }); + }); + }); + + describe("getCodeDetails", () => { + it("works", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const result = await client.getCodeDetails(1); + + const expectedInfo: Code = { + id: deployedHackatom.codeId, + checksum: deployedHackatom.checksum, + creator: faucet.address0, + }; + + // check info + expect(result).toEqual(jasmine.objectContaining(expectedInfo)); + // check data + expect(sha256(result.data)).toEqual(fromHex(expectedInfo.checksum)); + }); + + it("caches downloads", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const openedClient = client as unknown as PrivateFinschiaClient; + const getCodeSpy = spyOn(openedClient.queryClient!.wasm, "getCode").and.callThrough(); + + const result1 = await client.getCodeDetails(deployedHackatom.codeId); // from network + const result2 = await client.getCodeDetails(deployedHackatom.codeId); // from cache + expect(result2).toEqual(result1); + + expect(getCodeSpy).toHaveBeenCalledTimes(1); + }); + }); + + describe("getContracts", () => { + it("works", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const result = await client.getContracts(deployedHackatom.codeId); + const expectedAddresses = deployedHackatom.instances.map((info) => info.address); + + // Test first 3 instances we get from scripts/wasmd/init.sh. There may me more than that in the result. + expect(result[0]).toEqual(expectedAddresses[0]); + expect(result[1]).toEqual(expectedAddresses[1]); + expect(result[2]).toEqual(expectedAddresses[2]); + }); + }); + + describe("getContract", () => { + it("works for instance without admin", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const zero = await client.getContract(deployedHackatom.instances[0].address); + expect(zero).toEqual({ + address: deployedHackatom.instances[0].address, + codeId: deployedHackatom.codeId, + creator: faucet.address0, + label: deployedHackatom.instances[0].label, + admin: undefined, + ibcPortId: undefined, + }); + }); + + it("works for instance with admin", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const two = await client.getContract(deployedHackatom.instances[2].address); + expect(two).toEqual({ + address: deployedHackatom.instances[2].address, + codeId: deployedHackatom.codeId, + creator: faucet.address0, + label: deployedHackatom.instances[2].label, + admin: faucet.address1, + ibcPortId: undefined, + }); + }); + + it("works for instance with IBC port ID", async () => { + pendingWithoutSimapp(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const contract = await client.getContract(deployedIbcReflect.instances[0].address); + expect(contract).toEqual( + jasmine.objectContaining({ + address: deployedIbcReflect.instances[0].address, + codeId: deployedIbcReflect.codeId, + ibcPortId: deployedIbcReflect.instances[0].ibcPortId, + }), + ); + }); + }); + + describe("queryContractRaw", () => { + const configKey = toAscii("config"); + const otherKey = toAscii("this_does_not_exist"); + let contract: HackatomInstance | undefined; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet); + const { codeId } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const instantiateMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() }; + const label = "random hackatom"; + const { contractAddress } = await client.instantiate( + faucet.address0, + codeId, + instantiateMsg, + label, + defaultInstantiateFee, + ); + contract = { instantiateMsg: instantiateMsg, address: contractAddress }; + } + }); + + it("can query existing key", async () => { + pendingWithoutSimapp(); + assert(contract); + + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const raw = await client.queryContractRaw(contract.address, configKey); + assert(raw, "must get result"); + expect(JSON.parse(fromAscii(raw))).toEqual({ + verifier: contract.instantiateMsg.verifier, + beneficiary: contract.instantiateMsg.beneficiary, + funder: faucet.address0, + }); + }); + + it("can query non-existent key", async () => { + pendingWithoutSimapp(); + assert(contract); + + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const raw = await client.queryContractRaw(contract.address, otherKey); + expect(raw).toEqual(new Uint8Array()); + }); + + it("errors for non-existent contract", async () => { + pendingWithoutSimapp(); + assert(contract); + + const nonExistentAddress = makeRandomAddress(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + await expectAsync(client.queryContractRaw(nonExistentAddress, configKey)).toBeRejectedWithError( + /not found/i, + ); + }); + }); + + describe("queryContractSmart", () => { + let contract: HackatomInstance | undefined; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet); + const { codeId } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const instantiateMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() }; + const label = "a different hackatom"; + const { contractAddress } = await client.instantiate( + faucet.address0, + codeId, + instantiateMsg, + label, + defaultInstantiateFee, + ); + contract = { instantiateMsg: instantiateMsg, address: contractAddress }; + } + }); + + it("works", async () => { + pendingWithoutSimapp(); + assert(contract); + + const client = await FinschiaClient.connect(simapp.tendermintUrl); + const result = await client.queryContractSmart(contract.address, { verifier: {} }); + expect(result).toEqual({ verifier: contract.instantiateMsg.verifier }); + }); + + it("errors for malformed query message", async () => { + pendingWithoutSimapp(); + assert(contract); + + const client = await FinschiaClient.connect(simapp.tendermintUrl); + await expectAsync(client.queryContractSmart(contract.address, { broken: {} })).toBeRejectedWithError( + /Error parsing into type hackatom::msg::QueryMsg: unknown variant/i, + ); + }); + + it("errors for non-existent contract", async () => { + pendingWithoutSimapp(); + + const nonExistentAddress = makeRandomAddress(); + const client = await FinschiaClient.connect(simapp.tendermintUrl); + await expectAsync( + client.queryContractSmart(nonExistentAddress, { verifier: {} }), + ).toBeRejectedWithError(/not found/i); + }); + }); +}); diff --git a/packages/finschia/src/finschiaClient.ts b/packages/finschia/src/finschiaclient.ts similarity index 99% rename from packages/finschia/src/finschiaClient.ts rename to packages/finschia/src/finschiaclient.ts index a112c2584..c7352e862 100644 --- a/packages/finschia/src/finschiaClient.ts +++ b/packages/finschia/src/finschiaclient.ts @@ -104,6 +104,7 @@ function createQueryClientWithExtensions(tmClient: Tendermint34Client): QueryCli /** Use for testing only */ export interface PrivateFinschiaClient { readonly tmClient: Tendermint34Client | undefined; + readonly queryClient: QueryClientWithExtensions | undefined; } export class FinschiaClient { diff --git a/packages/finschia/src/modules/wasm/queries.spec.ts b/packages/finschia/src/modules/wasm/queries.spec.ts index da2ae82d4..2a532a1e5 100644 --- a/packages/finschia/src/modules/wasm/queries.spec.ts +++ b/packages/finschia/src/modules/wasm/queries.spec.ts @@ -17,20 +17,20 @@ import { ContractCodeHistoryOperationType } from "cosmjs-types/cosmwasm/wasm/v1/ import { ContractStatus } from "lbmjs-types/cosmwasm/wasm/v1/types"; import Long from "long"; +import { makeLinkPath } from "../../paths"; +import { SigningFinschiaClient } from "../../signingfinschiaclient"; import { - alice, bech32AddressMatcher, ContractUploadInstructions, defaultSigningClientOptions, + faucet, getHackatom, makeRandomAddress, makeWasmClient, - pendingWithoutWasmd, - wasmd, - wasmdEnabled, -} from "../../cosmwasm-testutils.spec"; -import { makeLinkPath } from "../../paths"; -import { SigningFinschiaClient } from "../../signingfinschiaclient"; + pendingWithoutSimapp, + simapp, + simappEnabled, +} from "../../testutils.spec"; import { MsgExecuteContractEncodeObject, MsgInstantiateContractEncodeObject, @@ -51,7 +51,7 @@ async function uploadContract( const theMsg: MsgStoreCodeEncodeObject = { typeUrl: "/cosmwasm.wasm.v1.MsgStoreCode", value: MsgStoreCode.fromPartial({ - sender: alice.address0, + sender: faucet.address0, wasmByteCode: contract.data, }), }; @@ -60,7 +60,7 @@ async function uploadContract( gas: "89000000", }; const firstAddress = (await signer.getAccounts())[0].address; - const client = await SigningStargateClient.connectWithSigner(wasmd.endpoint, signer, { + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, signer, { ...defaultSigningClientOptions, registry, }); @@ -77,12 +77,12 @@ async function instantiateContract( const theMsg: MsgInstantiateContractEncodeObject = { typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", value: MsgInstantiateContract.fromPartial({ - sender: alice.address0, + sender: faucet.address0, codeId: Long.fromNumber(codeId), label: "my escrow", msg: toAscii( JSON.stringify({ - verifier: alice.address0, + verifier: faucet.address0, beneficiary: beneficiaryAddress, }), ), @@ -95,7 +95,7 @@ async function instantiateContract( }; const firstAddress = (await signer.getAccounts())[0].address; - const client = await SigningStargateClient.connectWithSigner(wasmd.endpoint, signer, { + const client = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, signer, { ...defaultSigningClientOptions, registry, }); @@ -111,7 +111,7 @@ async function executeContract( const theMsg: MsgExecuteContractEncodeObject = { typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", value: MsgExecuteContract.fromPartial({ - sender: alice.address0, + sender: faucet.address0, contract: contractAddress, msg: toAscii(JSON.stringify(msg)), funds: [], @@ -123,7 +123,7 @@ async function executeContract( }; const firstAddress = (await signer.getAccounts())[0].address; - const client = await SigningFinschiaClient.connectWithSigner(wasmd.endpoint, signer, { + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, signer, { ...defaultSigningClientOptions, registry, }); @@ -137,10 +137,10 @@ describe("WasmExtension", () => { let hackatomContractAddress: string | undefined; beforeAll(async () => { - if (wasmdEnabled()) { - const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { hdPaths: [makeLinkPath(0)], - prefix: wasmd.prefix, + prefix: simapp.prefix, }); const result = await uploadContract(wallet, hackatom); assertIsDeliverTxSuccess(result); @@ -161,27 +161,27 @@ describe("WasmExtension", () => { describe("listCodeInfo", () => { it("has recently uploaded contract as last entry", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomCodeId); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); const { codeInfos } = await client.wasm.listCodeInfo(); assert(codeInfos); const lastCode = codeInfos[codeInfos.length - 1]; expect(lastCode.codeId.toNumber()).toEqual(hackatomCodeId); - expect(lastCode.creator).toEqual(alice.address0); + expect(lastCode.creator).toEqual(faucet.address0); expect(toHex(lastCode.dataHash)).toEqual(toHex(sha256(hackatom.data))); }); }); describe("getCode", () => { it("contains fill code information", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomCodeId); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); const { codeInfo, data } = await client.wasm.getCode(hackatomCodeId); assert(codeInfo); expect(codeInfo.codeId.toNumber()).toEqual(hackatomCodeId); - expect(codeInfo.creator).toEqual(alice.address0); + expect(codeInfo.creator).toEqual(faucet.address0); expect(toHex(codeInfo.dataHash)).toEqual(toHex(sha256(hackatom.data))); expect(data).toEqual(hackatom.data); }); @@ -190,13 +190,13 @@ describe("WasmExtension", () => { // TODO: move listContractsByCodeId tests out of here describe("getContractInfo", () => { it("works", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomCodeId); - const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { hdPaths: [makeLinkPath(0)], - prefix: wasmd.prefix, + prefix: simapp.prefix, }); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); // create new instance and compare before and after const { contracts: existingContracts } = await client.wasm.listContractsByCodeId(hackatomCodeId); @@ -223,7 +223,7 @@ describe("WasmExtension", () => { assert(contractInfo); expect({ ...contractInfo }).toEqual({ codeId: Long.fromNumber(hackatomCodeId, true), - creator: alice.address0, + creator: faucet.address0, label: "my escrow", admin: "", ibcPortId: "", @@ -235,9 +235,9 @@ describe("WasmExtension", () => { }); it("rejects for non-existent address", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomCodeId); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); const nonExistentAddress = makeRandomAddress(); await expectAsync(client.wasm.getContractInfo(nonExistentAddress)).toBeRejectedWithError(/not found/i); }); @@ -245,13 +245,13 @@ describe("WasmExtension", () => { describe("getContractCodeHistory", () => { it("can list contract history", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomCodeId); - const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { hdPaths: [makeLinkPath(0)], - prefix: wasmd.prefix, + prefix: simapp.prefix, }); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); // create new instance and compare before and after const beneficiaryAddress = makeRandomAddress(); @@ -271,7 +271,7 @@ describe("WasmExtension", () => { operation: ContractCodeHistoryOperationType.CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT, msg: toAscii( JSON.stringify({ - verifier: alice.address0, + verifier: faucet.address0, beneficiary: beneficiaryAddress, }), ), @@ -280,9 +280,9 @@ describe("WasmExtension", () => { }); it("returns empty list for non-existent address", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomCodeId); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); const nonExistentAddress = makeRandomAddress(); const history = await client.wasm.getContractCodeHistory(nonExistentAddress); expect(history.entries).toEqual([]); @@ -291,9 +291,9 @@ describe("WasmExtension", () => { describe("getAllContractState", () => { it("can get all state", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomContractAddress); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); const { models } = await client.wasm.getAllContractState(hackatomContractAddress); assert(models); expect(models.length).toEqual(1); @@ -305,8 +305,8 @@ describe("WasmExtension", () => { }); it("rejects for non-existent address", async () => { - pendingWithoutWasmd(); - const client = await makeWasmClient(wasmd.endpoint); + pendingWithoutSimapp(); + const client = await makeWasmClient(simapp.tendermintUrl); const nonExistentAddress = makeRandomAddress(); await expectAsync(client.wasm.getAllContractState(nonExistentAddress)).toBeRejectedWithError( /not found/i, @@ -316,9 +316,9 @@ describe("WasmExtension", () => { describe("queryContractRaw", () => { it("can query by key", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomContractAddress); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); const raw = await client.wasm.queryContractRaw(hackatomContractAddress, hackatomConfigKey); assert(raw.data, "must get result"); const model = JSON.parse(fromAscii(raw.data)); @@ -327,16 +327,16 @@ describe("WasmExtension", () => { }); it("returns empty for missing key", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomContractAddress); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); const { data } = await client.wasm.queryContractRaw(hackatomContractAddress, fromHex("cafe0dad")); expect(data).toEqual(new Uint8Array()); }); it("returns null for non-existent address", async () => { - pendingWithoutWasmd(); - const client = await makeWasmClient(wasmd.endpoint); + pendingWithoutSimapp(); + const client = await makeWasmClient(simapp.tendermintUrl); const nonExistentAddress = makeRandomAddress(); await expectAsync( client.wasm.queryContractRaw(nonExistentAddress, hackatomConfigKey), @@ -346,18 +346,18 @@ describe("WasmExtension", () => { describe("queryContractSmart", () => { it("can make smart queries", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomContractAddress); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); const request = { verifier: {} }; const result = await client.wasm.queryContractSmart(hackatomContractAddress, request); - expect(result).toEqual({ verifier: alice.address0 }); + expect(result).toEqual({ verifier: faucet.address0 }); }); it("throws for invalid query requests", async () => { - pendingWithoutWasmd(); + pendingWithoutSimapp(); assert(hackatomContractAddress); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); const request = { nosuchkey: {} }; await expectAsync( client.wasm.queryContractSmart(hackatomContractAddress, request), @@ -365,8 +365,8 @@ describe("WasmExtension", () => { }); it("throws for non-existent address", async () => { - pendingWithoutWasmd(); - const client = await makeWasmClient(wasmd.endpoint); + pendingWithoutSimapp(); + const client = await makeWasmClient(simapp.tendermintUrl); const nonExistentAddress = makeRandomAddress(); const request = { verifier: {} }; await expectAsync(client.wasm.queryContractSmart(nonExistentAddress, request)).toBeRejectedWithError( @@ -377,12 +377,12 @@ describe("WasmExtension", () => { describe("broadcastTx", () => { it("can upload, instantiate and execute wasm", async () => { - pendingWithoutWasmd(); - const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { hdPaths: [makeLinkPath(0)], - prefix: wasmd.prefix, + prefix: simapp.prefix, }); - const client = await makeWasmClient(wasmd.endpoint); + const client = await makeWasmClient(simapp.tendermintUrl); const funds = [coin(1234, "cony"), coin(321, "stake")]; const beneficiaryAddress = makeRandomAddress(); diff --git a/packages/finschia/src/signingfinschiaclient.spec.ts b/packages/finschia/src/signingfinschiaclient.spec.ts new file mode 100644 index 000000000..c739cfbd9 --- /dev/null +++ b/packages/finschia/src/signingfinschiaclient.spec.ts @@ -0,0 +1,1555 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Secp256k1HdWallet } from "@cosmjs/amino"; +import { sha256 } from "@cosmjs/crypto"; +import { toHex, toUtf8 } from "@cosmjs/encoding"; +import { decodeTxRaw, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; +import { + AminoMsgDelegate, + AminoTypes, + assertIsDeliverTxFailure, + assertIsDeliverTxSuccess, + coin, + coins, + createStakingAminoConverters, + isDeliverTxFailure, + MsgDelegateEncodeObject, + MsgSendEncodeObject, +} from "@cosmjs/stargate"; +import { assert, sleep } from "@cosmjs/utils"; +import { DeepPartial, MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx"; +import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; +import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; +import { AuthInfo, TxBody, TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; +import { MsgExecuteContract, MsgStoreCode } from "cosmjs-types/cosmwasm/wasm/v1/tx"; +import Long from "long"; +import pako from "pako"; +import protobuf from "protobufjs/minimal"; + +import { MsgExecuteContractEncodeObject, MsgStoreCodeEncodeObject } from "./modules"; +import { makeLinkPath } from "./paths"; +import { SigningFinschiaClient } from "./signingfinschiaclient"; +import { + defaultClearAdminFee, + defaultExecuteFee, + defaultGasPrice, + defaultInstantiateFee, + defaultMigrateFee, + defaultSendFee, + defaultSigningClientOptions, + defaultUpdateAdminFee, + defaultUploadAndInstantiateFee, + defaultUploadFee, + deployedHackatom, + faucet, + getHackatom, + makeRandomAddress, + makeWasmClient, + ModifyingDirectSecp256k1HdWallet, + ModifyingSecp256k1HdWallet, + pendingWithoutSimapp, + simapp, + unused, + validator, +} from "./testutils.spec"; + +describe("SigningFinschiaClient", () => { + describe("connectWithSigner", () => { + it("can be constructed", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + }); + expect(client).toBeTruthy(); + client.disconnect(); + }); + + it("can be constructed with custom registry", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const registry = new Registry(); + registry.register("/custom.MsgCustom", MsgSend); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix, registry: registry }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + expect(client.registry.lookupType("/custom.MsgCustom")).toEqual(MsgSend); + client.disconnect(); + }); + }); + + describe("simulate", () => { + it("works", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + + const executeContractMsg: MsgExecuteContractEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", + value: MsgExecuteContract.fromPartial({ + sender: faucet.address0, + contract: deployedHackatom.instances[0].address, + msg: toUtf8(`{"release":{}}`), + funds: [], + }), + }; + const memo = "Go go go"; + const gasUsed = await client.simulate(faucet.address0, [executeContractMsg], memo); + expect(gasUsed).toBeGreaterThanOrEqual(80_000); + expect(gasUsed).toBeLessThanOrEqual(95_000); + client.disconnect(); + }); + }); + + describe("sendTokens", () => { + it("works with direct signer", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + const amount = coins(7890, "cony"); + const beneficiaryAddress = makeRandomAddress(); + const memo = "for dinner"; + + // no tokens here + + const before = await client.getBalance(beneficiaryAddress, "cony"); + expect(before).toEqual({ + denom: "cony", + amount: "0", + }); + + // send + const result = await client.sendTokens( + faucet.address0, + beneficiaryAddress, + amount, + defaultSendFee, + memo, + ); + assertIsDeliverTxSuccess(result); + expect(result.rawLog).toBeTruthy(); + + // got tokens + const after = await client.getBalance(beneficiaryAddress, "cony"); + expect(after).toEqual(amount[0]); + }); + + it("works with legacy Amino signer", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + const amount = coins(7890, "cony"); + const beneficiaryAddress = makeRandomAddress(); + const memo = "for dinner"; + + // no tokens here + const before = await client.getBalance(beneficiaryAddress, "cony"); + expect(before).toEqual({ + denom: "cony", + amount: "0", + }); + + // send + const result = await client.sendTokens( + faucet.address0, + beneficiaryAddress, + amount, + defaultSendFee, + memo, + ); + assertIsDeliverTxSuccess(result); + expect(result.rawLog).toBeTruthy(); + + // got tokens + const after = await client.getBalance(beneficiaryAddress, "cony"); + expect(after).toEqual(amount[0]); + }); + }); + + describe("sendIbcTokens", () => { + it("works with direct signing", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + const memo = "Cross-chain fun"; + const fee = { + amount: coins(2000, "cony"), + gas: "180000", // 180k + }; + + // both timeouts set + { + const result = await client.sendIbcTokens( + faucet.address0, + faucet.address1, + coin(1234, "cony"), + "fooPort", + "fooChannel", + { revisionHeight: Long.fromNumber(123), revisionNumber: Long.fromNumber(456) }, + Math.floor(Date.now() / 1000) + 60, + fee, + memo, + ); + // CheckTx must pass but the execution must fail in DeliverTx due to invalid channel/port + expect(isDeliverTxFailure(result)).toEqual(true); + } + + // no height timeout + { + const result = await client.sendIbcTokens( + faucet.address0, + faucet.address1, + coin(1234, "cony"), + "fooPort", + "fooChannel", + undefined, + Math.floor(Date.now() / 1000) + 60, + fee, + memo, + ); + // CheckTx must pass but the execution must fail in DeliverTx due to invalid channel/port + expect(isDeliverTxFailure(result)).toEqual(true); + } + }); + + it("works with Amino signing", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + const memo = "Cross-chain fun"; + const fee = { + amount: coins(2000, "cony"), + gas: "180000", // 180k + }; + + // both timeouts set + { + const result = await client.sendIbcTokens( + faucet.address0, + faucet.address1, + coin(1234, "cony"), + "fooPort", + "fooChannel", + { revisionHeight: Long.fromNumber(123), revisionNumber: Long.fromNumber(456) }, + Math.floor(Date.now() / 1000) + 60, + fee, + memo, + ); + // CheckTx must pass but the execution must fail in DeliverTx due to invalid channel/port + expect(isDeliverTxFailure(result)).toEqual(true); + } + + // no height timeout + { + const result = await client.sendIbcTokens( + faucet.address0, + faucet.address1, + coin(1234, "cony"), + "fooPort", + "fooChannel", + undefined, + Math.floor(Date.now() / 1000) + 60, + fee, + memo, + ); + // CheckTx must pass but the execution must fail in DeliverTx due to invalid channel/port + expect(isDeliverTxFailure(result)).toEqual(true); + } + }); + }); + + describe("upload", () => { + it("works", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const wasm = getHackatom().data; + const { codeId, originalChecksum, originalSize, compressedChecksum, compressedSize } = + await client.upload(faucet.address0, wasm, defaultUploadFee); + expect(originalChecksum).toEqual(toHex(sha256(wasm))); + expect(originalSize).toEqual(wasm.length); + expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/); + expect(compressedSize).toBeLessThan(wasm.length * 0.5); + expect(codeId).toBeGreaterThanOrEqual(1); + client.disconnect(); + }); + }); + + describe("instantiate", () => { + it("works with transfer amount", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const { codeId } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const funds = [coin(1234, "cony"), coin(321, "stake")]; + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress, height, gasWanted, gasUsed } = await client.instantiate( + faucet.address0, + codeId, + { + verifier: faucet.address0, + beneficiary: beneficiaryAddress, + }, + "My cool label", + defaultInstantiateFee, + { + memo: "Let's see if the memo is used", + funds: funds, + }, + ); + const wasmClient = await makeWasmClient(simapp.tendermintUrl); + const ucosmBalance = await wasmClient.bank.balance(contractAddress, "cony"); + const ustakeBalance = await wasmClient.bank.balance(contractAddress, "stake"); + expect(ucosmBalance).toEqual(funds[0]); + expect(ustakeBalance).toEqual(funds[1]); + expect(height).toBeGreaterThan(0); + expect(gasWanted).toBeGreaterThan(0); + expect(gasUsed).toBeGreaterThan(0); + client.disconnect(); + }); + + it("works with admin", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const { codeId } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress, height, gasWanted, gasUsed } = await client.instantiate( + faucet.address0, + codeId, + { + verifier: faucet.address0, + beneficiary: beneficiaryAddress, + }, + "My cool label", + defaultInstantiateFee, + { admin: unused.address }, + ); + const wasmClient = await makeWasmClient(simapp.tendermintUrl); + const { contractInfo } = await wasmClient.wasm.getContractInfo(contractAddress); + assert(contractInfo); + expect(height).toBeGreaterThan(0); + expect(gasWanted).toBeGreaterThan(0); + expect(gasUsed).toBeGreaterThan(0); + expect(contractInfo.admin).toEqual(unused.address); + client.disconnect(); + }); + + it("can instantiate one code multiple times", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const { codeId } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const { + contractAddress: address1, + height, + gasWanted, + gasUsed, + } = await client.instantiate( + faucet.address0, + codeId, + { + verifier: faucet.address0, + beneficiary: makeRandomAddress(), + }, + "contract 1", + defaultInstantiateFee, + ); + const { contractAddress: address2 } = await client.instantiate( + faucet.address0, + codeId, + { + verifier: faucet.address0, + beneficiary: makeRandomAddress(), + }, + "contract 2", + defaultInstantiateFee, + ); + expect(height).toBeGreaterThan(0); + expect(gasWanted).toBeGreaterThan(0); + expect(gasUsed).toBeGreaterThan(0); + expect(address1).not.toEqual(address2); + client.disconnect(); + }); + + it("works with legacy Amino signer", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + + // With admin + await client.instantiate( + faucet.address0, + deployedHackatom.codeId, + { + verifier: faucet.address0, + beneficiary: makeRandomAddress(), + }, + "contract 1", + defaultInstantiateFee, + { admin: makeRandomAddress() }, + ); + + // Without admin + await client.instantiate( + faucet.address0, + deployedHackatom.codeId, + { + verifier: faucet.address0, + beneficiary: makeRandomAddress(), + }, + "contract 1", + defaultInstantiateFee, + ); + + client.disconnect(); + }); + }); + + describe("uploadAndInstantiate", () => { + it("works with transfer amount", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const wasm = getHackatom().data; + const funds = [coin(1234, "cony"), coin(321, "stake")]; + const beneficiaryAddress = makeRandomAddress(); + const { originalSize, originalChecksum, compressedSize, compressedChecksum, codeId, contractAddress } = + await client.uploadAndInstantiate( + faucet.address0, + wasm, + { + verifier: faucet.address0, + beneficiary: beneficiaryAddress, + }, + "My Test label", + defaultUploadAndInstantiateFee, + { + memo: "Let's see if the memo is used", + funds: funds, + }, + ); + expect(originalChecksum).toEqual(toHex(sha256(wasm))); + expect(originalSize).toEqual(wasm.length); + expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/); + expect(compressedSize).toBeLessThan(wasm.length * 0.5); + expect(codeId).toBeGreaterThanOrEqual(1); + const wasmClient = await makeWasmClient(simapp.tendermintUrl); + const conyBalance = await wasmClient.bank.balance(contractAddress, "cony"); + expect(conyBalance).toEqual(funds[0]); + const stakeBalance = await wasmClient.bank.balance(contractAddress, "stake"); + expect(stakeBalance).toEqual(funds[1]); + client.disconnect(); + }); + + it("works with admin", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const wasm = getHackatom().data; + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress } = await client.uploadAndInstantiate( + faucet.address0, + wasm, + { verifier: faucet.address0, beneficiary: beneficiaryAddress }, + "My test label", + defaultUploadAndInstantiateFee, + { admin: unused.address }, + ); + const wasmClient = await makeWasmClient(simapp.tendermintUrl); + const { contractInfo } = await wasmClient.wasm.getContractInfo(contractAddress); + assert(contractInfo); + expect(contractInfo.admin).toEqual(unused.address); + client.disconnect(); + }); + }); + + describe("updateAdmin", () => { + it("can update an admin", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const { codeId } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress } = await client.instantiate( + faucet.address0, + codeId, + { + verifier: faucet.address0, + beneficiary: beneficiaryAddress, + }, + "My cool label", + defaultInstantiateFee, + + { + admin: faucet.address0, + }, + ); + const wasmClient = await makeWasmClient(simapp.tendermintUrl); + const { contractInfo: contractInfo1 } = await wasmClient.wasm.getContractInfo(contractAddress); + assert(contractInfo1); + expect(contractInfo1.admin).toEqual(faucet.address0); + + const { height, gasUsed, gasWanted } = await client.updateAdmin( + faucet.address0, + contractAddress, + unused.address, + defaultUpdateAdminFee, + ); + const { contractInfo: contractInfo2 } = await wasmClient.wasm.getContractInfo(contractAddress); + assert(contractInfo2); + expect(contractInfo2.admin).toEqual(unused.address); + expect(height).toBeGreaterThan(0); + expect(gasWanted).toBeGreaterThan(0); + expect(gasUsed).toBeGreaterThan(0); + client.disconnect(); + }); + }); + + describe("clearAdmin", () => { + it("can clear an admin", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const { codeId } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress } = await client.instantiate( + faucet.address0, + codeId, + { + verifier: faucet.address0, + beneficiary: beneficiaryAddress, + }, + "My cool label", + defaultInstantiateFee, + { + admin: faucet.address0, + }, + ); + const wasmClient = await makeWasmClient(simapp.tendermintUrl); + const { contractInfo: contractInfo1 } = await wasmClient.wasm.getContractInfo(contractAddress); + assert(contractInfo1); + expect(contractInfo1.admin).toEqual(faucet.address0); + + const { height, gasUsed, gasWanted } = await client.clearAdmin( + faucet.address0, + contractAddress, + defaultClearAdminFee, + ); + const { contractInfo: contractInfo2 } = await wasmClient.wasm.getContractInfo(contractAddress); + assert(contractInfo2); + expect(contractInfo2.admin).toEqual(""); + expect(height).toBeGreaterThan(0); + expect(gasWanted).toBeGreaterThan(0); + expect(gasUsed).toBeGreaterThan(0); + client.disconnect(); + }); + }); + + describe("migrate", () => { + it("works", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const { codeId: codeId1 } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const { codeId: codeId2 } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress } = await client.instantiate( + faucet.address0, + codeId1, + { + verifier: faucet.address0, + beneficiary: beneficiaryAddress, + }, + "My cool label", + defaultInstantiateFee, + { + admin: faucet.address0, + }, + ); + const wasmClient = await makeWasmClient(simapp.tendermintUrl); + const { contractInfo: contractInfo1 } = await wasmClient.wasm.getContractInfo(contractAddress); + assert(contractInfo1); + expect(contractInfo1.admin).toEqual(faucet.address0); + + const newVerifier = makeRandomAddress(); + const { height, gasUsed, gasWanted } = await client.migrate( + faucet.address0, + contractAddress, + codeId2, + { verifier: newVerifier }, + defaultMigrateFee, + ); + expect(height).toBeGreaterThan(0); + expect(gasWanted).toBeGreaterThan(0); + expect(gasUsed).toBeGreaterThan(0); + const { contractInfo: contractInfo2 } = await wasmClient.wasm.getContractInfo(contractAddress); + assert(contractInfo2); + expect({ ...contractInfo2 }).toEqual({ + ...contractInfo1, + codeId: Long.fromNumber(codeId2, true), + }); + + client.disconnect(); + }); + + it("works with legacy Amino signer", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const { codeId: codeId1 } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const { codeId: codeId2 } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress } = await client.instantiate( + faucet.address0, + codeId1, + { + verifier: faucet.address0, + beneficiary: beneficiaryAddress, + }, + "My cool label", + defaultInstantiateFee, + { admin: faucet.address0 }, + ); + const wasmClient = await makeWasmClient(simapp.tendermintUrl); + const { contractInfo: contractInfo1 } = await wasmClient.wasm.getContractInfo(contractAddress); + assert(contractInfo1); + expect(contractInfo1.admin).toEqual(faucet.address0); + + const newVerifier = makeRandomAddress(); + await client.migrate( + faucet.address0, + contractAddress, + codeId2, + { verifier: newVerifier }, + defaultMigrateFee, + ); + const { contractInfo: contractInfo2 } = await wasmClient.wasm.getContractInfo(contractAddress); + assert(contractInfo2); + expect({ ...contractInfo2 }).toEqual({ + ...contractInfo1, + codeId: Long.fromNumber(codeId2, true), + }); + + client.disconnect(); + }); + }); + + describe("execute", () => { + it("works", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const { codeId } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + // instantiate + const funds = [coin(233444, "cony"), coin(5454, "stake")]; + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress } = await client.instantiate( + faucet.address0, + codeId, + { + verifier: faucet.address0, + beneficiary: beneficiaryAddress, + }, + "amazing random contract", + defaultInstantiateFee, + { + funds: funds, + }, + ); + // execute + const result = await client.execute( + faucet.address0, + contractAddress, + { release: {} }, + defaultExecuteFee, + ); + expect(result.height).toBeGreaterThan(0); + expect(result.gasWanted).toBeGreaterThan(0); + expect(result.gasUsed).toBeGreaterThan(0); + const wasmEvent = result.logs[0].events.find((e) => e.type === "wasm"); + assert(wasmEvent, "Event of type wasm expected"); + expect(wasmEvent.attributes).toContain({ key: "action", value: "release" }); + expect(wasmEvent.attributes).toContain({ + key: "destination", + value: beneficiaryAddress, + }); + // Verify token transfer from contract to beneficiary + const wasmClient = await makeWasmClient(simapp.tendermintUrl); + const beneficiaryBalanceCony = await wasmClient.bank.balance(beneficiaryAddress, "cony"); + expect(beneficiaryBalanceCony).toEqual(funds[0]); + const beneficiaryBalanceStake = await wasmClient.bank.balance(beneficiaryAddress, "stake"); + expect(beneficiaryBalanceStake).toEqual(funds[1]); + const contractBalanceCony = await wasmClient.bank.balance(contractAddress, "cony"); + expect(contractBalanceCony).toEqual(coin(0, "cony")); + const contractBalanceStake = await wasmClient.bank.balance(contractAddress, "stake"); + expect(contractBalanceStake).toEqual(coin(0, "stake")); + + client.disconnect(); + }); + + it("works with legacy Amino signer", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const options = { ...defaultSigningClientOptions, prefix: simapp.prefix }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + const { codeId } = await client.upload(faucet.address0, getHackatom().data, defaultUploadFee); + // instantiate + const funds = [coin(233444, "cony"), coin(5454, "stake")]; + const beneficiaryAddress = makeRandomAddress(); + const { contractAddress } = await client.instantiate( + faucet.address0, + codeId, + { + verifier: faucet.address0, + beneficiary: beneficiaryAddress, + }, + "amazing random contract", + defaultInstantiateFee, + { + funds: funds, + }, + ); + // execute + const result = await client.execute( + faucet.address0, + contractAddress, + { release: {} }, + defaultExecuteFee, + ); + const wasmEvent = result.logs[0].events.find((e) => e.type === "wasm"); + assert(wasmEvent, "Event of type wasm expected"); + expect(wasmEvent.attributes).toContain({ key: "action", value: "release" }); + expect(wasmEvent.attributes).toContain({ + key: "destination", + value: beneficiaryAddress, + }); + // Verify token transfer from contract to beneficiary + const wasmClient = await makeWasmClient(simapp.tendermintUrl); + const beneficiaryBalanceCony = await wasmClient.bank.balance(beneficiaryAddress, "cony"); + expect(beneficiaryBalanceCony).toEqual(funds[0]); + const beneficiaryBalanceStake = await wasmClient.bank.balance(beneficiaryAddress, "stake"); + expect(beneficiaryBalanceStake).toEqual(funds[1]); + const contractBalanceCony = await wasmClient.bank.balance(contractAddress, "cony"); + expect(contractBalanceCony).toEqual(coin(0, "cony")); + const contractBalanceStake = await wasmClient.bank.balance(contractAddress, "stake"); + expect(contractBalanceStake).toEqual(coin(0, "stake")); + + client.disconnect(); + }); + }); + + describe("signAndBroadcast", () => { + describe("direct mode", () => { + it("works", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + }); + const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; + + const msg = MsgDelegate.fromPartial({ + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "stake"), + }); + const msgAny: MsgDelegateEncodeObject = { + typeUrl: msgDelegateTypeUrl, + value: msg, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "180000", // 180k + }; + const memo = "Use your power wisely"; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + + it("returns DeliverTxFailure on DeliverTx failure", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + const msg = MsgSend.fromPartial({ + fromAddress: faucet.address0, + toAddress: makeRandomAddress(), + amount: coins(Number.MAX_SAFE_INTEGER, "stake"), + }); + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msg, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "99000", + }; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee); + assertIsDeliverTxFailure(result); + expect(result.code).toBeGreaterThan(0); + expect(result.gasWanted).toEqual(99_000); + expect(result.gasUsed).toBeLessThanOrEqual(99_000); + // todo: I don't know why fail. + expect(result.gasUsed).toBeGreaterThan(28_000); + }); + + it("works with auto gas", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + gasPrice: defaultGasPrice, + }); + const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; + + const msg = MsgDelegate.fromPartial({ + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "stake"), + }); + const msgAny: MsgDelegateEncodeObject = { + typeUrl: msgDelegateTypeUrl, + value: msg, + }; + const memo = "Use your power wisely"; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], "auto", memo); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + + it("works with a modifying signer", async () => { + pendingWithoutSimapp(); + const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + }); + const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; + + const msg = MsgDelegate.fromPartial({ + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "stake"), + }); + const msgAny: MsgDelegateEncodeObject = { + typeUrl: msgDelegateTypeUrl, + value: msg, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "180000", // 180k + }; + const memo = "Use your power wisely"; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo); + assertIsDeliverTxSuccess(result); + + await sleep(500); + + const searchResult = await client.getTx(result.transactionHash); + assert(searchResult, "Must find transaction"); + const tx = decodeTxRaw(searchResult.tx); + // From ModifyingDirectSecp256k1HdWallet + expect(tx.body.memo).toEqual("This was modified"); + expect({ ...tx.authInfo.fee!.amount[0] }).toEqual(coin(3000, "cony")); + expect(tx.authInfo.fee!.gasLimit.toNumber()).toEqual(333333); + + client.disconnect(); + }); + }); + + describe("legacy Amino mode", () => { + it("works with bank MsgSend", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + }); + + const msgSend: MsgSend = { + fromAddress: faucet.address0, + toAddress: makeRandomAddress(), + amount: coins(1234, "cony"), + }; + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msgSend, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "200000", + }; + const memo = "Use your tokens wisely"; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + + it("works with staking MsgDelegate", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + aminoTypes: new AminoTypes(createStakingAminoConverters(simapp.prefix)), + prefix: simapp.prefix, + }); + + const msgDelegate: MsgDelegate = { + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "stake"), + }; + const msgAny: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msgDelegate, + }; + const fee = { + amount: coins(2000, "stake"), + gas: "200000", + }; + const memo = "Use your tokens wisely"; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + + it("works with wasm MsgStoreCode", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + }); + const { data } = getHackatom(); + + const msgStoreCode: MsgStoreCode = { + sender: faucet.address0, + wasmByteCode: pako.gzip(data), + instantiatePermission: undefined, + }; + const msgAny: MsgStoreCodeEncodeObject = { + typeUrl: "/cosmwasm.wasm.v1.MsgStoreCode", + value: msgStoreCode, + }; + const fee = { + amount: coins(2000, "stake"), + gas: "1500000", + }; + const memo = "Use your tokens wisely"; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + + it("works with a custom registry and custom message", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + + const customRegistry = new Registry(); + const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; + interface CustomMsgDelegate { + customDelegatorAddress?: string; + customValidatorAddress?: string; + customAmount?: Coin; + } + const baseCustomMsgDelegate: CustomMsgDelegate = { + customDelegatorAddress: "", + customValidatorAddress: "", + }; + const CustomMsgDelegate = { + // Adapted from autogenerated MsgDelegate implementation + encode( + message: CustomMsgDelegate, + writer: protobuf.Writer = protobuf.Writer.create(), + ): protobuf.Writer { + writer.uint32(10).string(message.customDelegatorAddress ?? ""); + writer.uint32(18).string(message.customValidatorAddress ?? ""); + if (message.customAmount !== undefined) { + Coin.encode(message.customAmount, writer.uint32(26).fork()).ldelim(); + } + return writer; + }, + + decode(): CustomMsgDelegate { + throw new Error("decode method should not be required"); + }, + + fromJSON(): CustomMsgDelegate { + throw new Error("fromJSON method should not be required"); + }, + + fromPartial(object: DeepPartial): CustomMsgDelegate { + const message = { ...baseCustomMsgDelegate } as CustomMsgDelegate; + if (object.customDelegatorAddress !== undefined && object.customDelegatorAddress !== null) { + message.customDelegatorAddress = object.customDelegatorAddress; + } else { + message.customDelegatorAddress = ""; + } + if (object.customValidatorAddress !== undefined && object.customValidatorAddress !== null) { + message.customValidatorAddress = object.customValidatorAddress; + } else { + message.customValidatorAddress = ""; + } + if (object.customAmount !== undefined && object.customAmount !== null) { + message.customAmount = Coin.fromPartial(object.customAmount); + } else { + message.customAmount = undefined; + } + return message; + }, + + toJSON(): unknown { + throw new Error("toJSON method should not be required"); + }, + }; + customRegistry.register(msgDelegateTypeUrl, CustomMsgDelegate); + const customAminoTypes = new AminoTypes({ + "/cosmos.staking.v1beta1.MsgDelegate": { + aminoType: "cosmos-sdk/MsgDelegate", + toAmino: ({ + customDelegatorAddress, + customValidatorAddress, + customAmount, + }: CustomMsgDelegate): AminoMsgDelegate["value"] => { + assert(customDelegatorAddress, "missing customDelegatorAddress"); + assert(customValidatorAddress, "missing validatorAddress"); + assert(customAmount, "missing amount"); + assert(customAmount.amount, "missing amount.amount"); + assert(customAmount.denom, "missing amount.denom"); + return { + delegator_address: customDelegatorAddress, + validator_address: customValidatorAddress, + amount: { + amount: customAmount.amount, + denom: customAmount.denom, + }, + }; + }, + fromAmino: ({ + delegator_address, + validator_address, + amount, + }: AminoMsgDelegate["value"]): CustomMsgDelegate => ({ + customDelegatorAddress: delegator_address, + customValidatorAddress: validator_address, + customAmount: Coin.fromPartial(amount), + }), + }, + }); + const options = { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + registry: customRegistry, + aminoTypes: customAminoTypes, + }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + + const msg = { + customDelegatorAddress: faucet.address0, + customValidatorAddress: validator.validatorAddress, + customAmount: coin(1234, "stake"), + }; + const msgAny = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "200000", + }; + const memo = "Use your power wisely"; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + + it("works with a modifying signer", async () => { + pendingWithoutSimapp(); + const wallet = await ModifyingSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + aminoTypes: new AminoTypes(createStakingAminoConverters(simapp.prefix)), + prefix: simapp.prefix, + }); + + const msg = { + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "stake"), + }; + const msgAny: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "200000", + }; + const memo = "Use your power wisely"; + const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo); + assertIsDeliverTxSuccess(result); + + await sleep(500); + + const searchResult = await client.getTx(result.transactionHash); + assert(searchResult, "Must find transaction"); + const tx = decodeTxRaw(searchResult.tx); + // From ModifyingSecp256k1HdWallet + expect(tx.body.memo).toEqual("This was modified"); + expect({ ...tx.authInfo.fee!.amount[0] }).toEqual(coin(3000, "cony")); + expect(tx.authInfo.fee!.gasLimit.toNumber()).toEqual(333333); + + client.disconnect(); + }); + }); + }); + + describe("sign", () => { + describe("direct mode", () => { + it("works", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + }); + + const msg = MsgDelegate.fromPartial({ + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "stake"), + }); + const msgAny: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "180000", // 180k + }; + const memo = "Use your power wisely"; + const signed = await client.sign(faucet.address0, [msgAny], fee, memo); + + // ensure signature is valid + const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + + it("works with a modifying signer", async () => { + pendingWithoutSimapp(); + const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + }); + + const msg = MsgDelegate.fromPartial({ + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "stake"), + }); + const msgAny: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "180000", // 180k + }; + const memo = "Use your power wisely"; + const signed = await client.sign(faucet.address0, [msgAny], fee, memo); + + const body = TxBody.decode(signed.bodyBytes); + const authInfo = AuthInfo.decode(signed.authInfoBytes); + // From ModifyingDirectSecp256k1HdWallet + expect(body.memo).toEqual("This was modified"); + expect({ ...authInfo.fee!.amount[0] }).toEqual(coin(3000, "cony")); + expect(authInfo.fee!.gasLimit.toNumber()).toEqual(333333); + + // ensure signature is valid + const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + }); + + describe("legacy Amino mode", () => { + it("works with bank MsgSend", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + }); + + const msgSend: MsgSend = { + fromAddress: faucet.address0, + toAddress: makeRandomAddress(), + amount: coins(1234, "cony"), + }; + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msgSend, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "200000", + }; + const memo = "Use your tokens wisely"; + const signed = await client.sign(faucet.address0, [msgAny], fee, memo); + + // ensure signature is valid + const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + + it("works with staking MsgDelegate", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + aminoTypes: new AminoTypes(createStakingAminoConverters(simapp.prefix)), + prefix: simapp.prefix, + }); + + const msgDelegate: MsgDelegate = { + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "stake"), + }; + const msgAny: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msgDelegate, + }; + const fee = { + amount: coins(2000, "stake"), + gas: "200000", + }; + const memo = "Use your tokens wisely"; + const signed = await client.sign(faucet.address0, [msgAny], fee, memo); + + // ensure signature is valid + const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + + it("works with a custom registry and custom message", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + + const customRegistry = new Registry(); + const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; + interface CustomMsgDelegate { + customDelegatorAddress?: string; + customValidatorAddress?: string; + customAmount?: Coin; + } + const baseCustomMsgDelegate: CustomMsgDelegate = { + customDelegatorAddress: "", + customValidatorAddress: "", + }; + const CustomMsgDelegate = { + // Adapted from autogenerated MsgDelegate implementation + encode( + message: CustomMsgDelegate, + writer: protobuf.Writer = protobuf.Writer.create(), + ): protobuf.Writer { + writer.uint32(10).string(message.customDelegatorAddress ?? ""); + writer.uint32(18).string(message.customValidatorAddress ?? ""); + if (message.customAmount !== undefined && message.customAmount !== undefined) { + Coin.encode(message.customAmount, writer.uint32(26).fork()).ldelim(); + } + return writer; + }, + + decode(): CustomMsgDelegate { + throw new Error("decode method should not be required"); + }, + + fromJSON(): CustomMsgDelegate { + throw new Error("fromJSON method should not be required"); + }, + + fromPartial(object: DeepPartial): CustomMsgDelegate { + const message = { ...baseCustomMsgDelegate } as CustomMsgDelegate; + if (object.customDelegatorAddress !== undefined && object.customDelegatorAddress !== null) { + message.customDelegatorAddress = object.customDelegatorAddress; + } else { + message.customDelegatorAddress = ""; + } + if (object.customValidatorAddress !== undefined && object.customValidatorAddress !== null) { + message.customValidatorAddress = object.customValidatorAddress; + } else { + message.customValidatorAddress = ""; + } + if (object.customAmount !== undefined && object.customAmount !== null) { + message.customAmount = Coin.fromPartial(object.customAmount); + } else { + message.customAmount = undefined; + } + return message; + }, + + toJSON(): unknown { + throw new Error("toJSON method should not be required"); + }, + }; + customRegistry.register(msgDelegateTypeUrl, CustomMsgDelegate); + const customAminoTypes = new AminoTypes({ + "/cosmos.staking.v1beta1.MsgDelegate": { + aminoType: "cosmos-sdk/MsgDelegate", + toAmino: ({ + customDelegatorAddress, + customValidatorAddress, + customAmount, + }: CustomMsgDelegate): AminoMsgDelegate["value"] => { + assert(customDelegatorAddress, "missing customDelegatorAddress"); + assert(customValidatorAddress, "missing validatorAddress"); + assert(customAmount, "missing amount"); + return { + delegator_address: customDelegatorAddress, + validator_address: customValidatorAddress, + amount: { + amount: customAmount.amount, + denom: customAmount.denom, + }, + }; + }, + fromAmino: ({ + delegator_address, + validator_address, + amount, + }: AminoMsgDelegate["value"]): CustomMsgDelegate => ({ + customDelegatorAddress: delegator_address, + customValidatorAddress: validator_address, + customAmount: Coin.fromPartial(amount), + }), + }, + }); + const options = { + ...defaultSigningClientOptions, + registry: customRegistry, + aminoTypes: customAminoTypes, + }; + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options); + + const msg: CustomMsgDelegate = { + customDelegatorAddress: faucet.address0, + customValidatorAddress: validator.validatorAddress, + customAmount: coin(1234, "stake"), + }; + const msgAny = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "200000", + }; + const memo = "Use your power wisely"; + const signed = await client.sign(faucet.address0, [msgAny], fee, memo); + + // ensure signature is valid + const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + + it("works with a modifying signer", async () => { + pendingWithoutSimapp(); + const wallet = await ModifyingSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(0)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + aminoTypes: new AminoTypes(createStakingAminoConverters(simapp.prefix)), + prefix: simapp.prefix, + }); + + const msg: MsgDelegate = { + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(1234, "stake"), + }; + const msgAny: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }; + const fee = { + amount: coins(2000, "cony"), + gas: "200000", + }; + const memo = "Use your power wisely"; + const signed = await client.sign(faucet.address0, [msgAny], fee, memo); + + const body = TxBody.decode(signed.bodyBytes); + const authInfo = AuthInfo.decode(signed.authInfoBytes); + // From ModifyingSecp256k1HdWallet + expect(body.memo).toEqual("This was modified"); + expect({ ...authInfo.fee!.amount[0] }).toEqual(coin(3000, "cony")); + expect(authInfo.fee!.gasLimit.toNumber()).toEqual(333333); + + // ensure signature is valid + const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); + }); + }); +}); diff --git a/packages/finschia/src/testutils.spec.ts b/packages/finschia/src/testutils.spec.ts index 080a6382c..b189e35e8 100644 --- a/packages/finschia/src/testutils.spec.ts +++ b/packages/finschia/src/testutils.spec.ts @@ -1,54 +1,46 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { AminoSignResponse, Secp256k1HdWallet, Secp256k1HdWalletOptions, StdSignDoc } from "@cosmjs/amino"; import { Bip39, EnglishMnemonic, Random } from "@cosmjs/crypto"; -import { toBech32 } from "@cosmjs/encoding"; +import { fromBase64, toBech32 } from "@cosmjs/encoding"; import { - coins, DirectSecp256k1HdWallet, DirectSecp256k1HdWalletOptions, DirectSignResponse, makeAuthInfoBytes, } from "@cosmjs/proto-signing"; -import { calculateFee, GasPrice, SigningStargateClientOptions } from "@cosmjs/stargate"; +import { + AuthExtension, + BankExtension, + calculateFee, + coins, + GasPrice, + QueryClient, + setupAuthExtension, + setupBankExtension, +} from "@cosmjs/stargate"; +import { SigningStargateClientOptions } from "@cosmjs/stargate"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; import { AuthInfo, SignDoc, TxBody } from "cosmjs-types/cosmos/tx/v1beta1/tx"; -export function simappEnabled(): boolean { - return !!process.env.SIMAPP_ENABLED; -} - -export function pendingWithoutSimapp(): void { - if (!simappEnabled()) { - return pending("Set SIMAPP42_ENABLED or SIMAPP44_ENABLED to enable Simapp based tests"); - } -} - -export function slowSimappEnabled(): boolean { - return !!process.env.SIMAPP_ENABLED; -} - -export function pendingWithoutSlowSimapp(): void { - if (!slowSimappEnabled()) { - return pending("Set SLOW_SIMAPP42_ENABLED or SLOW_SIMAPP44_ENABLED to enable slow Simapp based tests"); - } -} - -export function makeRandomAddressBytes(): Uint8Array { - return Random.getBytes(20); -} - -export function makeRandomAddress(): string { - return toBech32("link", makeRandomAddressBytes()); -} - -/** Returns first element. Throws if array has a different length than 1. */ -export function fromOneElementArray(elements: ArrayLike): T { - if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`); - return elements[0]; -} +import { setupWasmExtension, WasmExtension } from "./modules"; +import hackatom from "./testdata/contract.json"; export const defaultGasPrice = GasPrice.fromString("0.025cony"); export const defaultSendFee = calculateFee(100_000, defaultGasPrice); +export const defaultUploadFee = calculateFee(1_500_000, defaultGasPrice); +export const defaultInstantiateFee = calculateFee(500_000, defaultGasPrice); +export const defaultUploadAndInstantiateFee = calculateFee(2_000_000, defaultGasPrice); +export const defaultExecuteFee = calculateFee(200_000, defaultGasPrice); +export const defaultMigrateFee = calculateFee(200_000, defaultGasPrice); +export const defaultUpdateAdminFee = calculateFee(80_000, defaultGasPrice); +export const defaultClearAdminFee = calculateFee(80_000, defaultGasPrice); + +/** An internal testing type. SigningCosmWasmClient has a similar but different interface */ +export interface ContractUploadInstructions { + /** The wasm bytecode */ + readonly data: Uint8Array; +} export const simapp = { tendermintUrl: "localhost:26658", @@ -61,26 +53,34 @@ export const simapp = { blockTime: 1_000, // ms totalSupply: 1100000000000, // cony govMinDeposit: coins(10000000, "stake"), -}; - -export const slowSimapp = { - tendermintUrl: "localhost:26660", - tendermintUrlWs: "ws://localhost:26660", - tendermintUrlHttp: "http://localhost:26660", - chainId: "simd-testing", - denomStaking: "ustake", - denomFee: "cony", - blockTime: 10_000, // ms - totalSupply: 21000000000, // cony + validator: { + address: "linkvaloper146asaycmtydq45kxc8evntqfgepagygeddajpy", + }, }; /** Setting to speed up testing */ export const defaultSigningClientOptions: SigningStargateClientOptions = { broadcastPollIntervalMs: 300, broadcastTimeoutMs: 8_000, - gasPrice: GasPrice.fromString("0.01cony"), }; +export function getHackatom(): ContractUploadInstructions { + return { + data: fromBase64(hackatom.data), + }; +} + +export function makeRandomAddress(): string { + return toBech32("link", Random.getBytes(20)); +} + +export const tendermintIdMatcher = /^[0-9A-F]{64}$/; +/** @see https://rgxdb.com/r/1NUN74O6 */ +export const base64Matcher = + /^(?:[a-zA-Z0-9+/]{4})*(?:|(?:[a-zA-Z0-9+/]{3}=)|(?:[a-zA-Z0-9+/]{2}==)|(?:[a-zA-Z0-9+/]{1}===))$/; +// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 +export const bech32AddressMatcher = /^[\x21-\x7e]{1,83}1[02-9ac-hj-np-z]{38,58}$/; + export const faucet = { mnemonic: "mind flame tobacco sense move hammer drift crime ring globe art gaze cinnamon helmet cruise special produce notable negative wait path scrap recall have", @@ -120,7 +120,7 @@ export const faucet = { export const unused = { pubkey: { type: "tendermint/PubKeySecp256k1", - value: "AlPkJfV+nWxUCb2mPdMLSb/G9zNvywDir8CgxpAUoPjE", + value: "A7Tvuh48+JzNyBnTeK2Qw987f5FqFHK/QH65pTVsZvuh", }, address: "link1g7gsgktl9yjqatacswlwvns5yzy4u5jehsx2pz", accountNumber: 8, @@ -163,8 +163,61 @@ export const validator = { export const nonExistentAddress = "link1hvuxwh9sp2zlc3ee5nnhngln6auv4ak4kyuspq"; -export const nonNegativeIntegerMatcher = /^[0-9]+$/; -export const tendermintIdMatcher = /^[0-9A-F]{64}$/; +/** Deployed as part of scripts/wasmd/init.sh */ +export const deployedHackatom = { + codeId: 1, + checksum: "470c5b703a682f778b8b088d48169b8d6e43f7f44ac70316692cdbe69e6605e3", + instances: [ + { + beneficiary: faucet.address0, + address: "link14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sgf2vn8", + label: "From deploy_hackatom.js (0)", + }, + { + beneficiary: faucet.address1, + address: "link1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrshuxemw", + label: "From deploy_hackatom.js (1)", + }, + { + beneficiary: faucet.address2, + address: "link1yyca08xqdgvjz0psg56z67ejh9xms6l436u8y58m82npdqqhmmtq6cjue5", + label: "From deploy_hackatom.js (2)", + }, + ], +}; + +/** Deployed as part of scripts/wasmd/init.sh */ +export const deployedIbcReflect = { + codeId: 2, + instances: [ + { + address: "link1aakfpghcanxtc45gpqlx8j3rq0zcpyf49qmhm9mdjrfx036h4z5s782d42", + ibcPortId: "wasm.link1aakfpghcanxtc45gpqlx8j3rq0zcpyf49qmhm9mdjrfx036h4z5s782d42", + }, + ], +}; +export function simappEnabled(): boolean { + return !!process.env.SIMAPP_ENABLED; +} + +export function pendingWithoutSimapp(): void { + if (!simappEnabled()) { + return pending("Set SIMAPP_ENABLED to enable simapp-based tests"); + } +} + +/** Returns first element. Throws if array has a different length than 1. */ +export function fromOneElementArray(elements: ArrayLike): T { + if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`); + return elements[0]; +} + +export async function makeWasmClient( + endpoint: string, +): Promise { + const tmClient = await Tendermint34Client.connect(endpoint); + return QueryClient.withExtensions(tmClient, setupAuthExtension, setupBankExtension, setupWasmExtension); +} /** * A class for testing clients using an Amino signer which modifies the transaction it receives before signing From a69a894992561ba66e1cfc4498b5dc4c50f6476f Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Thu, 8 Sep 2022 10:52:56 +0900 Subject: [PATCH 05/15] chore: run delegate before get staking balance test --- packages/finschia/src/finschiaclient.spec.ts | 22 ++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/finschia/src/finschiaclient.spec.ts b/packages/finschia/src/finschiaclient.spec.ts index fcba109ba..ebbec7924 100644 --- a/packages/finschia/src/finschiaclient.spec.ts +++ b/packages/finschia/src/finschiaclient.spec.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { coin, Secp256k1HdWallet } from "@cosmjs/amino"; import { Code } from "@cosmjs/cosmwasm-stargate"; import { sha256 } from "@cosmjs/crypto"; import { fromAscii, fromBase64, fromHex, toAscii, toBase64 } from "@cosmjs/encoding"; @@ -29,6 +30,7 @@ import { makeLinkPath } from "./paths"; import { SigningFinschiaClient } from "./signingfinschiaclient"; import { defaultInstantiateFee, + defaultSigningClientOptions, defaultUploadFee, deployedHackatom, deployedIbcReflect, @@ -367,12 +369,28 @@ describe("FinschiaClient", () => { }); describe("getBalanceStaked", () => { + beforeAll(async () => { + pendingWithoutSimapp(); + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(4)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + }); + await client.delegateTokens(faucet.address4, validator.validatorAddress, coin(1234, "stake"), { + amount: coins(2000, "cony"), + gas: "200000", + }); + }); it("works", async () => { pendingWithoutSimapp(); const client = await FinschiaClient.connect(simapp.tendermintUrl); - const response = await client.getBalanceStaked(faucet.address0); + const response = await client.getBalanceStaked(faucet.address4); - expect(response).toEqual({ denom: "stake", amount: "63474" }); + expect(response).toEqual({ denom: "stake", amount: "1234" }); }); }); From b295ca5884db3909be8a832f136ff8cd0c529d74 Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Thu, 8 Sep 2022 17:56:29 +0900 Subject: [PATCH 06/15] fix: change not to use pending in beforeall --- packages/finschia/src/finschiaclient.spec.ts | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/finschia/src/finschiaclient.spec.ts b/packages/finschia/src/finschiaclient.spec.ts index ebbec7924..68801a130 100644 --- a/packages/finschia/src/finschiaclient.spec.ts +++ b/packages/finschia/src/finschiaclient.spec.ts @@ -370,20 +370,20 @@ describe("FinschiaClient", () => { describe("getBalanceStaked", () => { beforeAll(async () => { - pendingWithoutSimapp(); - pendingWithoutSimapp(); - const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPaths: [makeLinkPath(4)], - prefix: simapp.prefix, - }); - const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { - ...defaultSigningClientOptions, - prefix: simapp.prefix, - }); - await client.delegateTokens(faucet.address4, validator.validatorAddress, coin(1234, "stake"), { - amount: coins(2000, "cony"), - gas: "200000", - }); + if (simappEnabled()) { + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeLinkPath(4)], + prefix: simapp.prefix, + }); + const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, { + ...defaultSigningClientOptions, + prefix: simapp.prefix, + }); + await client.delegateTokens(faucet.address4, validator.validatorAddress, coin(1234, "stake"), { + amount: coins(2000, "cony"), + gas: "200000", + }); + } }); it("works", async () => { pendingWithoutSimapp(); From d1203f6890c99b2a2f4777ed222d5ee8565bf7d3 Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Thu, 8 Sep 2022 18:09:14 +0900 Subject: [PATCH 07/15] chore: add run test github action for finschia --- .github/workflows/finschia-test.yml | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/finschia-test.yml diff --git a/.github/workflows/finschia-test.yml b/.github/workflows/finschia-test.yml new file mode 100644 index 000000000..09b8196c5 --- /dev/null +++ b/.github/workflows/finschia-test.yml @@ -0,0 +1,49 @@ +name: Test + +on: + pull_request: + push: + branches: + - main + +jobs: + tests: + name: Run tests + runs-on: self-hosted + + steps: + - name: Check out Git repository + uses: actions/checkout@v3 + + - name: download contracts + run: | + cd scripts/simapp/contracts + bash download.sh + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "16" + - run: if [ ! -x "$(command -v yarn)" ]; then npm install -g yarn; fi + + - name: Version information + run: echo "node $(node --version)"; echo "yarn $(yarn --version)" + + - name: Install dependencies + run: | + yarn cache clean --all + yarn install + + - name: Build + run: yarn build + + - name: start simd + run: CI= scripts/simapp/start.sh + + - name: init contract on simd + run: scripts/simapp/init.sh + + - name: Unit Test + run: | + cd packages/finschia + SIMAPP_ENABLED=true yarn run test From f2f64899890aafbeaec9e1d3e41a2367506876df Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Thu, 8 Sep 2022 18:20:29 +0900 Subject: [PATCH 08/15] chore: change action name --- .github/workflows/finschia-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/finschia-test.yml b/.github/workflows/finschia-test.yml index 09b8196c5..e81f7ec15 100644 --- a/.github/workflows/finschia-test.yml +++ b/.github/workflows/finschia-test.yml @@ -1,4 +1,4 @@ -name: Test +name: Finschia-Test on: pull_request: From 85b9f4a3e83b4a60cc8ea5ec1ea99e96542fb918 Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Fri, 16 Sep 2022 18:18:59 +0900 Subject: [PATCH 09/15] chore: add contributor in finschia package --- packages/finschia/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/finschia/package.json b/packages/finschia/package.json index 94ef9e2a9..56e213a18 100644 --- a/packages/finschia/package.json +++ b/packages/finschia/package.json @@ -4,7 +4,8 @@ "description": "Utilities for LBM SDK 0.45.0-rc7", "contributors": [ "Simon Warta ", - "zemyblue " + "zemyblue ", + "loin3 " ], "license": "Apache-2.0", "main": "build/index.js", From d1232cb7438c5aed9ef272648e35ca660e314856 Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Mon, 19 Sep 2022 22:47:00 +0900 Subject: [PATCH 10/15] chore: delete modules duplicated with cosmjs --- .../src/modules/evidence/aminomessages.ts | 28 -- .../src/modules/feegrant/aminomessages.ts | 8 - .../src/modules/ibc/aminomessages.spec.ts | 184 --------- .../finschia/src/modules/ibc/aminomessages.ts | 113 ------ .../src/modules/ibc/ibctestdata.spec.ts | 112 ------ packages/finschia/src/modules/ibc/messages.ts | 11 +- .../finschia/src/modules/ibc/queries.spec.ts | 2 +- packages/finschia/src/modules/index.ts | 31 +- .../src/modules/wasm/aminomessages.spec.ts | 356 ------------------ .../src/modules/wasm/aminomessages.ts | 242 ------------ .../finschia/src/modules/wasm/messages.ts | 58 --- .../finschia/src/modules/wasm/queries.spec.ts | 12 +- .../src/signingfinschiaclient.spec.ts | 2 +- .../finschia/src/signingfinschiaclient.ts | 33 +- 14 files changed, 25 insertions(+), 1167 deletions(-) delete mode 100644 packages/finschia/src/modules/evidence/aminomessages.ts delete mode 100644 packages/finschia/src/modules/feegrant/aminomessages.ts delete mode 100644 packages/finschia/src/modules/ibc/aminomessages.spec.ts delete mode 100644 packages/finschia/src/modules/ibc/aminomessages.ts delete mode 100644 packages/finschia/src/modules/ibc/ibctestdata.spec.ts delete mode 100644 packages/finschia/src/modules/wasm/aminomessages.spec.ts delete mode 100644 packages/finschia/src/modules/wasm/aminomessages.ts diff --git a/packages/finschia/src/modules/evidence/aminomessages.ts b/packages/finschia/src/modules/evidence/aminomessages.ts deleted file mode 100644 index 7542f01a8..000000000 --- a/packages/finschia/src/modules/evidence/aminomessages.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { AminoMsg } from "@cosmjs/amino"; -import { AminoConverters } from "@cosmjs/stargate"; - -// See https://github.com/cosmos/cosmos-sdk/blob/v0.45.1/proto/cosmos/evidence/v1beta1/tx.proto - -interface Any { - readonly type_url: string; - readonly value: Uint8Array; -} - -/** Supports submitting arbitrary evidence */ -export interface AminoMsgSubmitEvidence extends AminoMsg { - readonly type: "cosmos-sdk/MsgSubmitEvidence"; - readonly value: { - /** Bech32 account address */ - readonly submitter: string; - readonly evidence: Any; - }; -} - -export function isAminoMsgSubmitEvidence(msg: AminoMsg): msg is AminoMsgSubmitEvidence { - return msg.type === "cosmos-sdk/MsgSubmitEvidence"; -} - -export function createEvidenceAminoConverters(): AminoConverters { - throw new Error("Not implemented"); -} diff --git a/packages/finschia/src/modules/feegrant/aminomessages.ts b/packages/finschia/src/modules/feegrant/aminomessages.ts deleted file mode 100644 index 7f30bf579..000000000 --- a/packages/finschia/src/modules/feegrant/aminomessages.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { AminoConverters } from "@cosmjs/stargate"; - -export function createFreegrantAminoConverters(): AminoConverters { - return { - "/cosmos.feegrant.v1beta1.MsgGrantAllowance": "not_supported_by_chain", - "/cosmos.feegrant.v1beta1.MsgRevokeAllowance": "not_supported_by_chain", - }; -} diff --git a/packages/finschia/src/modules/ibc/aminomessages.spec.ts b/packages/finschia/src/modules/ibc/aminomessages.spec.ts deleted file mode 100644 index 8a2835eeb..000000000 --- a/packages/finschia/src/modules/ibc/aminomessages.spec.ts +++ /dev/null @@ -1,184 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { coin } from "@cosmjs/proto-signing"; -import { AminoTypes } from "@cosmjs/stargate"; -import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; -import Long from "long"; - -import { AminoMsgTransfer, createIbcAminoConverters } from "./aminomessages"; - -describe("AminoTypes", () => { - describe("toAmino", () => { - it("works for MsgTransfer", () => { - const msg: MsgTransfer = { - sourcePort: "testport", - sourceChannel: "testchannel", - token: coin(1234, "utest"), - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - timeoutHeight: { - revisionHeight: Long.fromString("123", true), - revisionNumber: Long.fromString("456", true), - }, - timeoutTimestamp: Long.fromString("789", true), - }; - const aminoTypes = new AminoTypes(createIbcAminoConverters()); - const aminoMsg = aminoTypes.toAmino({ - typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", - value: msg, - }); - const expected: AminoMsgTransfer = { - type: "cosmos-sdk/MsgTransfer", - value: { - source_port: "testport", - source_channel: "testchannel", - token: coin(1234, "utest"), - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - timeout_height: { - revision_height: "123", - revision_number: "456", - }, - timeout_timestamp: "789", - }, - }; - expect(aminoMsg).toEqual(expected); - }); - - it("works for MsgTransfer with empty values", () => { - const msg: MsgTransfer = { - sourcePort: "testport", - sourceChannel: "testchannel", - token: coin(1234, "utest"), - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - timeoutHeight: { - revisionHeight: Long.UZERO, - revisionNumber: Long.UZERO, - }, - timeoutTimestamp: Long.UZERO, - }; - const aminoTypes = new AminoTypes(createIbcAminoConverters()); - const aminoMsg = aminoTypes.toAmino({ - typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", - value: msg, - }); - const expected: AminoMsgTransfer = { - type: "cosmos-sdk/MsgTransfer", - value: { - source_port: "testport", - source_channel: "testchannel", - token: coin(1234, "utest"), - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - timeout_height: { - revision_height: undefined, - revision_number: undefined, - }, - timeout_timestamp: undefined, - }, - }; - expect(aminoMsg).toEqual(expected); - }); - - it("works for MsgTransfer with no height timeout", () => { - const msg: MsgTransfer = { - sourcePort: "testport", - sourceChannel: "testchannel", - token: coin(1234, "utest"), - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - timeoutHeight: undefined, - timeoutTimestamp: Long.UZERO, - }; - const aminoMsg = new AminoTypes(createIbcAminoConverters()).toAmino({ - typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", - value: msg, - }); - const expected: AminoMsgTransfer = { - type: "cosmos-sdk/MsgTransfer", - value: { - source_port: "testport", - source_channel: "testchannel", - token: coin(1234, "utest"), - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - timeout_height: {}, - timeout_timestamp: undefined, - }, - }; - expect(aminoMsg).toEqual(expected); - }); - }); - - describe("fromAmino", () => { - it("works for MsgTransfer", () => { - const aminoMsg: AminoMsgTransfer = { - type: "cosmos-sdk/MsgTransfer", - value: { - source_port: "testport", - source_channel: "testchannel", - token: coin(1234, "utest"), - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - timeout_height: { - revision_height: "123", - revision_number: "456", - }, - timeout_timestamp: "789", - }, - }; - const msg = new AminoTypes(createIbcAminoConverters()).fromAmino(aminoMsg); - const expectedValue: MsgTransfer = { - sourcePort: "testport", - sourceChannel: "testchannel", - token: coin(1234, "utest"), - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - timeoutHeight: { - revisionHeight: Long.fromString("123", true), - revisionNumber: Long.fromString("456", true), - }, - timeoutTimestamp: Long.fromString("789", true), - }; - expect(msg).toEqual({ - typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", - value: expectedValue, - }); - }); - - it("works for MsgTransfer with default values", () => { - const aminoMsg: AminoMsgTransfer = { - type: "cosmos-sdk/MsgTransfer", - value: { - source_port: "testport", - source_channel: "testchannel", - token: coin(1234, "utest"), - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - timeout_height: { - // revision_height omitted - // revision_number omitted - }, - // timeout_timestamp omitted - }, - }; - const msg = new AminoTypes(createIbcAminoConverters()).fromAmino(aminoMsg); - const expectedValue: MsgTransfer = { - sourcePort: "testport", - sourceChannel: "testchannel", - token: coin(1234, "utest"), - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - receiver: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - timeoutHeight: { - revisionHeight: Long.UZERO, - revisionNumber: Long.UZERO, - }, - timeoutTimestamp: Long.UZERO, - }; - expect(msg).toEqual({ - typeUrl: "/ibc.applications.transfer.v1.MsgTransfer", - value: expectedValue, - }); - }); - }); -}); diff --git a/packages/finschia/src/modules/ibc/aminomessages.ts b/packages/finschia/src/modules/ibc/aminomessages.ts deleted file mode 100644 index 141555c6d..000000000 --- a/packages/finschia/src/modules/ibc/aminomessages.ts +++ /dev/null @@ -1,113 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { AminoMsg, Coin } from "@cosmjs/amino"; -import { AminoConverters } from "@cosmjs/stargate"; -import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; -import Long from "long"; - -// https://github.com/cosmos/ibc-go/blob/07b6a97b67d17fd214a83764cbdb2c2c3daef445/modules/core/02-client/types/client.pb.go#L297-L312 -interface AminoHeight { - /** 0 values must be omitted (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/x/ibc/core/02-client/types/client.pb.go#L252). */ - readonly revision_number?: string; - /** 0 values must be omitted (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/x/ibc/core/02-client/types/client.pb.go#L254). */ - readonly revision_height?: string; -} - -// https://github.com/cosmos/ibc-go/blob/07b6a97b67d17fd214a83764cbdb2c2c3daef445/modules/apps/transfer/types/tx.pb.go#L33-L53 -/** Transfers fungible tokens (i.e Coins) between ICS20 enabled chains */ -export interface AminoMsgTransfer extends AminoMsg { - readonly type: "cosmos-sdk/MsgTransfer"; - readonly value: { - readonly source_port: string; - readonly source_channel: string; - readonly token?: Coin; - /** Bech32 account address */ - readonly sender: string; - /** Bech32 account address */ - readonly receiver: string; - /** - * The timeout as a (revision_number, revision_height) pair. - * - * This fied is is non-optional (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/x/ibc/applications/transfer/types/tx.pb.go#L49). - * In order to not set the timeout height, set it to {}. - */ - readonly timeout_height: AminoHeight; - /** - * Timeout timestamp in nanoseconds since Unix epoch. The timeout is disabled when set to 0. - * - * 0 values must be omitted (https://github.com/cosmos/cosmos-sdk/blob/v0.42.7/x/ibc/applications/transfer/types/tx.pb.go#L52). - */ - readonly timeout_timestamp?: string; - }; -} - -export function isAminoMsgTransfer(msg: AminoMsg): msg is AminoMsgTransfer { - return msg.type === "cosmos-sdk/MsgTransfer"; -} - -function omitDefault(input: T): T | undefined { - if (typeof input === "string") { - return input === "" ? undefined : input; - } - - if (typeof input === "number") { - return input === 0 ? undefined : input; - } - - if (Long.isLong(input)) { - return input.isZero() ? undefined : input; - } - - throw new Error(`Got unsupported type '${typeof input}'`); -} - -export function createIbcAminoConverters(): AminoConverters { - return { - "/ibc.applications.transfer.v1.MsgTransfer": { - aminoType: "cosmos-sdk/MsgTransfer", - toAmino: ({ - sourcePort, - sourceChannel, - token, - sender, - receiver, - timeoutHeight, - timeoutTimestamp, - }: MsgTransfer): AminoMsgTransfer["value"] => ({ - source_port: sourcePort, - source_channel: sourceChannel, - token: token, - sender: sender, - receiver: receiver, - timeout_height: timeoutHeight - ? { - revision_height: omitDefault(timeoutHeight.revisionHeight)?.toString(), - revision_number: omitDefault(timeoutHeight.revisionNumber)?.toString(), - } - : {}, - timeout_timestamp: omitDefault(timeoutTimestamp)?.toString(), - }), - fromAmino: ({ - source_port, - source_channel, - token, - sender, - receiver, - timeout_height, - timeout_timestamp, - }: AminoMsgTransfer["value"]): MsgTransfer => ({ - sourcePort: source_port, - sourceChannel: source_channel, - token: token, - sender: sender, - receiver: receiver, - timeoutHeight: timeout_height - ? { - revisionHeight: Long.fromString(timeout_height.revision_height || "0", true), - revisionNumber: Long.fromString(timeout_height.revision_number || "0", true), - } - : undefined, - timeoutTimestamp: Long.fromString(timeout_timestamp || "0", true), - }), - }, - }; -} diff --git a/packages/finschia/src/modules/ibc/ibctestdata.spec.ts b/packages/finschia/src/modules/ibc/ibctestdata.spec.ts deleted file mode 100644 index 1ee5eb836..000000000 --- a/packages/finschia/src/modules/ibc/ibctestdata.spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { fromBase64 } from "@cosmjs/encoding"; -import { - Channel, - Counterparty as ChannelCounterparty, - IdentifiedChannel, - Order, - PacketState, - State as ChannelState, -} from "cosmjs-types/ibc/core/channel/v1/channel"; -import { MerklePrefix } from "cosmjs-types/ibc/core/commitment/v1/commitment"; -import { - ConnectionEnd, - Counterparty as ConnectionCounterparty, - IdentifiedConnection, - State as ConnectionState, - Version, -} from "cosmjs-types/ibc/core/connection/v1/connection"; -import Long from "long"; - -// From scripts/simapp42/genesis-ibc.json - -export const portId = "transfer"; -export const channelId = "channel-0"; -export const connectionId = "connection-0"; -export const clientId = "99-ostracon-0"; - -export const channel = Channel.fromPartial({ - state: ChannelState.STATE_OPEN, - ordering: Order.ORDER_UNORDERED, - counterparty: ChannelCounterparty.fromPartial({ - portId: portId, - channelId: channelId, - }), - connectionHops: [connectionId], - version: "ics20-1", -}); - -export const identifiedChannel = IdentifiedChannel.fromPartial({ - state: ChannelState.STATE_OPEN, - ordering: Order.ORDER_UNORDERED, - counterparty: ChannelCounterparty.fromPartial({ - portId: portId, - channelId: "channel-0", - }), - connectionHops: [connectionId], - version: "ics20-1", - portId: portId, - channelId: channelId, -}); - -/** - * ``` - * jq ".channel_genesis.commitments[0]" scripts/simapp42/genesis-ibc.json - * ``` - */ -export const commitment = { - sequence: 1, - data: fromBase64("hYz5Dx6o09DcSEWZR6xlJYwLgYUnLithsXMGtujic4I="), -}; - -export const packetState = PacketState.fromPartial({ - portId: portId, - channelId: channelId, - sequence: Long.fromInt(commitment.sequence, true), - data: commitment.data, -}); - -/** - * Unfortunatly empty right now - * - * ``` - * jq ".channel_genesis.acknowledgements" scripts/simapp42/genesis-ibc.json - * ``` - */ -export const packetAcknowledgements: PacketState[] = []; - -export const connection = ConnectionEnd.fromPartial({ - clientId: clientId, - versions: [ - Version.fromPartial({ - identifier: "1", - features: ["ORDER_ORDERED", "ORDER_UNORDERED"], - }), - ], - state: ConnectionState.STATE_OPEN, - counterparty: ConnectionCounterparty.fromPartial({ - clientId: "07-tendermint-0", - connectionId: "connection-0", - prefix: MerklePrefix.fromPartial({ - keyPrefix: fromBase64("aWJj"), - }), - }), -}); - -export const identifiedConnection = IdentifiedConnection.fromPartial({ - id: connectionId, - clientId: clientId, - versions: [ - Version.fromPartial({ - identifier: "1", - features: ["ORDER_ORDERED", "ORDER_UNORDERED"], - }), - ], - state: ConnectionState.STATE_OPEN, - counterparty: ConnectionCounterparty.fromPartial({ - clientId: "07-tendermint-0", - connectionId: "connection-0", - prefix: MerklePrefix.fromPartial({ - keyPrefix: fromBase64("aWJj"), - }), - }), -}); diff --git a/packages/finschia/src/modules/ibc/messages.ts b/packages/finschia/src/modules/ibc/messages.ts index 2eb32da87..3659f2846 100644 --- a/packages/finschia/src/modules/ibc/messages.ts +++ b/packages/finschia/src/modules/ibc/messages.ts @@ -1,4 +1,4 @@ -import { EncodeObject, GeneratedType } from "@cosmjs/proto-signing"; +import { GeneratedType } from "@cosmjs/proto-signing"; import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; import { MsgAcknowledgement, @@ -46,12 +46,3 @@ export const ibcTypes: ReadonlyArray<[string, GeneratedType]> = [ ["/ibc.core.connection.v1.MsgConnectionOpenInit", MsgConnectionOpenInit], ["/ibc.core.connection.v1.MsgConnectionOpenTry", MsgConnectionOpenTry], ]; - -export interface MsgTransferEncodeObject extends EncodeObject { - readonly typeUrl: "/ibc.applications.transfer.v1.MsgTransfer"; - readonly value: Partial; -} - -export function isMsgTransferEncodeObject(object: EncodeObject): object is MsgTransferEncodeObject { - return (object as MsgTransferEncodeObject).typeUrl === "/ibc.applications.transfer.v1.MsgTransfer"; -} diff --git a/packages/finschia/src/modules/ibc/queries.spec.ts b/packages/finschia/src/modules/ibc/queries.spec.ts index 5b522a17b..8c29e22d0 100644 --- a/packages/finschia/src/modules/ibc/queries.spec.ts +++ b/packages/finschia/src/modules/ibc/queries.spec.ts @@ -1,9 +1,9 @@ +import * as ibcTest from "@cosmjs/cosmwasm-stargate"; import { QueryClient } from "@cosmjs/stargate"; import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; import Long from "long"; import { pendingWithoutSimapp, simapp } from "../../testutils.spec"; -import * as ibcTest from "./ibctestdata.spec"; import { IbcExtension, setupIbcExtension } from "./queries"; async function makeClientWithIbc(rpcUrl: string): Promise<[QueryClient & IbcExtension, Tendermint34Client]> { diff --git a/packages/finschia/src/modules/index.ts b/packages/finschia/src/modules/index.ts index d1a350649..a9046aaf0 100644 --- a/packages/finschia/src/modules/index.ts +++ b/packages/finschia/src/modules/index.ts @@ -1,12 +1,6 @@ export { collectionTypes } from "./collection/messages"; export { CollectionExtension, setupCollectionExtension } from "./collection/queries"; -export { - AminoMsgSubmitEvidence, - createEvidenceAminoConverters, - isAminoMsgSubmitEvidence, -} from "./evidence/aminomessages"; export { EvidenceExtension, setupEvidenceExtension } from "./evidence/queries"; -export { createFreegrantAminoConverters } from "./feegrant/aminomessages"; export { feegrantTypes } from "./feegrant/messages"; export { FeeGrantExtension, setupFeeGrantExtension } from "./feegrant/queries"; export { @@ -25,36 +19,13 @@ export { ThresholdDecisionPolicyEncodeObject, } from "./foundation/messages"; export { FoundationExtension, FoundationProposalId, setupFoundationExtension } from "./foundation/queries"; -export { AminoMsgTransfer, createIbcAminoConverters, isAminoMsgTransfer } from "./ibc/aminomessages"; -export { ibcTypes, isMsgTransferEncodeObject, MsgTransferEncodeObject } from "./ibc/messages"; +export { ibcTypes } from "./ibc/messages"; export { IbcExtension, setupIbcExtension } from "./ibc/queries"; export { tokenTypes } from "./token/messages"; export { setupTokenExtension, TokenExtension } from "./token/queries"; export { - AminoMsgClearAdmin, - AminoMsgExecuteContract, - AminoMsgInstantiateContract, - AminoMsgMigrateContract, - AminoMsgStoreCode, - AminoMsgUpdateAdmin, - cosmWasmTypes, - createWasmAminoConverters, -} from "./wasm/aminomessages"; -export { - isMsgClearAdminEncodeObject, - isMsgExecuteEncodeObject, - isMsgInstantiateContractEncodeObject, - isMsgMigrateEncodeObject, isMsgStoreCodeAndInstantiateContract, - isMsgStoreCodeEncodeObject, - isMsgUpdateAdminEncodeObject, - MsgClearAdminEncodeObject, - MsgExecuteContractEncodeObject, - MsgInstantiateContractEncodeObject, - MsgMigrateContractEncodeObject, MsgStoreCodeAndInstantiateContractEncodeObject, - MsgStoreCodeEncodeObject, - MsgUpdateAdminEncodeObject, wasmTypes, } from "./wasm/messages"; export { JsonObject, setupWasmExtension, WasmExtension } from "./wasm/queries"; diff --git a/packages/finschia/src/modules/wasm/aminomessages.spec.ts b/packages/finschia/src/modules/wasm/aminomessages.spec.ts deleted file mode 100644 index 3c9b5b47e..000000000 --- a/packages/finschia/src/modules/wasm/aminomessages.spec.ts +++ /dev/null @@ -1,356 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { fromBase64, toUtf8 } from "@cosmjs/encoding"; -import { AminoTypes, coins } from "@cosmjs/stargate"; -import { - MsgClearAdmin, - MsgExecuteContract, - MsgInstantiateContract, - MsgMigrateContract, - MsgStoreCode, - MsgUpdateAdmin, -} from "cosmjs-types/cosmwasm/wasm/v1/tx"; -import Long from "long"; - -import { - AminoMsgClearAdmin, - AminoMsgExecuteContract, - AminoMsgInstantiateContract, - AminoMsgMigrateContract, - AminoMsgStoreCode, - AminoMsgUpdateAdmin, - createWasmAminoConverters, -} from "./aminomessages"; - -describe("AminoTypes", () => { - describe("toAmino", () => { - it("works for MsgStoreCode", () => { - const msg: MsgStoreCode = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - wasmByteCode: fromBase64("WUVMTE9XIFNVQk1BUklORQ=="), - instantiatePermission: undefined, - }; - const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ - typeUrl: "/cosmwasm.wasm.v1.MsgStoreCode", - value: msg, - }); - const expected: AminoMsgStoreCode = { - type: "wasm/MsgStoreCode", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - wasm_byte_code: "WUVMTE9XIFNVQk1BUklORQ==", - }, - }; - expect(aminoMsg).toEqual(expected); - }); - - it("works for MsgInstantiateContract", () => { - // With admin - { - const msg: MsgInstantiateContract = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - codeId: Long.fromString("12345"), - label: "sticky", - msg: toUtf8(`{"foo":"bar"}`), - funds: coins(1234, "cony"), - admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - }; - const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ - typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", - value: msg, - }); - const expected: AminoMsgInstantiateContract = { - type: "wasm/MsgInstantiateContract", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - code_id: "12345", - label: "sticky", - // todo: we need to fix the msg representation of lbm-sdk's wasm proto. refer https://github.com/CosmWasm/wasmd/pull/658 - msg: { foo: "bar" }, - funds: coins(1234, "cony"), - admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - }, - }; - expect(aminoMsg).toEqual(expected); - } - - // Without admin - { - const msg: MsgInstantiateContract = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - codeId: Long.fromString("12345"), - label: "sticky", - msg: toUtf8(`{"foo":"bar"}`), - funds: coins(1234, "cony"), - admin: "", - }; - const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ - typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", - value: msg, - }); - const expected: AminoMsgInstantiateContract = { - type: "wasm/MsgInstantiateContract", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - code_id: "12345", - label: "sticky", - msg: { foo: "bar" }, - funds: coins(1234, "cony"), - admin: undefined, - }, - }; - expect(aminoMsg).toEqual(expected); - } - }); - - it("works for MsgUpdateAdmin", () => { - const msg: MsgUpdateAdmin = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - newAdmin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - }; - const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ - typeUrl: "/cosmwasm.wasm.v1.MsgUpdateAdmin", - value: msg, - }); - const expected: AminoMsgUpdateAdmin = { - type: "wasm/MsgUpdateAdmin", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - new_admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - }, - }; - expect(aminoMsg).toEqual(expected); - }); - - it("works for MsgClearAdmin", () => { - const msg: MsgClearAdmin = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - }; - const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ - typeUrl: "/cosmwasm.wasm.v1.MsgClearAdmin", - value: msg, - }); - const expected: AminoMsgClearAdmin = { - type: "wasm/MsgClearAdmin", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - }, - }; - expect(aminoMsg).toEqual(expected); - }); - - it("works for MsgExecuteContract", () => { - const msg: MsgExecuteContract = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - msg: toUtf8(`{"foo":"bar"}`), - funds: coins(1234, "cony"), - }; - const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ - typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", - value: msg, - }); - const expected: AminoMsgExecuteContract = { - type: "wasm/MsgExecuteContract", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - msg: { foo: "bar" }, - funds: coins(1234, "cony"), - }, - }; - expect(aminoMsg).toEqual(expected); - }); - - it("works for MsgMigrateContract", () => { - const msg: MsgMigrateContract = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - codeId: Long.fromString("98765"), - msg: toUtf8(`{"foo":"bar"}`), - }; - const aminoMsg = new AminoTypes(createWasmAminoConverters()).toAmino({ - typeUrl: "/cosmwasm.wasm.v1.MsgMigrateContract", - value: msg, - }); - const expected: AminoMsgMigrateContract = { - type: "wasm/MsgMigrateContract", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - code_id: "98765", - msg: { foo: "bar" }, - }, - }; - expect(aminoMsg).toEqual(expected); - }); - }); - - describe("fromAmino", () => { - it("works for MsgStoreCode", () => { - const aminoMsg: AminoMsgStoreCode = { - type: "wasm/MsgStoreCode", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - wasm_byte_code: "WUVMTE9XIFNVQk1BUklORQ==", - }, - }; - const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); - const expectedValue: MsgStoreCode = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - wasmByteCode: fromBase64("WUVMTE9XIFNVQk1BUklORQ=="), - instantiatePermission: undefined, - }; - expect(msg).toEqual({ - typeUrl: "/cosmwasm.wasm.v1.MsgStoreCode", - value: expectedValue, - }); - }); - - it("works for MsgInstantiateContract", () => { - // With admin - { - const aminoMsg: AminoMsgInstantiateContract = { - type: "wasm/MsgInstantiateContract", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - code_id: "12345", - label: "sticky", - msg: { foo: "bar" }, - funds: coins(1234, "cony"), - admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - }, - }; - const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); - const expectedValue: MsgInstantiateContract = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - codeId: Long.fromString("12345"), - label: "sticky", - msg: toUtf8(`{"foo":"bar"}`), - funds: coins(1234, "cony"), - admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - }; - expect(msg).toEqual({ - typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", - value: expectedValue, - }); - } - - // Without admin - { - const aminoMsg: AminoMsgInstantiateContract = { - type: "wasm/MsgInstantiateContract", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - code_id: "12345", - label: "sticky", - msg: { foo: "bar" }, - funds: coins(1234, "cony"), - }, - }; - const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); - const expectedValue: MsgInstantiateContract = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - codeId: Long.fromString("12345"), - label: "sticky", - msg: toUtf8(`{"foo":"bar"}`), - funds: coins(1234, "cony"), - admin: "", - }; - expect(msg).toEqual({ - typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract", - value: expectedValue, - }); - } - }); - - it("works for MsgUpdateAdmin", () => { - const aminoMsg: AminoMsgUpdateAdmin = { - type: "wasm/MsgUpdateAdmin", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - new_admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - }, - }; - const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); - const expectedValue: MsgUpdateAdmin = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - newAdmin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - }; - expect(msg).toEqual({ - typeUrl: "/cosmwasm.wasm.v1.MsgUpdateAdmin", - value: expectedValue, - }); - }); - - it("works for MsgClearAdmin", () => { - const aminoMsg: AminoMsgClearAdmin = { - type: "wasm/MsgClearAdmin", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - }, - }; - const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); - const expectedValue: MsgClearAdmin = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - }; - expect(msg).toEqual({ - typeUrl: "/cosmwasm.wasm.v1.MsgClearAdmin", - value: expectedValue, - }); - }); - - it("works for MsgExecuteContract", () => { - const aminoMsg: AminoMsgExecuteContract = { - type: "wasm/MsgExecuteContract", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - msg: { foo: "bar" }, - funds: coins(1234, "cony"), - }, - }; - const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); - const expectedValue: MsgExecuteContract = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - msg: toUtf8(`{"foo":"bar"}`), - funds: coins(1234, "cony"), - }; - expect(msg).toEqual({ - typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract", - value: expectedValue, - }); - }); - - it("works for MsgMigrateContract", () => { - const aminoMsg: AminoMsgMigrateContract = { - type: "wasm/MsgMigrateContract", - value: { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - code_id: "98765", - msg: { foo: "bar" }, - }, - }; - const msg = new AminoTypes(createWasmAminoConverters()).fromAmino(aminoMsg); - const expectedValue: MsgMigrateContract = { - sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", - contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", - codeId: Long.fromString("98765"), - msg: toUtf8(`{"foo":"bar"}`), - }; - expect(msg).toEqual({ - typeUrl: "/cosmwasm.wasm.v1.MsgMigrateContract", - value: expectedValue, - }); - }); - }); -}); diff --git a/packages/finschia/src/modules/wasm/aminomessages.ts b/packages/finschia/src/modules/wasm/aminomessages.ts deleted file mode 100644 index 5ba5cac97..000000000 --- a/packages/finschia/src/modules/wasm/aminomessages.ts +++ /dev/null @@ -1,242 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding"; -import { AminoConverters, Coin } from "@cosmjs/stargate"; -import { - MsgClearAdmin, - MsgExecuteContract, - MsgInstantiateContract, - MsgMigrateContract, - MsgStoreCode, - MsgUpdateAdmin, -} from "cosmjs-types/cosmwasm/wasm/v1/tx"; -import Long from "long"; - -// TODO: implement -/** - * @see https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/types.proto#L36-L41 - */ -type AccessConfig = never; - -/** - * The Amino JSON representation of [MsgStoreCode]. - * - * [MsgStoreCode]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L28-L39 - */ -export interface AminoMsgStoreCode { - type: "wasm/MsgStoreCode"; - value: { - /** Bech32 account address */ - readonly sender: string; - /** Base64 encoded Wasm */ - readonly wasm_byte_code: string; - readonly instantiate_permission?: AccessConfig; - }; -} - -/** - * The Amino JSON representation of [MsgExecuteContract]. - * - * [MsgExecuteContract]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L73-L86 - */ -export interface AminoMsgExecuteContract { - type: "wasm/MsgExecuteContract"; - value: { - /** Bech32 account address */ - readonly sender: string; - /** Bech32 account address */ - readonly contract: string; - /** Execute message as JavaScript object */ - readonly msg: any; - readonly funds: readonly Coin[]; - }; -} - -/** - * The Amino JSON representation of [MsgInstantiateContract]. - * - * [MsgInstantiateContract]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L46-L64 - */ -export interface AminoMsgInstantiateContract { - type: "wasm/MsgInstantiateContract"; - value: { - /** Bech32 account address */ - readonly sender: string; - /** ID of the Wasm code that was uploaded before */ - readonly code_id: string; - /** Human-readable label for this contract */ - readonly label: string; - /** Instantiate message as JavaScript object */ - readonly msg: any; - readonly funds: readonly Coin[]; - /** Bech32-encoded admin address */ - readonly admin?: string; - }; -} - -/** - * The Amino JSON representation of [MsgMigrateContract]. - * - * [MsgMigrateContract]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L94-L104 - */ -export interface AminoMsgMigrateContract { - type: "wasm/MsgMigrateContract"; - value: { - /** Bech32 account address */ - readonly sender: string; - /** Bech32 account address */ - readonly contract: string; - /** The new code */ - readonly code_id: string; - /** Migrate message as JavaScript object */ - readonly msg: any; - }; -} - -/** - * The Amino JSON representation of [MsgUpdateAdmin]. - * - * [MsgUpdateAdmin]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L113-L121 - */ -export interface AminoMsgUpdateAdmin { - type: "wasm/MsgUpdateAdmin"; - value: { - /** Bech32-encoded sender address. This must be the old admin. */ - readonly sender: string; - /** Bech32-encoded contract address to be updated */ - readonly contract: string; - /** Bech32-encoded address of the new admin */ - readonly new_admin: string; - }; -} - -/** - * The Amino JSON representation of [MsgClearAdmin]. - * - * [MsgClearAdmin]: https://github.com/CosmWasm/wasmd/blob/v0.18.0-rc1/proto/cosmwasm/wasm/v1/tx.proto#L126-L132 - */ -export interface AminoMsgClearAdmin { - type: "wasm/MsgClearAdmin"; - value: { - /** Bech32-encoded sender address. This must be the old admin. */ - readonly sender: string; - /** Bech32-encoded contract address to be updated */ - readonly contract: string; - }; -} - -export function createWasmAminoConverters(): AminoConverters { - return { - "/cosmwasm.wasm.v1.MsgStoreCode": { - aminoType: "wasm/MsgStoreCode", - toAmino: ({ sender, wasmByteCode }: MsgStoreCode): AminoMsgStoreCode["value"] => ({ - sender: sender, - wasm_byte_code: toBase64(wasmByteCode), - }), - fromAmino: ({ sender, wasm_byte_code }: AminoMsgStoreCode["value"]): MsgStoreCode => ({ - sender: sender, - wasmByteCode: fromBase64(wasm_byte_code), - instantiatePermission: undefined, - }), - }, - "/cosmwasm.wasm.v1.MsgInstantiateContract": { - aminoType: "wasm/MsgInstantiateContract", - toAmino: ({ - sender, - codeId, - label, - msg, - funds, - admin, - }: MsgInstantiateContract): AminoMsgInstantiateContract["value"] => ({ - sender: sender, - code_id: codeId.toString(), - label: label, - msg: JSON.parse(fromUtf8(msg)), - funds: funds, - admin: admin || undefined, - }), - fromAmino: ({ - sender, - code_id, - label, - msg, - funds, - admin, - }: AminoMsgInstantiateContract["value"]): MsgInstantiateContract => ({ - sender: sender, - codeId: Long.fromString(code_id), - label: label, - msg: toUtf8(JSON.stringify(msg)), - funds: [...funds], - admin: admin ?? "", - }), - }, - "/cosmwasm.wasm.v1.MsgUpdateAdmin": { - aminoType: "wasm/MsgUpdateAdmin", - toAmino: ({ sender, newAdmin, contract }: MsgUpdateAdmin): AminoMsgUpdateAdmin["value"] => ({ - sender: sender, - new_admin: newAdmin, - contract: contract, - }), - fromAmino: ({ sender, new_admin, contract }: AminoMsgUpdateAdmin["value"]): MsgUpdateAdmin => ({ - sender: sender, - newAdmin: new_admin, - contract: contract, - }), - }, - "/cosmwasm.wasm.v1.MsgClearAdmin": { - aminoType: "wasm/MsgClearAdmin", - toAmino: ({ sender, contract }: MsgClearAdmin): AminoMsgClearAdmin["value"] => ({ - sender: sender, - contract: contract, - }), - fromAmino: ({ sender, contract }: AminoMsgClearAdmin["value"]): MsgClearAdmin => ({ - sender: sender, - contract: contract, - }), - }, - "/cosmwasm.wasm.v1.MsgExecuteContract": { - aminoType: "wasm/MsgExecuteContract", - toAmino: ({ sender, contract, msg, funds }: MsgExecuteContract): AminoMsgExecuteContract["value"] => ({ - sender: sender, - contract: contract, - msg: JSON.parse(fromUtf8(msg)), - funds: funds, - }), - fromAmino: ({ - sender, - contract, - msg, - funds, - }: AminoMsgExecuteContract["value"]): MsgExecuteContract => ({ - sender: sender, - contract: contract, - msg: toUtf8(JSON.stringify(msg)), - funds: [...funds], - }), - }, - "/cosmwasm.wasm.v1.MsgMigrateContract": { - aminoType: "wasm/MsgMigrateContract", - toAmino: ({ sender, contract, codeId, msg }: MsgMigrateContract): AminoMsgMigrateContract["value"] => ({ - sender: sender, - contract: contract, - code_id: codeId.toString(), - msg: JSON.parse(fromUtf8(msg)), - }), - fromAmino: ({ - sender, - contract, - code_id, - msg, - }: AminoMsgMigrateContract["value"]): MsgMigrateContract => ({ - sender: sender, - contract: contract, - codeId: Long.fromString(code_id), - msg: toUtf8(JSON.stringify(msg)), - }), - }, - }; -} - -/** @deprecated use `createWasmAminoConverters()` */ -export const cosmWasmTypes: AminoConverters = createWasmAminoConverters(); diff --git a/packages/finschia/src/modules/wasm/messages.ts b/packages/finschia/src/modules/wasm/messages.ts index ace12c8eb..483a976b9 100644 --- a/packages/finschia/src/modules/wasm/messages.ts +++ b/packages/finschia/src/modules/wasm/messages.ts @@ -19,28 +19,6 @@ export const wasmTypes: ReadonlyArray<[string, GeneratedType]> = [ ["/cosmwasm.wasm.v1.MsgUpdateAdmin", MsgUpdateAdmin], ]; -export interface MsgStoreCodeEncodeObject extends EncodeObject { - readonly typeUrl: "/cosmwasm.wasm.v1.MsgStoreCode"; - readonly value: Partial; -} - -export function isMsgStoreCodeEncodeObject(object: EncodeObject): object is MsgStoreCodeEncodeObject { - return (object as MsgStoreCodeEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgStoreCode"; -} - -export interface MsgInstantiateContractEncodeObject extends EncodeObject { - readonly typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract"; - readonly value: Partial; -} - -export function isMsgInstantiateContractEncodeObject( - object: EncodeObject, -): object is MsgInstantiateContractEncodeObject { - return ( - (object as MsgInstantiateContractEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgInstantiateContract" - ); -} - export interface MsgStoreCodeAndInstantiateContractEncodeObject extends EncodeObject { readonly typeUrl: "/cosmwasm.wasm.v1.MsgStoreCodeAndInstantiateContract"; readonly value: Partial; @@ -54,39 +32,3 @@ export function isMsgStoreCodeAndInstantiateContract( "/cosmwasm.wasm.v1.MsgStoreCodeAndInstantiateContract" ); } - -export interface MsgUpdateAdminEncodeObject extends EncodeObject { - readonly typeUrl: "/cosmwasm.wasm.v1.MsgUpdateAdmin"; - readonly value: Partial; -} - -export function isMsgUpdateAdminEncodeObject(object: EncodeObject): object is MsgUpdateAdminEncodeObject { - return (object as MsgUpdateAdminEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgUpdateAdmin"; -} - -export interface MsgClearAdminEncodeObject extends EncodeObject { - readonly typeUrl: "/cosmwasm.wasm.v1.MsgClearAdmin"; - readonly value: Partial; -} - -export function isMsgClearAdminEncodeObject(object: EncodeObject): object is MsgClearAdminEncodeObject { - return (object as MsgClearAdminEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgClearAdmin"; -} - -export interface MsgMigrateContractEncodeObject extends EncodeObject { - readonly typeUrl: "/cosmwasm.wasm.v1.MsgMigrateContract"; - readonly value: Partial; -} - -export function isMsgMigrateEncodeObject(object: EncodeObject): object is MsgMigrateContractEncodeObject { - return (object as MsgMigrateContractEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgMigrateContract"; -} - -export interface MsgExecuteContractEncodeObject extends EncodeObject { - readonly typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract"; - readonly value: Partial; -} - -export function isMsgExecuteEncodeObject(object: EncodeObject): object is MsgExecuteContractEncodeObject { - return (object as MsgExecuteContractEncodeObject).typeUrl === "/cosmwasm.wasm.v1.MsgExecuteContract"; -} diff --git a/packages/finschia/src/modules/wasm/queries.spec.ts b/packages/finschia/src/modules/wasm/queries.spec.ts index 2a532a1e5..98b1dfc9f 100644 --- a/packages/finschia/src/modules/wasm/queries.spec.ts +++ b/packages/finschia/src/modules/wasm/queries.spec.ts @@ -1,3 +1,8 @@ +import { + MsgExecuteContractEncodeObject, + MsgInstantiateContractEncodeObject, + MsgStoreCodeEncodeObject, +} from "@cosmjs/cosmwasm-stargate"; import { sha256 } from "@cosmjs/crypto"; import { fromAscii, fromHex, toAscii, toHex } from "@cosmjs/encoding"; import { DirectSecp256k1HdWallet, OfflineDirectSigner, Registry } from "@cosmjs/proto-signing"; @@ -31,12 +36,7 @@ import { simapp, simappEnabled, } from "../../testutils.spec"; -import { - MsgExecuteContractEncodeObject, - MsgInstantiateContractEncodeObject, - MsgStoreCodeEncodeObject, - wasmTypes, -} from "./messages"; +import { wasmTypes } from "./messages"; const registry = new Registry(wasmTypes); diff --git a/packages/finschia/src/signingfinschiaclient.spec.ts b/packages/finschia/src/signingfinschiaclient.spec.ts index c739cfbd9..7ed92a279 100644 --- a/packages/finschia/src/signingfinschiaclient.spec.ts +++ b/packages/finschia/src/signingfinschiaclient.spec.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Secp256k1HdWallet } from "@cosmjs/amino"; +import { MsgExecuteContractEncodeObject, MsgStoreCodeEncodeObject } from "@cosmjs/cosmwasm-stargate"; import { sha256 } from "@cosmjs/crypto"; import { toHex, toUtf8 } from "@cosmjs/encoding"; import { decodeTxRaw, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; @@ -25,7 +26,6 @@ import Long from "long"; import pako from "pako"; import protobuf from "protobufjs/minimal"; -import { MsgExecuteContractEncodeObject, MsgStoreCodeEncodeObject } from "./modules"; import { makeLinkPath } from "./paths"; import { SigningFinschiaClient } from "./signingfinschiaclient"; import { diff --git a/packages/finschia/src/signingfinschiaclient.ts b/packages/finschia/src/signingfinschiaclient.ts index d84d9afd2..f73ab2427 100644 --- a/packages/finschia/src/signingfinschiaclient.ts +++ b/packages/finschia/src/signingfinschiaclient.ts @@ -8,6 +8,15 @@ import { MigrateResult, UploadResult, } from "@cosmjs/cosmwasm-stargate"; +import { + createWasmAminoConverters, + MsgClearAdminEncodeObject, + MsgExecuteContractEncodeObject, + MsgInstantiateContractEncodeObject, + MsgMigrateContractEncodeObject, + MsgStoreCodeEncodeObject, + MsgUpdateAdminEncodeObject, +} from "@cosmjs/cosmwasm-stargate"; import { sha256 } from "@cosmjs/crypto"; import { fromBase64, toHex, toUtf8 } from "@cosmjs/encoding"; import { Int53, Uint53 } from "@cosmjs/math"; @@ -45,6 +54,11 @@ import { SigningStargateClientOptions, StdFee, } from "@cosmjs/stargate"; +import { + createFreegrantAminoConverters, + createIbcAminoConverters, + MsgTransferEncodeObject, +} from "@cosmjs/stargate"; import { HttpEndpoint, Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { assert, assertDefined } from "@cosmjs/utils"; import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; @@ -67,24 +81,7 @@ import Long from "long"; import pako from "pako"; import { FinschiaClient } from "./finschiaclient"; -import { - collectionTypes, - createFreegrantAminoConverters, - createIbcAminoConverters, - createWasmAminoConverters, - feegrantTypes, - foundationTypes, - ibcTypes, - MsgClearAdminEncodeObject, - MsgExecuteContractEncodeObject, - MsgInstantiateContractEncodeObject, - MsgMigrateContractEncodeObject, - MsgStoreCodeEncodeObject, - MsgTransferEncodeObject, - MsgUpdateAdminEncodeObject, - tokenTypes, - wasmTypes, -} from "./modules"; +import { collectionTypes, feegrantTypes, foundationTypes, ibcTypes, tokenTypes, wasmTypes } from "./modules"; export interface UploadAndInstantiateResult { /** Size of the original wasm code in bytes */ From 7c1de7dce836de59646296779a6fd55229501cad Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Tue, 20 Sep 2022 23:16:18 +0900 Subject: [PATCH 11/15] chore: change contributor email to noreply --- packages/finschia/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/finschia/package.json b/packages/finschia/package.json index 56e213a18..1a14b9029 100644 --- a/packages/finschia/package.json +++ b/packages/finschia/package.json @@ -5,7 +5,7 @@ "contributors": [ "Simon Warta ", "zemyblue ", - "loin3 " + "loin3 <55660267+loin3@users.noreply.github.com>" ], "license": "Apache-2.0", "main": "build/index.js", From 3d80f9a25255d8f590a2f32ed1c5649b8f3c1249 Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Wed, 21 Sep 2022 15:54:03 +0900 Subject: [PATCH 12/15] chore: change finschia readme --- packages/finschia/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/finschia/README.md b/packages/finschia/README.md index 13bf02216..3a7160438 100644 --- a/packages/finschia/README.md +++ b/packages/finschia/README.md @@ -1,2 +1 @@ -# @lbmjs/stargate - +# @lbmjs/finschia From 08ec242535a81455be35f4079abd5b29456a38fd Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Wed, 21 Sep 2022 18:25:24 +0900 Subject: [PATCH 13/15] chore: change md file content to fit with lbmjs --- packages/finschia/CUSTOM_PROTOBUF_CODECS.md | 44 ++++++++++----------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/finschia/CUSTOM_PROTOBUF_CODECS.md b/packages/finschia/CUSTOM_PROTOBUF_CODECS.md index c53b57d8b..a822f9700 100644 --- a/packages/finschia/CUSTOM_PROTOBUF_CODECS.md +++ b/packages/finschia/CUSTOM_PROTOBUF_CODECS.md @@ -4,24 +4,23 @@ As of [v0.40](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.40.0), the Cosmos SDK uses [Protocol Buffers](https://developers.google.com/protocol-buffers) (also known as "protobuf") as its standard serialization format for blockchain state and -wire communication. CosmJS by default supports Protocol Buffer serialization for -many of the standard queries and messages defined by the Cosmos SDK, as well as -[CosmWasm](https://github.com/CosmWasm/wasmd). This document explains how you -can make use of Protocol Buffer serialization for your own custom modules with -CosmJS. +wire communication. LBMJS by default supports Protocol Buffer serialization for +many of the standard queries and messages defined by the LBM SDK. This document +explains how you can make use of Protocol Buffer serialization for your own +custom modules with LBMJS. ## Prerequisites - You are working on a TypeScript project. (Plain JS is possible but not covered by this document. It should work if you just strip out the type information.) -- You have installed `@cosmjs/proto-signing`, `@cosmjs/stargate` and - `@cosmjs/tendermint-rpc` as dependencies. In general these dependencies should - all have the same version, and this document is accurate as of version 0.26. +- You have installed `@cosmjs/proto-signing`, `@cosmjs/tendermint-rpc` and + `@lbmjs/finschia` as dependencies. In general CosmJS dependencies should all + have the same version, and this document is accurate as of version 0.26. ``` "dependencies": { "@cosmjs/proto-signing": "^0.26.4", - "@cosmjs/stargate": "^0.26.4", "@cosmjs/tendermint-rpc": "^0.26.4", + "@lbmjs/finschia": "^0.4.0-rc8", // ... } ``` @@ -43,14 +42,14 @@ You will need these files locally. There are two ways this is typically done: to download the definition files from the Cosmos SDK repository. 2. **Git submodules** allow linking external repositories into the current project's git. This is done in - [the cosmjs-types repo](https://github.com/confio/cosmjs-types). + [the lbmjs-types repo](https://github.com/line/lbmjs-types). If the proto files are not publicly available, the first way should be preferred. Otherwise permission management can become very complicated. ## Step 2: Generate codec files -In CosmJS we use [ts-proto](https://github.com/stephenh/ts-proto) to generate +In LBMJS we use [ts-proto](https://github.com/stephenh/ts-proto) to generate codec files, and in this document we assume you will follow the same route. Here is an example usage: @@ -85,9 +84,9 @@ helps. The name of the script renames the protoc plugin from `ts_proto` to `--ts_proto_yarn_2_opt="…"`. A full example is available in the cosmjs-types repo: -[protoc-gen-ts_proto_yarn_2](https://github.com/confio/cosmjs-types/blob/v0.2.1/bin/protoc-gen-ts_proto_yarn_2) +[protoc-gen-ts_proto_yarn_2](https://github.com/line/lbmjs-types/blob/v0.46.0-rc7/bin/protoc-gen-ts_proto_yarn_2) and -[codegen.sh](https://github.com/confio/cosmjs-types/blob/v0.2.1/scripts/codegen.sh). +[codegen.sh](https://github.com/line/lbmjs-types/blob/v0.46.0-rc7/scripts/codegen.sh). ### Step 3 @@ -100,8 +99,8 @@ generation). Now we look into using this codec. This section is split in ## Step 3a: Instantiate a signing client using your custom message types This section assumes that your definition files included `MsgXxx` `message` -definitions for use in submitting transactions to a Cosmos SDK blockchain. You -can instantiate a signing client for Stargate which supports those message types +definitions for use in submitting transactions to a LBM SDK blockchain. You can +instantiate a signing client for Finschia which supports those message types using a custom registry. We expose a `Registry` class from `@cosmjs/proto-signing` for you to use, which maps type URLs to codec objects. For example: @@ -109,12 +108,12 @@ For example: ```ts import { DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; import { - defaultRegistryTypes as defaultStargateTypes, - SigningStargateClient, -} from "@cosmjs/stargate"; + finschiaRegistryTypes as defaultRegistryTypes, + SigningFinschiaClient, +} from "@lbmjs/finschia"; import { MsgXxx } from "./path/to/generated/codec/my/custom/tx"; // Replace with your own Msg import -const myRegistry = new Registry(defaultStargateTypes); +const myRegistry = new Registry(defaultRegistryTypes); myRegistry.register("/my.custom.MsgXxx", MsgXxx); // Replace with your own type URL and Msg class const mnemonic = // Replace with your own mnemonic "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone"; @@ -124,7 +123,7 @@ const signer = await DirectSecp256k1HdWallet.fromMnemonic( mnemonic, { prefix: "myprefix" }, // Replace with your own Bech32 address prefix ); -const client = await SigningStargateClient.connectWithSigner( +const client = await SigningFinschiaClient.connectWithSigner( "my.endpoint.com", // Replace with your own RPC endpoint signer, { registry: myRegistry }, @@ -229,5 +228,6 @@ const queryClient = QueryClient.withExtensions( const queryResult = await queryClient.mymodule.customQuery("bar"); ``` -You can see how CosmJS sets up the `bank` extension for its default query client -[here](https://github.com/cosmos/cosmjs/blob/v0.26.4/packages/stargate/src/queries/bank.ts). +You can see how LBMJS sets up the `foundation` extension for its default query +client +[here](https://github.com/cosmos/lbmjs/blob/v0.4.0-rc8/packages/finscha/src/modules/foundation/queries.ts). From 0a2866aad26c95dba37c1e2babc2fd03ff133b37 Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Wed, 21 Sep 2022 18:37:14 +0900 Subject: [PATCH 14/15] chore: delete contents that does not be supported any more --- packages/finschia/CUSTOM_PROTOBUF_CODECS.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/finschia/CUSTOM_PROTOBUF_CODECS.md b/packages/finschia/CUSTOM_PROTOBUF_CODECS.md index a822f9700..8aeb68511 100644 --- a/packages/finschia/CUSTOM_PROTOBUF_CODECS.md +++ b/packages/finschia/CUSTOM_PROTOBUF_CODECS.md @@ -36,11 +36,7 @@ custom modules with LBMJS. You will need these files locally. There are two ways this is typically done: -1. **Download copies** from an external source into the project. For example, we - used - [this script](https://github.com/cosmos/cosmjs/blob/v0.25.6/packages/stargate/scripts/get-proto.sh) - to download the definition files from the Cosmos SDK repository. -2. **Git submodules** allow linking external repositories into the current +1. **Git submodules** allow linking external repositories into the current project's git. This is done in [the lbmjs-types repo](https://github.com/line/lbmjs-types). From 8c3815e9c9bc4a2e1aa5ee6a017f72e1f7153743 Mon Sep 17 00:00:00 2001 From: "jinseong.cho" Date: Wed, 21 Sep 2022 21:47:25 +0900 Subject: [PATCH 15/15] delete: custom protobuf codes md --- packages/finschia/CUSTOM_PROTOBUF_CODECS.md | 229 -------------------- 1 file changed, 229 deletions(-) delete mode 100644 packages/finschia/CUSTOM_PROTOBUF_CODECS.md diff --git a/packages/finschia/CUSTOM_PROTOBUF_CODECS.md b/packages/finschia/CUSTOM_PROTOBUF_CODECS.md deleted file mode 100644 index 8aeb68511..000000000 --- a/packages/finschia/CUSTOM_PROTOBUF_CODECS.md +++ /dev/null @@ -1,229 +0,0 @@ -# Custom Protocol Buffer Codecs - -As of [v0.40](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.40.0), the -Cosmos SDK uses -[Protocol Buffers](https://developers.google.com/protocol-buffers) (also known -as "protobuf") as its standard serialization format for blockchain state and -wire communication. LBMJS by default supports Protocol Buffer serialization for -many of the standard queries and messages defined by the LBM SDK. This document -explains how you can make use of Protocol Buffer serialization for your own -custom modules with LBMJS. - -## Prerequisites - -- You are working on a TypeScript project. (Plain JS is possible but not covered - by this document. It should work if you just strip out the type information.) -- You have installed `@cosmjs/proto-signing`, `@cosmjs/tendermint-rpc` and - `@lbmjs/finschia` as dependencies. In general CosmJS dependencies should all - have the same version, and this document is accurate as of version 0.26. - ``` - "dependencies": { - "@cosmjs/proto-signing": "^0.26.4", - "@cosmjs/tendermint-rpc": "^0.26.4", - "@lbmjs/finschia": "^0.4.0-rc8", - // ... - } - ``` -- You have installed `ts-proto` as a development dependency. This document is - accurate as of version 1.84. -- You have installed [`protoc`](https://github.com/protocolbuffers/protobuf). - This document is accurate as of version 3.17. -- This document assumes that the Protocol Buffer definitions which you need are - already available somewhere in - [`.proto` files](https://developers.google.com/protocol-buffers/docs/proto). - -## Step 1: Acquire the definition files - -You will need these files locally. There are two ways this is typically done: - -1. **Git submodules** allow linking external repositories into the current - project's git. This is done in - [the lbmjs-types repo](https://github.com/line/lbmjs-types). - -If the proto files are not publicly available, the first way should be -preferred. Otherwise permission management can become very complicated. - -## Step 2: Generate codec files - -In LBMJS we use [ts-proto](https://github.com/stephenh/ts-proto) to generate -codec files, and in this document we assume you will follow the same route. Here -is an example usage: - -```sh -protoc \ - --plugin="./node_modules/.bin/protoc-gen-ts_proto" \ - --ts_proto_out="./path/to/output/directory" \ - --proto_path="./path/to/definitions" \ - --ts_proto_opt="esModuleInterop=true,forceLong=long,useOptionals=true" \ - "./path/to/definitions/file.proto" \ - "./path/to/definitions/another.proto" -``` - -Note that the available `ts-proto` options are described -[here](https://github.com/stephenh/ts-proto#supported-options). You can see the -script we used for the `@cosmjs/stargate` package -[here](https://github.com/cosmos/cosmjs/blob/v0.25.6/packages/stargate/scripts/define-proto.sh). - -### Working with Yarn 2+ - -The binary `./node_modules/.bin/protoc-gen-ts_proto` is not easily available -when using Yarn 2 or higher. You also need to execute `node` through `yarn`. In -such cases an executable wrapper script `bin/protoc-gen-ts_proto_yarn_2` with - -``` -#!/usr/bin/env -S yarn node -require('ts-proto/build/plugin') -``` - -helps. The name of the script renames the protoc plugin from `ts_proto` to -`ts_proto_yarn_2` and the `protoc` must now be prefixed accordingly, like -`--ts_proto_yarn_2_opt="…"`. - -A full example is available in the cosmjs-types repo: -[protoc-gen-ts_proto_yarn_2](https://github.com/line/lbmjs-types/blob/v0.46.0-rc7/bin/protoc-gen-ts_proto_yarn_2) -and -[codegen.sh](https://github.com/line/lbmjs-types/blob/v0.46.0-rc7/scripts/codegen.sh). - -### Step 3 - -In Step 2 we saw how the codec is generated (i.e. the TypeScript code -generation). Now we look into using this codec. This section is split in - -- Step 3a: custom messages -- Step 3b: custom queries - -## Step 3a: Instantiate a signing client using your custom message types - -This section assumes that your definition files included `MsgXxx` `message` -definitions for use in submitting transactions to a LBM SDK blockchain. You can -instantiate a signing client for Finschia which supports those message types -using a custom registry. We expose a `Registry` class from -`@cosmjs/proto-signing` for you to use, which maps type URLs to codec objects. -For example: - -```ts -import { DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; -import { - finschiaRegistryTypes as defaultRegistryTypes, - SigningFinschiaClient, -} from "@lbmjs/finschia"; -import { MsgXxx } from "./path/to/generated/codec/my/custom/tx"; // Replace with your own Msg import - -const myRegistry = new Registry(defaultRegistryTypes); -myRegistry.register("/my.custom.MsgXxx", MsgXxx); // Replace with your own type URL and Msg class -const mnemonic = // Replace with your own mnemonic - "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone"; - -// Inside an async function... -const signer = await DirectSecp256k1HdWallet.fromMnemonic( - mnemonic, - { prefix: "myprefix" }, // Replace with your own Bech32 address prefix -); -const client = await SigningFinschiaClient.connectWithSigner( - "my.endpoint.com", // Replace with your own RPC endpoint - signer, - { registry: myRegistry }, -); -``` - -Now when you want to sign and broadcast a transaction which contains a message -of your custom type, the client will know how to serialize (and deserialize) it: - -```ts -const myAddress = "wasm1pkptre7fdkl6gfrzlesjjvhxhlc3r4gm32kke3"; -const message = { - typeUrl: "/my.custom.MsgXxx", // Same as above - value: MsgXxx.fromPartial({ - foo: "bar", - }), -}; -const fee = { - amount: [ - { - denom: "udenom", // Use the appropriate fee denom for your chain - amount: "120000", - }, - ], - gas: "10000", -}; - -// Inside an async function... -// This method uses the registry you provided -const response = await client.signAndBroadcast(myAddress, [message], fee); -``` - -You can see a more complete example in Confio’s -[`ts-relayer` repo](https://github.com/confio/ts-relayer/blob/v0.3.1/src/lib/ibcclient.ts). - -### Step 3b: Instantiate a query client using your custom query service - -This section assumes that your definition files included a `Query` `service` -with `rpc` methods. `ts-proto` will generate a `QueryClientImpl` class which -needs to be provided with an RPC client. - -Creating an RPC client with the functionality required by this generated class -currently requires a few layers of abstraction. Here is how you can achieve it -using CosmJS helpers: - -```ts -import { createProtobufRpcClient, QueryClient } from "@cosmjs/stargate"; -import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; -import { QueryClientImpl } from "./path/to/generated/codec/my/custom/query"; - -// Inside an async function... -// The Tendermint client knows how to talk to the Tendermint RPC endpoint -const tendermintClient = await Tendermint34Client.connect("my.endpoint.com"); - -// The generic Stargate query client knows how to use the Tendermint client to submit unverified ABCI queries -const queryClient = new QueryClient(tendermintClient); - -// This helper function wraps the generic Stargate query client for use by the specific generated query client -const rpcClient = createProtobufRpcClient(queryClient); - -// Here we instantiate a specific query client which will have the custom methods defined in the .proto file -const queryService = new QueryClientImpl(rpcClient); - -// Now you can use this service to submit queries -const queryResult = await queryService.MyCustomQuery({ - foo: "bar", -}); -``` - -Additionally, we provide a system for extending `@cosmjs/stargate`’s -`QueryClient` with methods of your own design, wrapping those of the query -service. For this you will need to define your own `setupXxxExtension` functions -and pass them to the `QueryClient.withExtensions` static method like this: - -```ts -// Define your extensions -function setupXxxExtension(base: QueryClient) { - const rpcClient = createProtobufRpcClient(base); - const queryService = new QueryClientImpl(rpcClient); - - return { - mymodule: { - customQuery: async (foo: string) => - queryService.MyCustomQuery({ foo: foo }), - }, - }; -} -function setupYyyExtension(base: QueryClient) { - // ... -} - -// Setup the query client -const queryClient = QueryClient.withExtensions( - tendermintClient, - setupXxxExtension, - setupYyyExtension, - // You can add up to 18 extensions -); - -// Inside an async function... -// Now your query client has been extended -const queryResult = await queryClient.mymodule.customQuery("bar"); -``` - -You can see how LBMJS sets up the `foundation` extension for its default query -client -[here](https://github.com/cosmos/lbmjs/blob/v0.4.0-rc8/packages/finscha/src/modules/foundation/queries.ts).